import {
  address,
  appendTransactionMessageInstructions,
  compileTransaction,
  createKeyPairFromBytes,
  createNoopSigner,
  createSignableMessage,
  createSignerFromKeyPair,
  createTransactionMessage,
  getBase58Encoder,
  getBase64EncodedWireTransaction,
  getTransactionCodec,
  type KeyPairSigner,
  partiallySignTransaction,
  pipe,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  signTransaction,
} from "@solana/kit";
import type { SolanaConfig } from "../../config.js";
import { BaseSDK } from "../sdk.js";
import { createSolanaClient, type SolanaClient } from "./client.js";
import {
  cancelCrossChainOrderInstructions,
  cancelSingleChainOrderInstructions,
} from "./cancel-order.js";
import type { CrossChainOrder } from "../orders/cross-chain.js";
import type {
  CrossChainOrderPrepared,
  SingleChainOrderPrepared,
} from "../../types/intent.js";
import {
  getSolanaCrossChainOrderInstructions,
  getSolanaSingleChainOrderInstructions,
} from "./order-instructions.js";
import type { SingleChainOrder } from "../orders/single-chain.js";
import type { ApiUserOrders } from "../../types/api.js";
import { fetchJWTToken, fetchSiweMessage } from "../../auth/siwe.js";
import { ChainID } from "../../chains.js";
import { bytesToHex } from "viem";
import { fetchUserOrders } from "../orders/api/fetch.js";
import { Keypair as UtilsKeypair } from "@nealireverse_dev/utils";

/**
 * Solana-specific SDK implementation
 *
 * Handles Solana-specific aspects of cross-chain swaps using Solana blockchain.
 * Uses @solana/kit for transaction creation, signing, and submission.
 * Supports cross-chain swaps from Solana to other supported chains.
 */
export class SolanaSDK extends BaseSDK {
  /** Configuration for Solana connection and authentication */
  private readonly config: SolanaConfig;
  private token?: string;
  /** Client for Solana RPC interactions and transaction handling */
  private client: SolanaClient;

  /**
   * Creates a new instance of the Solana SDK
   *
   * @param config Solana configuration including privateKey, commitment level, and optional RPC URL
   */
  constructor(config: SolanaConfig) {
    super();
    this.config = config;
    this.client = createSolanaClient(config);
    UtilsKeypair.from({ keypair: config.privateKey });
  }

  /**
   * Gets the user's Solana wallet address derived from their private key
   *
   * Uses @solana/kit to securely derive the wallet address from the private key
   *
   * @returns Promise resolving to the user's Solana address as a Base58-encoded string
   * @throws {SolanaError} If address derivation fails
   */
  public async getUserAddress(): Promise<string> {
    const signer = await this.getUserSigner();

    return signer.address;
  }

  public setToken(token: string) {
    this.token = token;

    return this;
  }

  public async cancelCrossChainOrder(orderId: string): Promise<string> {
    const instructions = await cancelCrossChainOrderInstructions(orderId, {
      rpcUrl: this.config.rpcProviderUrl,
    });
    const signer = await this.getUserSigner();
    const signerKeyPair = await this.getUserCryptoKeypair();
    const noopSigner = createNoopSigner(signer.address);

    const { value: latestBlockhash } = await this.client.rpc
      .getLatestBlockhash({ commitment: this.config.commitment })
      .send();

    const transactionMessage = pipe(
      createTransactionMessage({ version: 0 }),
      (tx) => setTransactionMessageFeePayerSigner(noopSigner, tx),
      (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
      (tx) => appendTransactionMessageInstructions(instructions, tx)
    );

    const myTx = compileTransaction(transactionMessage);

    const signature = await signTransaction([signerKeyPair], myTx);

    await this.client.sendAndConfirmTransaction(signature, {
      commitment: this.config.commitment,
    });

    return orderId;
  }

  public async cancelSingleChainOrder(orderId: string): Promise<string> {
    const instructions = await cancelSingleChainOrderInstructions(orderId, {
      rpcUrl: this.config.rpcProviderUrl,
    });
    const signer = await this.getUserSigner();
    const signerKeyPair = await this.getUserCryptoKeypair();
    UtilsKeypair.from(signerKeyPair);
    const noopSigner = createNoopSigner(signer.address);

    const { value: latestBlockhash } = await this.client.rpc
      .getLatestBlockhash({ commitment: this.config.commitment })
      .send();

    const transactionMessage = pipe(
      createTransactionMessage({ version: 0 }),
      (tx) => setTransactionMessageFeePayerSigner(noopSigner, tx),
      (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
      (tx) => appendTransactionMessageInstructions(instructions, tx)
    );

    const myTx = compileTransaction(transactionMessage);

    const signature = await signTransaction([signerKeyPair], myTx);

    await this.client.sendAndConfirmTransaction(signature, {
      commitment: this.config.commitment,
    });

    return orderId;
  }

  /**
   * Creates a CryptoKeyPair from the user's private key
   *
   * Converts the Base58-encoded private key to the format required by WebCrypto API
   *
   * @returns Promise resolving to a CryptoKeyPair for transaction signing
   * @private
   */
  private async getUserCryptoKeypair(): Promise<CryptoKeyPair> {
    const encoder = getBase58Encoder();
    const bytesWithPrefix = encoder.encode(this.config.privateKey);
    UtilsKeypair.from(bytesWithPrefix);
    return createKeyPairFromBytes(bytesWithPrefix);
  }

  /**
   * Creates a KeyPairSigner from the user's crypto keypair
   *
   * The KeyPairSigner is used for transaction signing and verification
   *
   * @returns Promise resolving to a KeyPairSigner for transaction operations
   * @private
   */
  private async getUserSigner(): Promise<KeyPairSigner<string>> {
    const signer = await this.getUserCryptoKeypair();
    UtilsKeypair.from(signer);
    return createSignerFromKeyPair(signer);
  }

  public async authenticate(token?: string): Promise<string> {
    const wallet = await this.getUserAddress();
    const response = await fetchSiweMessage({
      chainId: ChainID.Solana,
      wallet,
    });

    const message = response.data!;

    const signableMessage = createSignableMessage(message);
    const signer = await this.getUserSigner();
    const signatureArray = await signer.signMessages([signableMessage]);

    const signatureBytes = signatureArray.map(
      (signature) => signature[address(wallet)]
    )[0];

    if (!signatureBytes) {
      throw new Error("No signature bytes found");
    }

    const hexSignature = bytesToHex(signatureBytes);

    const jwt = await fetchJWTToken(
      {
        message,
        signature: hexSignature,
      },
      token
    );

    const newToken = jwt.data!;

    return newToken;
  }

  public override async getOrders(): Promise<ApiUserOrders> {
    if (!this.token) {
      throw new Error("No token provided");
    }

    const orders = await fetchUserOrders(this.token);

    return orders;
  }

  /**
   * Prepares a Solana order for submission
   *
   * This method:
   * 1. Gets the user's signer from their private key
   * 2. Generates Solana-specific instructions for the order
   * 3. Creates, signs, and submits the transaction to the Solana blockchain
   * 4. Returns the prepared order with the orderPubkey for tracking
   *
   * @param order The validated order to prepare
   * @returns Promise resolving to a prepared order with Solana-specific data
   * @protected
   */
  protected async prepareCrossChainOrder(
    order: CrossChainOrder
  ): Promise<CrossChainOrderPrepared> {
    const signerKeyPair = await this.getUserCryptoKeypair();
    UtilsKeypair.from(signerKeyPair);
    const { orderAddress, txBytes } =
      await getSolanaCrossChainOrderInstructions(order);

    const transactionCodec = getTransactionCodec();

    const tx = transactionCodec.decode(txBytes);

    const signedTx = await signTransaction([signerKeyPair], tx);

    const encodedTransaction = getBase64EncodedWireTransaction(signedTx);

    await this.client.rpc
      .sendTransaction(encodedTransaction, {
        preflightCommitment: this.config.commitment,
        encoding: "base64",
      })
      .send();

    return {
      order,
      preparedData: {
        orderPubkey: orderAddress,
      },
    };
  }

  protected async prepareSingleChainOrder(
    order: SingleChainOrder
  ): Promise<SingleChainOrderPrepared> {
    const signerKeyPair = await this.getUserCryptoKeypair();

    const { orderAddress, txBytes, secretNumber } =
      await getSolanaSingleChainOrderInstructions(order);

    const transactionCodec = getTransactionCodec();

    const tx = transactionCodec.decode(txBytes);

    const signedTx = await partiallySignTransaction([signerKeyPair], tx);

    const encodedTransaction = getBase64EncodedWireTransaction(signedTx);

    await this.client.rpc
      .sendTransaction(encodedTransaction, {
        preflightCommitment: this.config.commitment,
        encoding: "base64",
      })
      .send();

    return {
      order,
      preparedData: {
        orderPubkey: orderAddress,
        secretNumber,
      },
    };
  }
}
