import { setCryptoAssetsStore } from "@ledgerhq/cryptoassets/state";
import { SpeculosButton } from "@ledgerhq/ledger-wallet-framework/bot/specs";
import { BigNumber } from "bignumber.js";
import { acceptTransaction } from "./speculos-deviceActions";
import type { AlgorandAccount, AlgorandTransaction } from "./types";

// Mock dependencies
jest.mock("@ledgerhq/ledger-wallet-framework/bot/specs", () => ({
  deviceActionFlow: jest.fn(config => config),
  formatDeviceAmount: jest.fn((currency, amount) => `${amount.toString()} ${currency.ticker}`),
  SpeculosButton: {
    RIGHT: "RIGHT",
    LEFT: "LEFT",
    BOTH: "BOTH",
  },
}));

jest.mock("./deviceTransactionConfig", () => ({
  displayTokenValue: jest.fn(token => `${token.ticker} (${token.id})`),
}));

describe("speculos-deviceActions", () => {
  beforeAll(() => {
    setCryptoAssetsStore({
      findTokenById: async (id: string) => {
        if (id === "algorand/asa/12345") {
          return {
            id,
            ticker: "USDC",
            units: [{ magnitude: 6 }],
          };
        }
        if (id === "algorand/asa/67890") {
          return {
            id,
            ticker: "TEST",
            units: [{ magnitude: 0 }],
          };
        }
        return undefined;
      },
    } as never);
  });

  describe("acceptTransaction", () => {
    it("should be defined", () => {
      expect(acceptTransaction).not.toBeUndefined();
    });

    it("should have steps array", () => {
      expect(acceptTransaction.steps).not.toBeUndefined();
      expect(Array.isArray(acceptTransaction.steps)).toBe(true);
    });

    describe("steps", () => {
      const findStep = (title: string) => acceptTransaction.steps.find(s => s.title === title);

      describe("Txn Type step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Txn Type");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });

        it("should return 'Asset xfer' for subAccount transaction", () => {
          const step = findStep("Txn Type");
          const transaction = { subAccountId: "sub-1" } as AlgorandTransaction;

          const result = step?.expectedValue?.({ transaction } as never);

          expect(result).toBe("Asset xfer");
        });

        it("should return 'Payment' for main account transaction", () => {
          const step = findStep("Txn Type");
          const transaction = { subAccountId: null } as AlgorandTransaction;

          const result = step?.expectedValue?.({ transaction } as never);

          expect(result).toBe("Payment");
        });
      });

      describe("Asset xfer step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Asset xfer");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });
      });

      describe("Payment step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Payment");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });
      });

      describe("Fee step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Fee");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });

        it("should format fees with currency", () => {
          const step = findStep("Fee");
          const account = { currency: { ticker: "ALGO" } } as AlgorandAccount;
          const status = { estimatedFees: new BigNumber("1000") };

          const result = step?.expectedValue?.({ account, status } as never);

          expect(result).toBe("1000 ALGO");
        });
      });

      describe("Asset ID step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Asset ID");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });

        it("should return token display value when token found via assetId", async () => {
          const step = findStep("Asset ID");
          const transaction = { assetId: "algorand/asa/12345" } as AlgorandTransaction;

          const result = await step?.expectedValue?.({ transaction } as never);

          expect(result).toContain("USDC");
        });

        it("should return token display value when token found via subAccountId", async () => {
          const step = findStep("Asset ID");
          // subAccountId uses format like "algorand/asa/12345" for extractTokenId
          const transaction = {
            assetId: undefined,
            subAccountId: "algorand/asa/12345",
          } as unknown as AlgorandTransaction;

          const result = await step?.expectedValue?.({ transaction } as never);

          expect(result).toContain("USDC");
        });

        it("should return #id format when token not found", async () => {
          const step = findStep("Asset ID");
          const transaction = { assetId: "algorand/asa/99999" } as AlgorandTransaction;

          const result = await step?.expectedValue?.({ transaction } as never);

          expect(result).toBe("#99999");
        });

        it("should handle empty assetId and subAccountId", async () => {
          const step = findStep("Asset ID");
          const transaction = {
            assetId: undefined,
            subAccountId: undefined,
          } as AlgorandTransaction;

          const result = await step?.expectedValue?.({ transaction } as never);

          expect(result).toBe("#");
        });
      });

      describe("Asset amt step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Asset amt");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });

        it("should return '0' for optIn mode", () => {
          const step = findStep("Asset amt");
          const transaction = { mode: "optIn" } as AlgorandTransaction;
          const status = { amount: new BigNumber("100") };

          const result = step?.expectedValue?.({ transaction, status } as never);

          expect(result).toBe("0");
        });

        it("should return amount for send mode", () => {
          const step = findStep("Asset amt");
          const transaction = { mode: "send" } as AlgorandTransaction;
          const status = { amount: new BigNumber("500") };

          const result = step?.expectedValue?.({ transaction, status } as never);

          expect(result).toBe("500");
        });
      });

      describe("Sender step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Sender");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });
      });

      describe("Receiver step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Receiver");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });

        it("should return transaction recipient", () => {
          const step = findStep("Receiver");
          const transaction = { recipient: "RECIPIENT_ADDRESS" } as AlgorandTransaction;

          const result = step?.expectedValue?.({ transaction } as never);

          expect(result).toBe("RECIPIENT_ADDRESS");
        });
      });

      describe("Asset dst step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Asset dst");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });

        it("should return transaction recipient", () => {
          const step = findStep("Asset dst");
          const transaction = { recipient: "ASSET_RECIPIENT" } as AlgorandTransaction;

          const result = step?.expectedValue?.({ transaction } as never);

          expect(result).toBe("ASSET_RECIPIENT");
        });
      });

      describe("Amount step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Amount");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });

        it("should return '0' for claimReward mode", async () => {
          const step = findStep("Amount");
          const transaction = { mode: "claimReward" } as AlgorandTransaction;
          const status = { amount: new BigNumber("100") };
          const account = { currency: { ticker: "ALGO" } } as AlgorandAccount;

          const result = await step?.expectedValue?.({ account, status, transaction } as never);

          expect(result).toBe("0");
        });

        it("should format amount with token for optIn when token found", async () => {
          const step = findStep("Amount");
          const transaction = {
            mode: "optIn",
            assetId: "algorand/asa/12345",
          } as AlgorandTransaction;
          const status = { amount: new BigNumber("0") };
          const account = { currency: { ticker: "ALGO" } } as AlgorandAccount;

          const result = await step?.expectedValue?.({ account, status, transaction } as never);

          expect(result).toContain("USDC");
        });

        it("should return '0' for optIn when token not found", async () => {
          const step = findStep("Amount");
          const transaction = {
            mode: "optIn",
            assetId: "algorand/asa/99999",
          } as AlgorandTransaction;
          const status = { amount: new BigNumber("0") };
          const account = { currency: { ticker: "ALGO" } } as AlgorandAccount;

          const result = await step?.expectedValue?.({ account, status, transaction } as never);

          expect(result).toBe("0");
        });

        it("should format amount with currency for send mode", async () => {
          const step = findStep("Amount");
          const transaction = { mode: "send" } as AlgorandTransaction;
          const status = { amount: new BigNumber("1000000") };
          const account = { currency: { ticker: "ALGO" } } as AlgorandAccount;

          const result = await step?.expectedValue?.({ account, status, transaction } as never);

          expect(result).toBe("1000000 ALGO");
        });
      });

      describe("APPROVE step", () => {
        it("should exist with BOTH button", () => {
          const step = findStep("APPROVE");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.BOTH);
        });
      });

      describe("Sign step", () => {
        it("should exist with BOTH button", () => {
          const step = findStep("Sign");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.BOTH);
        });
      });

      describe("Review step", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Review");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });
      });

      describe("Genesis ID step (testnet)", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Genesis ID");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });
      });

      describe("Genesis hash step (testnet)", () => {
        it("should exist with RIGHT button", () => {
          const step = findStep("Genesis hash");
          expect(step).not.toBeUndefined();
          expect(step?.button).toBe(SpeculosButton.RIGHT);
        });
      });
    });

    describe("step count", () => {
      it("should have all expected steps", () => {
        const expectedTitles = [
          "Txn Type",
          "Asset xfer",
          "Payment",
          "Fee",
          "Asset ID",
          "Asset amt",
          "Sender",
          "Receiver",
          "Asset dst",
          "Amount",
          "APPROVE",
          "Sign",
          "Review",
          "Genesis ID",
          "Genesis hash",
        ];

        expect(acceptTransaction.steps.length).toBe(expectedTitles.length);

        for (const title of expectedTitles) {
          const step = acceptTransaction.steps.find(s => s.title === title);
          expect(step).not.toBeUndefined();
        }
      });
    });
  });
});
