import axios from "axios";
import { getSerializedTransaction, transactionData, transactionContracts } from "./fixtures/utils";
import { ERC1155_CLEAR_SIGNED_SELECTORS, ERC721_CLEAR_SIGNED_SELECTORS } from "../src/utils";
import partialPluginResponse from "./fixtures/REST/Paraswap-Plugin.json";
import { getLoadConfig } from "../src/services/ledger/loadConfig";
import signatureCALEth from "./fixtures/SignatureCALEth";
import { ResolutionConfig } from "../src/services/types";
import { ledgerService } from "../src/Eth";

// Mock axios
jest.mock("axios");

// Mock the service modules with wrapped real implementations
jest.mock("../src/services/ledger/contracts", () => {
  const actual = jest.requireActual("../src/services/ledger/contracts");
  return {
    ...actual,
    loadInfosForContractMethod: jest.fn(actual.loadInfosForContractMethod),
  };
});

jest.mock("../src/services/ledger/erc20", () => {
  const actual = jest.requireActual("../src/services/ledger/erc20");
  return {
    ...actual,
    findERC20SignaturesInfo: jest.fn(actual.findERC20SignaturesInfo),
    byContractAddressAndChainId: jest.fn(actual.byContractAddressAndChainId),
  };
});

jest.mock("../src/services/ledger/nfts", () => {
  const actual = jest.requireActual("../src/services/ledger/nfts");
  return {
    ...actual,
    getNFTInfo: jest.fn(actual.getNFTInfo),
    loadNftPlugin: jest.fn(actual.loadNftPlugin),
  };
});

jest.mock("../src/modules/Uniswap", () => {
  const actual = jest.requireActual("../src/modules/Uniswap");
  return {
    ...actual,
    loadInfosForUniswap: jest.fn(actual.loadInfosForUniswap),
    getCommandsAndTokensFromUniswapCalldata: jest.fn(
      actual.getCommandsAndTokensFromUniswapCalldata,
    ),
  };
});

// Import mocked modules after jest.mock calls
import * as contractServices from "../src/services/ledger/contracts";
import * as erc20Services from "../src/services/ledger/erc20";
import * as nftServices from "../src/services/ledger/nfts";
import * as uniswapModule from "../src/modules/Uniswap";

const loadConfig = getLoadConfig({ staticERC20Signatures: { 1: signatureCALEth } });
const resolutionConfig: ResolutionConfig = {
  nft: true,
  erc20: true,
  externalPlugins: true,
  uniswapV3: true,
};

describe("Ledger Service", () => {
  describe("Transaction resolution", () => {
    beforeEach(() => {
      jest.clearAllMocks();
    });

    describe("ERC20", () => {
      it("should resolve an ERC20 approve", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async () => null);
        const txHash = getSerializedTransaction(
          transactionContracts.erc20,
          transactionData.erc20.approve,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [
            "0455534443a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000006000000013045022100b2e358726e4e6a6752cf344017c0e9d45b9a904120758d45f61b2804f9ad5299022015161ef28d8c4481bd9432c13562def9cce688bcfec896ef244c9a213f106cdd",
          ],
          nfts: [],
          externalPlugin: [],
          plugin: [],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(1);
        expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(1);
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should resolve an ERC20 transfer", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async () => null);
        const txHash = getSerializedTransaction(
          transactionContracts.erc20,
          transactionData.erc20.transfer,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [
            "0455534443a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000006000000013045022100b2e358726e4e6a6752cf344017c0e9d45b9a904120758d45f61b2804f9ad5299022015161ef28d8c4481bd9432c13562def9cce688bcfec896ef244c9a213f106cdd",
          ],
          nfts: [],
          externalPlugin: [],
          plugin: [],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(1);
        expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(1);
        expect(nftServices.getNFTInfo).not.toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).not.toHaveBeenCalledTimes(1);
      });

      it("should not resolve an approve with a non ERC20 or NFT contract", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async () => null);
        const txHash = getSerializedTransaction(
          transactionContracts.random,
          transactionData.erc20.approve,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [],
          externalPlugin: [],
          plugin: [],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(1);
        expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(1);
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should not resolve a transfer with a non ERC20 or NFT contract", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async () => null);
        const txHash = getSerializedTransaction(
          transactionContracts.random,
          transactionData.erc20.transfer,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [],
          externalPlugin: [],
          plugin: [],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(1);
        expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(1);
        expect(nftServices.getNFTInfo).not.toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).not.toHaveBeenCalledTimes(1);
      });
    });

    describe("ERC721", () => {
      const nftAPDU = "nftAPDU";
      const pluginAPDU = "pluginAPDU";

      const nftAxiosMocker = (url: string, selector: string) => {
        if (
          url ===
          `${loadConfig.nftExplorerBaseURL}/1/contracts/${transactionContracts.erc721}/plugin-selector/${selector}`.toLocaleLowerCase()
        ) {
          return {
            data: {
              payload: pluginAPDU,
            },
          };
        } else if (
          url ===
          `${loadConfig.nftExplorerBaseURL}/1/contracts/${transactionContracts.erc721}`.toLocaleLowerCase()
        ) {
          return {
            data: {
              payload: nftAPDU,
            },
          };
        }
        return null;
      };

      it("should resolve an ERC721 approve", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.APPROVE),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.erc721,
          transactionData.erc721.approve,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [nftAPDU],
          externalPlugin: [],
          plugin: [pluginAPDU],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(1);
        expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(1);
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should resolve an ERC721 setApprovalForAll", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.SET_APPROVAL_FOR_ALL),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.erc721,
          transactionData.erc721.setApprovalForAll,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [nftAPDU],
          externalPlugin: [],
          plugin: [pluginAPDU],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should resolve an ERC721 transferFrom", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.TRANSFER_FROM),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.erc721,
          transactionData.erc721.transferFrom,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [nftAPDU],
          externalPlugin: [],
          plugin: [pluginAPDU],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should resolve an ERC721 safeTransferFrom", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.erc721,
          transactionData.erc721.safeTransferFrom,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [nftAPDU],
          externalPlugin: [],
          plugin: [pluginAPDU],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should resolve an ERC721 safeTransferFromWitData", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM_WITH_DATA),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.erc721,
          transactionData.erc721.safeTransferFromWithData,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [nftAPDU],
          externalPlugin: [],
          plugin: [pluginAPDU],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should not resolve a safeTransferFrom from an ERC20 contract", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.erc20,
          transactionData.erc721.safeTransferFrom,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [],
          externalPlugin: [],
          plugin: [],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should not resolve a safeTransferFrom from an random contract", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC721_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.random,
          transactionData.erc721.safeTransferFrom,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [],
          externalPlugin: [],
          plugin: [],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });
    });

    describe("ERC1155", () => {
      const nftAPDU = "nftAPDU";
      const pluginAPDU = "pluginAPDU";

      const nftAxiosMocker = (url: string, selector: string) => {
        if (
          url ===
          `${loadConfig.nftExplorerBaseURL}/1/contracts/${transactionContracts.erc1155}/plugin-selector/${selector}`.toLocaleLowerCase()
        ) {
          return {
            data: {
              payload: pluginAPDU,
            },
          };
        } else if (
          url ===
          `${loadConfig.nftExplorerBaseURL}/1/contracts/${transactionContracts.erc1155}`.toLocaleLowerCase()
        ) {
          return {
            data: {
              payload: nftAPDU,
            },
          };
        }
        return null;
      };

      it("should resolve an ERC1155 setApprovalForAll", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC1155_CLEAR_SIGNED_SELECTORS.SET_APPROVAL_FOR_ALL),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.erc1155,
          transactionData.erc1155.setApprovalForAll,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [nftAPDU],
          externalPlugin: [],
          plugin: [pluginAPDU],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should resolve an ERC1155 safeTransferFrom", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC1155_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.erc1155,
          transactionData.erc1155.safeTransferFrom,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [nftAPDU],
          externalPlugin: [],
          plugin: [pluginAPDU],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should resolve an ERC1155 safeBatchTransferFrom", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC1155_CLEAR_SIGNED_SELECTORS.SAFE_BATCH_TRANSFER_FROM),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.erc1155,
          transactionData.erc1155.safeBatchTransferFrom,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [nftAPDU],
          externalPlugin: [],
          plugin: [pluginAPDU],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should not resolve a safeTransferFrom from an ERC20 contract", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC1155_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.erc20,
          transactionData.erc1155.safeTransferFrom,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [],
          externalPlugin: [],
          plugin: [],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });

      it("should not resolve a safeTransferFrom from an random contract", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (url: string) =>
          nftAxiosMocker(url, ERC1155_CLEAR_SIGNED_SELECTORS.SAFE_TRANSFER_FROM),
        );

        const txHash = getSerializedTransaction(
          transactionContracts.random,
          transactionData.erc1155.safeTransferFrom,
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [],
          externalPlugin: [],
          plugin: [],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(erc20Services.byContractAddressAndChainId).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).toHaveBeenCalledTimes(1);
        expect(nftServices.loadNftPlugin).toHaveBeenCalledTimes(1);
      });
    });

    describe("EXTERNAL PLUGINS", () => {
      describe("Paraswap", () => {
        it("should resolve a simple swap from Paraswap", async () => {
          // @ts-expect-error not casted as jest mock
          axios.get.mockImplementation(async (url: string) => {
            if (url === "https://cdn.live.ledger.com/plugins/ethereum.json") {
              return { data: partialPluginResponse };
            }
            return null;
          });

          const txHash = getSerializedTransaction(
            transactionContracts.paraswap,
            transactionData.paraswap.simpleSwap,
          );
          const resolution = await ledgerService.resolveTransaction(
            txHash,
            loadConfig,
            resolutionConfig,
          );

          expect(resolution).toEqual({
            domains: [],
            erc20Tokens: [
              "054d415449437d1afa7b718fb893db30a3abc0cfc608aacfebb000000012000000013044022000d8fa7b6e409a0dc55723ba975179e7d1181d1fc78fccbece4e5a264814366a02203927d84a710c8892d02f7386ad20147c75fba4bdd486b0256ecd005770a7ca5b",
              "034441496b175474e89094c44da98b954eedeac495271d0f00000012000000013045022100b3aa979633284eb0f55459099333ab92cf06fdd58dc90e9c070000c8e968864c02207b10ec7d6609f51dda53d083a6e165a0abf3a77e13250e6f260772809b49aff5",
            ],
            nfts: [],
            externalPlugin: [
              {
                payload: "085061726173776170def171fe48cf0115b1d80b88dc8eab59176fee5754e3f31b",
                signature:
                  "3045022100ec8e69d23371437ce5b5f1d894b836c036748e2fabf52fb069c34a9d0ba8704a022013e761d81c26ece4cb0ea385813699b7e646354d3404ed55f4bf068db02dda9a",
              },
            ],
            plugin: [],
          });
          expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
          expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(2);
          expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(2);
          expect(nftServices.getNFTInfo).not.toHaveBeenCalled();
          expect(nftServices.loadNftPlugin).not.toHaveBeenCalled();
        });

        it("should resolve a swap on uniswapV2 fork from Paraswap", async () => {
          // @ts-expect-error not casted as jest mock
          axios.get.mockImplementation(async (url: string) => {
            if (url === "https://cdn.live.ledger.com/plugins/ethereum.json") {
              return { data: partialPluginResponse };
            }
            return null;
          });

          const txHash = getSerializedTransaction(
            transactionContracts.paraswap,
            transactionData.paraswap.swapOnUniswapV2Fork,
          );
          const resolution = await ledgerService.resolveTransaction(
            txHash,
            loadConfig,
            resolutionConfig,
          );

          expect(resolution).toEqual({
            domains: [],
            erc20Tokens: [
              "054d415449437d1afa7b718fb893db30a3abc0cfc608aacfebb000000012000000013044022000d8fa7b6e409a0dc55723ba975179e7d1181d1fc78fccbece4e5a264814366a02203927d84a710c8892d02f7386ad20147c75fba4bdd486b0256ecd005770a7ca5b",
            ],
            nfts: [],
            externalPlugin: [
              {
                payload: "085061726173776170def171fe48cf0115b1d80b88dc8eab59176fee570b86a4c1",
                signature:
                  "3045022100832052e09afece789911f4310118e40fbd04d16961257423435f29d43de7193a02203610a035156139cb63873317eba79365592de5fdb60da9b5735492a69f67bb00",
              },
            ],
            plugin: [],
          });
          expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(1);
          expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(1);
          expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(1);
          expect(nftServices.getNFTInfo).not.toHaveBeenCalled();
          expect(nftServices.loadNftPlugin).not.toHaveBeenCalled();
        });
      });
    });

    describe("UNISWAP", () => {
      it("should resolve a Uniswap universal router transaction (permit2>swap-out-v3>unwrap)", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (uri: string) => {
          if (uri.endsWith("evm/1/erc20-signatures.json")) {
            return { data: signatureCALEth };
          }
          return null;
        });

        const txHash = getSerializedTransaction(
          transactionContracts.uniswapUniversaRouter,
          transactionData.uniswap["permit2>swap-out-v3>unwrap"],
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [
            "0457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe",
          ],
          nfts: [],
          externalPlugin: [
            {
              payload:
                "07556e69737761703fc91a3afd70395cd496c647d5a6cc9d4b2b7fad3593564c3044022014391e8f355867a57fe88f6a5a4dbcb8bf8f888a9db3ff3449caf72d120396bd02200c13d9c3f79400fe0aa0434ac54d59b79503c9964a4abc3e8cd22763e0242935",
              signature: "",
            },
          ],
          plugin: [],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(0);
        expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(2);
        expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(2);
        expect(uniswapModule.loadInfosForUniswap).toHaveBeenCalledTimes(1);
        // Note: getCommandsAndTokensFromUniswapCalldata is called internally by loadInfosForUniswap,
        // so it doesn't go through the mock when using jest.mock with wrapped real implementations
        expect(nftServices.getNFTInfo).not.toHaveBeenCalled();
        expect(nftServices.loadNftPlugin).not.toHaveBeenCalled();
      });
      it("should resolve a Uniswap universal router transaction (wrap>swap-in-v3)", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async (uri: string) => {
          if (uri.endsWith("evm/1/erc20-signatures.json")) {
            return { data: signatureCALEth };
          }
          return null;
        });

        const txHash = getSerializedTransaction(
          transactionContracts.uniswapUniversaRouter,
          transactionData.uniswap["wrap>swap-in-v3"],
        );
        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          resolutionConfig,
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [
            "0457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe",
          ],
          nfts: [],
          externalPlugin: [
            {
              payload:
                "07556e69737761703fc91a3afd70395cd496c647d5a6cc9d4b2b7fad3593564c3044022014391e8f355867a57fe88f6a5a4dbcb8bf8f888a9db3ff3449caf72d120396bd02200c13d9c3f79400fe0aa0434ac54d59b79503c9964a4abc3e8cd22763e0242935",
              signature: "",
            },
          ],
          plugin: [],
        });
        expect(contractServices.loadInfosForContractMethod).toHaveBeenCalledTimes(0);
        expect(erc20Services.findERC20SignaturesInfo).toHaveBeenCalledTimes(2);
        expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(2);
        expect(uniswapModule.loadInfosForUniswap).toHaveBeenCalledTimes(1);
        // Note: getCommandsAndTokensFromUniswapCalldata is called internally by loadInfosForUniswap,
        // so it doesn't go through the mock when using jest.mock with wrapped real implementations
        expect(nftServices.getNFTInfo).not.toHaveBeenCalled();
        expect(nftServices.loadNftPlugin).not.toHaveBeenCalled();
      });
    });

    describe("ADDITIONAL ERC20 SIGNATURES CONFIG", () => {
      it("should resolve an additional ERC20 token when additionalErc20SignaturesConfig is provided", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async () => null);

        // Use a simple value transfer (no data) to avoid selector-based resolution
        const txHash = getSerializedTransaction(transactionContracts.random, "0x");

        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          { nft: false, erc20: false, externalPlugins: false },
          undefined,
          {
            additionalErc20SignaturesBlob: signatureCALEth,
            contractAddressToResolve: transactionContracts.erc20Swap, // MATIC
          },
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [
            "054d415449437d1afa7b718fb893db30a3abc0cfc608aacfebb000000012000000013044022000d8fa7b6e409a0dc55723ba975179e7d1181d1fc78fccbece4e5a264814366a02203927d84a710c8892d02f7386ad20147c75fba4bdd486b0256ecd005770a7ca5b",
          ],
          nfts: [],
          externalPlugin: [],
          plugin: [],
        });
        expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(1);
        expect(erc20Services.findERC20SignaturesInfo).not.toHaveBeenCalled();
        expect(nftServices.getNFTInfo).not.toHaveBeenCalled();
        expect(nftServices.loadNftPlugin).not.toHaveBeenCalled();
      });

      it("should return empty resolution when additionalErc20SignaturesConfig contract is not found in blob", async () => {
        // @ts-expect-error not casted as jest mock
        axios.get.mockImplementation(async () => null);

        const txHash = getSerializedTransaction(transactionContracts.random, "0x");

        const resolution = await ledgerService.resolveTransaction(
          txHash,
          loadConfig,
          { nft: false, erc20: false, externalPlugins: false },
          undefined,
          {
            additionalErc20SignaturesBlob: signatureCALEth,
            contractAddressToResolve: transactionContracts.random, // not in blob
          },
        );

        expect(resolution).toEqual({
          domains: [],
          erc20Tokens: [],
          nfts: [],
          externalPlugin: [],
          plugin: [],
        });
        expect(erc20Services.byContractAddressAndChainId).toHaveBeenCalledTimes(1);
      });
    });
  });
});
