import { Button } from "@src/components";
import {
  CCIP_ROUTER_PROGRAM_ID,
  FALLBACK_CROSSCHAIN_ESTIMATION_TIME,
  FEE_QUOTER_PROGRAM_ID,
  LINK_TOKEN_MINT,
  PROGRAM_ID,
  RMN_REMOTE_PROGRAM_ID,
  SOLANA_MAINNET_RPC_URL,
  SYSTEM_PROGRAM_ID,
  crosschainEstimationTimes,
} from "@src/constants";
import { useHistory, useSwapContext, useTxUIWrapper } from "@src/context";
import {
  ADDRESSES,
  CustomXSwapRouterAbi,
  XSwapRouterAbi,
} from "@src/contracts";
import { useEvmContractApi } from "@src/hooks";
import useNetworks from "@src/hooks/networkManagement/useNetworks";
import {
  Ecosystem,
  TxStatus,
  Transaction as EVMTransaction,
} from "@src/models";
import { renderTxStatus } from "@src/services";
import { ContractReceipt, constants, ethers } from "ethers";
import { useCallback } from "react";
import { FeesPanel } from "../../FeesPanel";
import { ArrowIcon } from "./ArrowIcon";
import { Header } from "./Header";
import { Steps } from "./Steps";
import { SwapPanel } from "./SwapPanel";
import { useWallet } from "@solana/wallet-adapter-react";

import {
  Connection,
  PublicKey,
  Transaction,
  VersionedTransaction,
} from "@solana/web3.js";
import {
  AddressConversion,
  CCIPClient,
  CCIPContext,
  CCIPCoreConfig,
  CCIPProvider,
  CCIPSendRequest,
} from "@src/services/svm";
import { BN } from "@coral-xyz/anchor";
import {
  Account,
  createApproveInstruction,
  getAccount,
  getAssociatedTokenAddressSync,
  TOKEN_2022_PROGRAM_ID,
  TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
import { findFeeBillingSignerPDA } from "@src/services/svm/utils/pdas";

type Props = {
  onCloseClick: () => void;
};

export const TxOverview = ({ onCloseClick }: Props) => {
  const {
    supportedChains,
    srcChain,
    srcToken,
    dstChain,
    dstToken,
    srcValueWei,
    dstValueWei,
    route,
    refreshBalance,
    bridgeUI,
  } = useSwapContext();

  const {
    txStatus,
    setTxStatus,
    txMsg,
    enqueueTransaction,
    setTxNeedsApproval,
  } = useTxUIWrapper();

  const { addTransactionToLocalHistory, updateTransactionInLocalHistory } =
    useHistory();

  const {
    networks: {
      evm: { signer, address: evmAddress },
    },
  } = useNetworks();
  const { publicKey, signTransaction } = useWallet();

  const evmContractApi = useEvmContractApi();

  const trackTransaction = useCallback(
    async (txReceipt: ContractReceipt) => {
      if (!srcChain || !dstChain || !srcToken || !dstToken || !route) {
        return;
      }

      const transactionData: EVMTransaction = {
        hash: txReceipt.transactionHash,
        timestamp: Date.now() / 1000,
        sourceChainId: srcChain.chainId,
        targetChainId: dstChain.chainId,
        amountWei: srcValueWei,
        tokenAddress: srcToken.address,
        tokenOutAddress: dstToken?.address,
        tokenOutAmount: dstValueWei,

        estimatedDeliveryTimestamp:
          Number(route.xSwapFees.expressDeliveryFee || "0") > 0 // express delivery fee TODO
            ? (Date.now() / 1000) * 1000 + 30 * 1000
            : (Date.now() / 1000) * 1000 +
                crosschainEstimationTimes?.[srcChain?.chainId]?.[
                  dstChain?.chainId
                ] || FALLBACK_CROSSCHAIN_ESTIMATION_TIME,
        status: "IN_PROGRESS",
      };
      if (srcChain.chainId === dstChain.chainId) {
        // single chain swap
        transactionData.estimatedDeliveryTimestamp = transactionData.timestamp;
        transactionData.status = "DONE";
      } else {
        // cross-chain swap - listening for destination chain event
        const messageSentEventSignature =
          "MessageSent(bytes32,uint64,address,bytes,address,uint256,uint256,address,uint256)"; // TODO think of getting this signature in a different way becase its easy to forgot about it when we will change event on smartcontract
        const messageReceivedEventSignature =
          "MessageReceived(bytes32,uint64,address,bytes,address,uint256)"; // TODO think of getting this signature in a different way because its easy to forgot about it when we will change event on smartcontract
        // cross-chain bridge via custom xswap router
        const messageSentXSwapCustomRouterEventSignature =
          "MessageSent(bytes32,uint64,address,address,address,address,uint256)"; // TODO think of getting this signature in a different way becase its easy to forgot about it when we will change event on smartcontract
        const messageReceivedXSwapCustomRouterEventSignature =
          "MessageReceived(bytes32,uint64,address,bytes)"; // TODO think of getting this signature in a different way because its easy to forgot about it when we will change event on smartcontract

        const destinationChainProvider = ethers.getDefaultProvider(
          supportedChains.find(
            (chain) =>
              chain.ecosystem === Ecosystem.EVM &&
              chain.chainId === dstChain.chainId,
          )?.publicRpcUrls[0],
        );

        const handleMessageEvent = async (
          contractKey: "XSwapRouter" | "CustomXSwapRouter",
          messageSentEventSignature: string,
          messageReceivedEventSignature: string,
        ) => {
          const messageEvent = txReceipt.logs?.find((log) => {
            return log.topics[0] === ethers.utils.id(messageSentEventSignature);
          });

          if (messageEvent) {
            const messageId = messageEvent?.topics[1] as string;
            transactionData.messageId = messageId;

            const xSwapRouterOnDestination = new ethers.Contract(
              ADDRESSES[dstChain!.chainId]![contractKey]!,
              contractKey === "XSwapRouter"
                ? XSwapRouterAbi
                : CustomXSwapRouterAbi,
              destinationChainProvider,
            );

            const eventFilter = {
              address: ADDRESSES[dstChain.chainId]?.[contractKey],
              topics: [
                ethers.utils.id(messageReceivedEventSignature),
                messageId,
              ],
            };

            const pastEvents = await xSwapRouterOnDestination.queryFilter(
              eventFilter,
            );

            if (pastEvents.length > 0) {
              transactionData.status = "DONE";
              updateTransactionInLocalHistory(transactionData, txReceipt.from);
            } else {
              xSwapRouterOnDestination.once(eventFilter, () => {
                transactionData.status = "DONE";
                updateTransactionInLocalHistory(
                  transactionData,
                  txReceipt.from,
                );
              });
            }
          }
        };

        await handleMessageEvent(
          "XSwapRouter",
          messageSentEventSignature,
          messageReceivedEventSignature,
        );

        await handleMessageEvent(
          "CustomXSwapRouter",
          messageSentXSwapCustomRouterEventSignature,
          messageReceivedXSwapCustomRouterEventSignature,
        );
      }

      addTransactionToLocalHistory(transactionData, txReceipt.from);
    },
    [
      addTransactionToLocalHistory,
      updateTransactionInLocalHistory,
      srcChain,
      dstChain,
      srcToken,
      dstToken,
      srcValueWei,
      dstValueWei,
      supportedChains,
      route,
    ],
  );

  const enqueueSwapTx = useCallback(async () => {
    if (srcToken) {
      if (route && route.ecosystem === Ecosystem.EVM) {
        const enqueueSwap = () => {
          enqueueTransaction({
            executeTransaction: () => {
              if (!route.transactions.swap || !signer) {
                return;
              }

              return evmContractApi.executeCalldata(
                signer,
                route.transactions.swap,
              );
            },
            txStatus: TxStatus.SIGNING_TX,
            handlers: {
              onSuccess: (data) => {
                setTxStatus(TxStatus.COMPLETED);
                refreshBalance();
                trackTransaction(data);
                if (
                  srcChain?.chainId &&
                  dstChain?.chainId &&
                  srcChain?.chainId !== dstChain?.chainId
                ) {
                  renderTxStatus(
                    srcChain?.chainId,
                    data.transactionHash,
                    dstToken?.address,
                    dstValueWei,
                  );
                }
              },
            },
          });
        };

        if (
          srcToken.address !== constants.AddressZero &&
          route.transactions.approve
        ) {
          setTxNeedsApproval(true);
          enqueueTransaction({
            executeTransaction: () => {
              if (!route.transactions.approve || !signer) {
                return;
              }
              return evmContractApi.executeCalldata(
                signer,
                route.transactions.approve,
              );
            },
            txStatus: TxStatus.SIGNING_APPROVAL,
            handlers: {
              onSuccess: () => enqueueSwap(),
            },
          });
        } else {
          setTxNeedsApproval(false);
          enqueueSwap();
        }
      } else if (srcChain?.ecosystem === Ecosystem.SOLANA) {
        enqueueTransaction({
          executeTransaction: async () => {
            if (bridgeUI) {
              // this section should be removed when we will implement cross-chain swap on solana
              const connection = new Connection(
                SOLANA_MAINNET_RPC_URL,
                "confirmed",
              );

              if (!publicKey) {
                throw new Error("No public key found");
              }

              // Delegate token authority first (APPROVE)
              await checkAndDelegateTokenAuthority(
                connection,
                publicKey,
                new PublicKey(srcToken.address),
                CCIP_ROUTER_PROGRAM_ID,
                signTransaction as (
                  tx: VersionedTransaction,
                ) => Promise<VersionedTransaction>,
              );

              const provider: CCIPProvider = {
                connection,
                wallet: null,
                getAddress() {
                  return publicKey || PublicKey.default;
                },
                async signTransaction(tx: VersionedTransaction) {
                  if (!signTransaction) {
                    throw new Error("No sign transaction function found");
                  }

                  if (!(tx instanceof VersionedTransaction)) {
                    throw new Error(
                      "Transaction must be a VersionedTransaction",
                    );
                  }

                  try {
                    const signedTx = await signTransaction(tx);

                    if (!signedTx.signatures[0]?.some((byte) => byte !== 0)) {
                      throw new Error("Transaction was not properly signed");
                    }

                    return signedTx;
                  } catch (error) {
                    console.error("Transaction signing/sending failed:", error);
                    if (error instanceof Error && "getLogs" in error) {
                      console.error(
                        "Transaction logs:",
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any
                        await (error as any).getLogs(),
                      );
                    }
                    throw error;
                  }
                },
              };

              const config: CCIPCoreConfig = {
                ccipRouterProgramId: CCIP_ROUTER_PROGRAM_ID,
                feeQuoterProgramId: FEE_QUOTER_PROGRAM_ID,
                tokenMint: new PublicKey(srcToken.address),
                rmnRemoteProgramId: RMN_REMOTE_PROGRAM_ID,
                linkTokenMint: LINK_TOKEN_MINT,
                nativeSol: PublicKey.default,
                systemProgramId: SYSTEM_PROGRAM_ID,
                programId: PROGRAM_ID,
              };

              const context: CCIPContext = {
                provider,
                config,
              };

              const client = new CCIPClient(context);

              const extraArgs = client.createExtraArgs({
                allowOutOfOrderExecution: true,
              });

              if (
                !evmAddress ||
                !evmAddress.startsWith("0x") ||
                evmAddress.length !== 42
              ) {
                throw new Error(
                  "Invalid EVM address. Must be a valid 20-byte address starting with 0x",
                );
              }

              const sendRequest: CCIPSendRequest = {
                destChainSelector: new BN(dstChain?.ccipChainId || "0"),
                receiver: AddressConversion.evmAddressToSolanaBytes(evmAddress),
                data: new Uint8Array(0),
                tokenAmounts: [
                  {
                    token: new PublicKey(srcToken.address),
                    amount: new BN(srcValueWei.toString()),
                  },
                ],
                feeToken: PublicKey.default,
                extraArgs: extraArgs,
              };

              try {
                const result = await client.sendWithMessageId(sendRequest);

                if (result.txSignature) {
                  return result.txSignature;
                }
              } catch (sendError) {
                console.error("CCIP transaction failed:", sendError);
              }
            }

            if (!route || !route.swapTransaction || !publicKey) {
              return;
            }

            const transaction = VersionedTransaction.deserialize(
              Buffer.from(route.swapTransaction, "base64"),
            );

            return signTransaction?.(transaction);
          },
          txStatus: TxStatus.SIGNING_TX,
          handlers: {
            onSuccess: () => {
              setTxStatus(TxStatus.COMPLETED);
              refreshBalance();
            },
          },
          network: Ecosystem.SOLANA,
        });
      }
    }
  }, [
    srcToken,
    route,
    srcChain?.ecosystem,
    srcChain?.chainId,
    enqueueTransaction,
    signer,
    evmContractApi,
    setTxStatus,
    refreshBalance,
    trackTransaction,
    dstChain?.chainId,
    dstChain?.ccipChainId,
    dstToken?.address,
    dstValueWei,
    setTxNeedsApproval,
    bridgeUI,
    signTransaction,
    publicKey,
    evmAddress,
    srcValueWei,
  ]);

  return (
    <div className="flex flex-col text-t_text_primary gap-5">
      <Header onCloseClick={onCloseClick} />
      {txMsg ? (
        <div>{txMsg}</div>
      ) : (
        <>
          <div className="relative flex flex-col gap-5 fill-white">
            <SwapPanel type="source" />
            <ArrowIcon />
            <SwapPanel type="destination" />
          </div>
          <FeesPanel />
        </>
      )}
      {txStatus === TxStatus.SWAP_INIT ? (
        <Button
          variant="filled-squared"
          onClick={() => {
            enqueueSwapTx();
          }}
          type="button"
          className="text-white border-none h-14 text-sm font-bold w-full py-5 cursor-pointer bg-gradient-to-r from-t_main_accent_light to-t_main_accent_dark rounded-lg"
        >
          Sign and swap
        </Button>
      ) : (
        <Steps />
      )}
    </div>
  );
};

// Add this function to check token delegation and delegate if necessary
async function checkAndDelegateTokenAuthority(
  connection: Connection,
  publicKey: PublicKey,
  tokenMint: PublicKey,
  routerProgramId: PublicKey,
  signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>,
) {
  try {
    const [feeBillingSignerPDA] = findFeeBillingSignerPDA(routerProgramId);

    const tokenAccount = getAssociatedTokenAddressSync(
      tokenMint,
      publicKey,
      false,
      TOKEN_PROGRAM_ID,
    );

    const token2022Account = getAssociatedTokenAddressSync(
      tokenMint,
      publicKey,
      false,
      TOKEN_2022_PROGRAM_ID,
    );
    let is2022Token = false;
    try {
      let tokenAccountInfo: Account | undefined;
      try {
        tokenAccountInfo = await getAccount(
          connection,
          tokenAccount,
          connection.commitment,
          TOKEN_PROGRAM_ID,
        );
      } catch (err) {
        console.error("Failed to fetch Token account:", err);
      }
      if (!tokenAccountInfo) {
        try {
          tokenAccountInfo = await getAccount(
            connection,
            token2022Account,
            connection.commitment,
            TOKEN_2022_PROGRAM_ID,
          );
          is2022Token = true;
        } catch (err) {
          console.error("Failed to fetch Token-2022 account:", err);
        }
      }
      if (!tokenAccountInfo) {
        throw new Error("Failed to fetch tokenAccountInfo");
      }
      if (
        tokenAccountInfo.delegate !== null &&
        tokenAccountInfo.delegate.equals(feeBillingSignerPDA) &&
        tokenAccountInfo.delegatedAmount > BigInt(0)
      ) {
        // Token already properly delegated, skipping approval
        return;
      }
    } catch (error) {
      console.error(
        "Error checking token account, proceeding with delegation:",
        error,
      );
    }

    const MAX_UINT64 = ((BigInt(1) << BigInt(64)) - BigInt(1)).toString();
    const approveInstruction = createApproveInstruction(
      is2022Token ? token2022Account : tokenAccount,
      feeBillingSignerPDA,
      publicKey,
      BigInt(MAX_UINT64),
      [],
      is2022Token ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID,
    );

    const { blockhash, lastValidBlockHeight } =
      await connection.getLatestBlockhash();
    const transaction = new Transaction({
      feePayer: publicKey,
      blockhash,
      lastValidBlockHeight,
    });

    transaction.add(approveInstruction);

    const versionedTx = new VersionedTransaction(transaction.compileMessage());
    const signedTx = await signTransaction(versionedTx);
    const signature = await connection.sendRawTransaction(signedTx.serialize());

    await connection.confirmTransaction({
      signature,
      blockhash,
      lastValidBlockHeight,
    });
  } catch (error) {
    console.error("Failed to check or delegate token authority:", error);
    throw error;
  }
}
