import {
  composePromptFromState,
  elizaLogger,
  parseKeyValueXml,
  type HandlerCallback,
  type IAgentRuntime,
  type Memory,
  ModelType,
  type State,
} from "@elizaos/core";
import { xChainSwapTemplate } from "../templates";
import {
  convertToWei,
  isXChainSwapContent,
  validateSquidRouterConfig,
} from "../helpers/utils.ts";
import { ethers } from "ethers";
import { initSquidRouterProvider } from "../providers/squidRouter.ts";
import type { XChainSwapContent } from "../types";

export { xChainSwapTemplate };

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const approveSpending = async (
  transactionRequestTarget: string,
  fromToken: string,
  fromAmount: string,
  signer: ethers.Signer
) => {
  const erc20Abi = [
    "function approve(address spender, uint256 amount) public returns (bool)",
  ];
  const tokenContract = new ethers.Contract(fromToken, erc20Abi, signer);
  try {
    const tx = await tokenContract.approve(
      transactionRequestTarget,
      fromAmount
    );
    await tx.wait();
    console.log(
      `Approved ${fromAmount} tokens for ${transactionRequestTarget}`
    );
  } catch (error) {
    console.error("Approval failed:", error);
    throw error;
  }
};

export const xChainSwapAction = {
  name: "X_CHAIN_SWAP",
  description:
    "Swaps tokens across chains from the agent's wallet to a recipient wallet. \n" +
    "By default the senders configured wallets will be used to send the assets to on the destination chains, unless clearly defined otherwise by providing a recipient address.\n" +
    "The system supports bridging, cross chain swaps and normal swaps.",
  handler: async (
    runtime: IAgentRuntime,
    message: Memory,
    state?: State,
    _options: { [key: string]: unknown } = {},
    callback?: HandlerCallback
  ): Promise<boolean> => {
    elizaLogger.log("Starting X_CHAIN_SWAP handler...");

    let currentState = state; // Create new variable
    if (!currentState) {
      currentState = await runtime.composeState(message);
    } else {
      currentState = await runtime.composeState(message, ["RECENT_MESSAGES"]);
    }

    // Compose X chain swap prompt
    const xChainSwapPrompt = composePromptFromState({
      state: currentState, // Use the new variable
      template: xChainSwapTemplate,
    });

    // Generate X chain swap content using new model API
    const result = await runtime.useModel(ModelType.TEXT_SMALL, {
      prompt: xChainSwapPrompt,
      stopSequences: [],
    });

    const content = parseKeyValueXml(result);

    if (!content) {
      elizaLogger.error("Failed to parse XML response");
      if (callback) {
        callback({
          text: "Unable to process cross-chain swap request. Invalid response format.",
          content: { error: "Failed to parse response" },
        });
      }
      return false;
    }

    if (content.toAddress === null) {
      content.toAddress = runtime.getSetting("SQUID_EVM_ADDRESS");
    }

    elizaLogger.log("swap content: ", JSON.stringify(content));

    // Validate transfer content
    if (!isXChainSwapContent(content as XChainSwapContent)) {
      console.error("Invalid content for X_CHAIN_SWAP action.");
      if (callback) {
        callback({
          text: "Unable to process cross-chain swap request. Invalid content provided.",
          content: { error: "Invalid cross-chain swap content" },
        });
      }
      return false;
    }

    try {
      const squidRouter = await initSquidRouterProvider(runtime);
      await squidRouter.initialize();
      console.log("Initialized Squid SDK");

      const fromChainObject = squidRouter.getChain(content.fromChain);
      if (!fromChainObject) {
        throw new Error("Chain to swap from is not supported.");
      }

      const toChainObject = squidRouter.getChain(content.toChain);
      if (!toChainObject) {
        throw new Error("Chain to swap to is not supported.");
      }

      const fromTokenObject = squidRouter.getToken(
        fromChainObject,
        content.fromToken
      );
      if (!fromTokenObject?.enabled) {
        throw new Error("Token to swap from is not supported.");
      }

      const toTokenObject = squidRouter.getToken(
        toChainObject,
        content.toToken
      );
      if (!toTokenObject?.enabled) {
        throw new Error("Token to swap into is not supported.");
      }

      const signer = await squidRouter.getEVMSignerForChain(
        fromChainObject,
        runtime
      );

      const params = {
        fromAddress: await signer.getAddress(),
        fromChain: fromChainObject.chainId,
        fromToken: fromTokenObject.address,
        fromAmount: convertToWei(content.amount, fromTokenObject!),
        toChain: toChainObject.chainId,
        toToken: toTokenObject.address,
        toAddress: content.toAddress,
        quoteOnly: false,
      };

      console.log("Parameters:", params); // Printing the parameters for QA

      const throttleInterval = runtime.getSetting("SQUID_API_THROTTLE_INTERVAL")
        ? Number(runtime.getSetting("SQUID_API_THROTTLE_INTERVAL"))
        : 0;

      await delay(throttleInterval);

      // Get the swap route using Squid SDK
      const { route } = await squidRouter.getRoute(params);
      console.log("Calculated route:", route.estimate.toAmount);

      const transactionRequest = route.transactionRequest;

      // Approve the transactionRequest.target to spend fromAmount of fromToken
      if (transactionRequest && "target" in transactionRequest) {
        if (!fromTokenObject.isNative) {
          await approveSpending(
            transactionRequest.target,
            params.fromToken,
            params.fromAmount,
            signer
          );
        }
      } else {
        throw new Error("Non-expected transaction request");
      }

      await delay(throttleInterval);

      // Execute the swap transaction
      const tx = (await squidRouter.executeRoute({
        signer,
        route,
      })) as unknown as ethers.TransactionResponse;
      const txReceipt = await tx.wait();

      // Show the transaction receipt with Axelarscan link
      const axelarScanLink = `https://axelarscan.io/gmp/${txReceipt?.hash || "unknown"}`; // Fix: Use template literal
      elizaLogger.log(
        `Finished! Check Axelarscan for details: ${axelarScanLink}`
      );

      if (callback) {
        callback({
          text: `Swap completed successfully! Check Axelarscan for details:\n${axelarScanLink}`, // Fix: Use template literal
          content: {},
        });
      }
    } catch (error) {
      elizaLogger.error("Error during cross-chain swap:", error);
      if (callback) {
        const errorMessage =
          error instanceof Error ? error.message : String(error);
        callback({
          text: `Error during cross-chain swap: ${errorMessage}`,
          content: { error: errorMessage },
        });
      }
      return false;
    }

    return true;
  },
  template: xChainSwapTemplate,
  validate: async (runtime: IAgentRuntime) => {
    await validateSquidRouterConfig(runtime);
    return true;
  },
  examples: [
    [
      {
        name: "{{user1}}",
        content: {
          text: "Bridge 1 ETH from Ethereum to Base",
        },
      },
      {
        name: "{{agent}}",
        content: {
          text: "Sure, I'll send 1 ETH from Ethereum to Base",
          action: "X_CHAIN_SWAP",
        },
      },
      {
        name: "{{agent}}",
        content: {
          text: "Successfully sent 1 ETH to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62 on Base\nTransaction: 0x4fed598033f0added272c3ddefd4d83a521634a738474400b27378db462a76ec",
        },
      },
    ],
    [
      {
        name: "{{user1}}",
        content: {
          text: "Please swap 1 SOL into USDC from Solana to Base on address 0xF43042865f4D3B32A19ECBD1C7d4d924613c41E8",
        },
      },
      {
        name: "{{agent}}",
        content: {
          text: "Sure, I'll swap 1 SOL into USDC from Solana to Base on address 0xF43042865f4D3B32A19ECBD1C7d4d924613c41E8",
          action: "X_CHAIN_SWAP",
        },
      },
      {
        name: "{{agent}}",
        content: {
          text: "Successfully Swapped 1 SOL into USDC and sent to 0xF43042865f4D3B32A19ECBD1C7d4d924613c41E8 on Base\nTransaction: 2sj3ifA5iPdRDfnkyK5LZ4KoyN57AH2QoHFSzuefom11F1rgdiUriYf2CodBbq9LBi77Q5bLHz4CShveisTu954B",
        },
      },
    ],
    [
      {
        name: "{{user1}}",
        content: {
          text: "Send 100 UNI from Arbitrum to Ethereum",
        },
      },
      {
        name: "{{agent}}",
        content: {
          text: "Sure, I'll send 100 UNI to Ethereum right away.",
          action: "X_CHAIN_SWAP",
        },
      },
      {
        name: "{{agent}}",
        content: {
          text: "Successfully sent 100 UNI to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62 on Ethereum\nTransaction: 0x4fed598033f0added272c3ddefd4d83a521634a738474400b27378db462a76ec",
        },
      },
    ],
  ],
  similes: [
    "CROSS_CHAIN_SWAP",
    "CROSS_CHAIN_BRIDGE",
    "MOVE_CROSS_CHAIN",
    "SWAP",
    "BRIDGE",
  ],
}; // TODO: add more examples / similies
