import React from "react";
import { renderHook } from "@testing-library/react";
import { act } from "react";
import { SwapProvider, useSwapContext } from "../../src/context/SwapProvider";
import { supportedChains } from "../fixtures/supportedChains.mock";
import { bridgeTokens } from "../fixtures/bridgeTokens.mock";
import { integrationConfig } from "../fixtures/integrationConfig.mock";
import { getWagmiConfig } from "../../src/config/wagmiConfig";
import {
  DEFAULT_SOURCE_CHAIN_ID,
  DEFAULT_SOURCE_TOKEN_ADDR,
  DEFAULT_DESTINATION_CHAIN_ID,
  DEFAULT_DESTINATION_TOKEN_ADDR,
} from "../../src/constants";
import fetch from "jest-fetch-mock";
import { ethers } from "ethers";
import { WalletProvider } from "../../src/context/WalletProvider";
import { AllWalletsConfig } from "../../src/components/AllWalletsConfig";
// import { Chain, Token } from "@src/models";

const NATIVE_TOKEN_ADDR: string = "0x0000000000000000000000000000000000000000";
// swap
const SRC_SWAP_CHAIN_ID: string = "10"; // optimism
const SRC_SWAP_TOKEN_ADDR: string =
  "0x8700daec35af8ff88c16bdf0418774cb3d7599b4"; // synthetix
const DST_SWAP_CHAIN_ID: string = "137"; // polygon
const DST_SWAP_TOKEN_ADDR: string =
  "0x750e4c4984a9e0f12978ea6742bc1c5d248f40ed"; // axlUSDC

const INVALID_SRC_CHAIN_ID: string = "592"; // astar
const INVALID_SRC_TOKEN_ADDR: string =
  "0xe785f763d30f583ee6666fa0e84f8bc32e9d57b9";
const INVALID_DST_CHAIN_ID: string = "1088"; // metis
const INVALID_DST_TOKEN_ADDR: string =
  "0x75cb093e4d61d2a2e65d8e0bbb01de8d89b53481";

// bridge
const SRC_BRIDGE_CHAIN_ID: string = "10";
const SRC_BRIDGE_TOKEN_ADDR: string =
  "0x0b2c639c533813f4aa9d7837caf62653d097ff85";
const DST_BRIDGE_CHAIN_ID: string = "137";
const DST_BRIDGE_TOKEN_ADDR: string =
  "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359";

// const INVALID_SRC_BRIDGE_CHAIN_ID: string = "592";
const INVALID_SRC_BRIDGE_TOKEN_ADDR: string =
  "0x0000000000000000000000000000000000000000";
const INVALID_DST_BRIDGE_CHAIN_ID: string = "1088";
const INVALID_DST_BRIDGE_TOKEN_ADDR: string =
  "0x0000000000000000000000000000000000000000";

const wagmiConfig = getWagmiConfig(supportedChains);

describe("SwapProvider", () => {
  // mock the API
  beforeEach(() => {
    fetch.resetMocks();
    fetchMock.mockIf(/^https?:\/\/xswap.link\/api.*$/, async (req) => {
      if (req.url.endsWith("/bridgeTokens")) {
        return Promise.resolve(JSON.stringify(bridgeTokens));
      } else {
        return Promise.resolve(JSON.stringify({}));
      }
    });
  });

  // create a wrapper for the SwapProvider
  const getWrapper = (integrationConfig, supportedChains, wagmiConfig) => {
    return ({ children }: { children: React.ReactNode }) => (
      <AllWalletsConfig wagmiConfig={wagmiConfig}>
        <WalletProvider>
          <SwapProvider
            integrationConfig={integrationConfig}
            supportedChains={supportedChains}
            overlay={false}
          >
            {children}
          </SwapProvider>
        </WalletProvider>
      </AllWalletsConfig>
    );
  };

  it("The default values should not be used for tests", async () => {
    if (SRC_SWAP_CHAIN_ID === DEFAULT_SOURCE_CHAIN_ID) {
      throw new Error("SRC_SWAP_CHAIN_ID can't be the default chain id");
    }
    if (SRC_SWAP_TOKEN_ADDR === DEFAULT_SOURCE_TOKEN_ADDR) {
      throw new Error("SRC_SWAP_TOKEN_ADDR can't be the default token address");
    }
    if (DST_SWAP_CHAIN_ID === DEFAULT_DESTINATION_CHAIN_ID) {
      throw new Error("DST_SWAP_CHAIN_ID can't be the default chain id");
    }
    if (DST_SWAP_TOKEN_ADDR === DEFAULT_DESTINATION_TOKEN_ADDR) {
      throw new Error("DST_SWAP_TOKEN_ADDR can't be the default token address");
    }
  });

  describe("Initial state setup", () => {
    it("should initialize for bridge with no values", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: true },
          supportedChains,
          wagmiConfig,
        ),
      });

      // Wait for async state updates to complete
      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });
      expect(result.current.srcChain?.chainId).toBe(undefined);
      expect(result.current.srcToken?.address.toLowerCase()).toBe(undefined);
      expect(result.current.dstChain?.chainId).toBe(undefined);
      expect(result.current.dstToken?.address.toLowerCase()).toBe(undefined);
    });

    it("should initialize for swap with default values", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      // Wait for async state updates to complete
      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcChain?.chainId).toBe(DEFAULT_SOURCE_CHAIN_ID);
      expect(result.current.srcToken?.address.toLowerCase()).toBe(
        DEFAULT_SOURCE_TOKEN_ADDR.toLowerCase(),
      );
      expect(result.current.dstChain?.chainId).toBe(
        DEFAULT_DESTINATION_CHAIN_ID,
      );
      expect(result.current.dstToken?.address.toLowerCase()).toBe(
        DEFAULT_DESTINATION_TOKEN_ADDR.toLowerCase(),
      );
    });

    it("should initialize for swap with custom valid values single chain", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            srcChainId: SRC_SWAP_CHAIN_ID,
            srcTokenAddr: SRC_SWAP_TOKEN_ADDR,
            dstChainId: SRC_SWAP_CHAIN_ID,
            dstTokenAddr: NATIVE_TOKEN_ADDR,
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      // Wait for async state updates to complete
      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcChain?.chainId).toBe(SRC_SWAP_CHAIN_ID);
      expect(result.current.srcToken?.address.toLowerCase()).toBe(
        SRC_SWAP_TOKEN_ADDR?.toLowerCase(),
      );
      expect(result.current.dstChain?.chainId).toBe(SRC_SWAP_CHAIN_ID);
      expect(result.current.dstToken?.address.toLowerCase()).toBe(
        NATIVE_TOKEN_ADDR?.toLowerCase(),
      );
    });

    it("should initialize for swap with custom valid values cross chain", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            srcChainId: SRC_SWAP_CHAIN_ID,
            srcTokenAddr: SRC_SWAP_TOKEN_ADDR,
            dstChainId: DST_SWAP_CHAIN_ID,
            dstTokenAddr: DST_SWAP_TOKEN_ADDR,
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcChain?.chainId).toBe(SRC_SWAP_CHAIN_ID);
      expect(result.current.srcToken?.address.toLowerCase()).toBe(
        SRC_SWAP_TOKEN_ADDR?.toLowerCase(),
      );
      expect(result.current.dstChain?.chainId).toBe(DST_SWAP_CHAIN_ID);
      expect(result.current.dstToken?.address.toLowerCase()).toBe(
        DST_SWAP_TOKEN_ADDR?.toLowerCase(),
      );
    });

    it("should handle invalid chain IDs gracefully", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            srcChainId: "99999999999",
            dstChainId: "99999999999",
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Should fall back to defaults
      expect(result.current.srcChain?.chainId).toBe(DEFAULT_SOURCE_CHAIN_ID);
      expect(result.current.dstChain?.chainId).toBe(
        DEFAULT_DESTINATION_CHAIN_ID,
      );
    });
  });

  describe("State changes", () => {
    it("should handle source chain change", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      const newChain = supportedChains.find(
        (chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
      );
      if (!newChain) throw new Error("Chain not found");

      await act(async () => {
        result.current.setSrcChain(newChain);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcChain?.chainId).toBe(SRC_SWAP_CHAIN_ID);
      // Should set a valid token for the new chain
      expect(result.current.srcToken?.address).toBeDefined();
    });

    it("should preserve token when changing to chain with same token", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // First set initial chain and token
      const initialChain = supportedChains.find(
        (chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
      );
      if (!initialChain) throw new Error("Initial chain not found");

      const initialToken = initialChain.tokens.find(
        (token) =>
          token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
      );
      if (!initialToken) throw new Error("Initial token not found");

      await act(async () => {
        result.current.setSrcChain(initialChain);
        result.current.setSrcToken(initialToken);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Now change to a different chain that has the same token (by tokenId)
      const newChain = supportedChains.find(
        (chain) =>
          chain.chainId !== initialChain.chainId &&
          chain.tokens.some(
            (token) =>
              token.tokenId === initialToken.tokenId && token.supported,
          ),
      );
      if (!newChain) throw new Error("New chain with same token not found");

      await act(async () => {
        result.current.setSrcChain(newChain);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Should preserve the same tokenId but with new chain's token address
      expect(result.current.srcToken?.tokenId).toBe(initialToken.tokenId);
      expect(result.current.srcToken?.address).not.toBe(initialToken.address);
    });

    it("should reset token when changing to chain without same token", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // First set initial chain and token
      const initialChain = supportedChains.find(
        (chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
      );
      if (!initialChain) throw new Error("Initial chain not found");

      const initialToken = initialChain.tokens.find(
        (token) =>
          token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
      );
      if (!initialToken) throw new Error("Initial token not found");

      await act(async () => {
        result.current.setSrcChain(initialChain);
        result.current.setSrcToken(initialToken);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Now change to a chain that doesn't have the same token
      const newChain = supportedChains.find(
        (chain) =>
          chain.chainId !== initialChain.chainId &&
          !chain.tokens.some((token) => token.tokenId === initialToken.tokenId),
      );
      if (!newChain) throw new Error("New chain without same token not found");

      await act(async () => {
        result.current.setSrcChain(newChain);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Should have a different token now
      expect(result.current.srcToken?.tokenId).not.toBe(initialToken.tokenId);
      expect(result.current.srcToken?.address).not.toBe(initialToken.address);
      // But should still have a valid token
      expect(result.current.srcToken).toBeDefined();
    });

    it("should handle destination chain change", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      const newChain = supportedChains.find(
        (chain) => chain.chainId === DST_SWAP_CHAIN_ID,
      );
      if (!newChain) throw new Error("Chain not found");

      await act(async () => {
        result.current.setDstChain(newChain);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.dstChain?.chainId).toBe(DST_SWAP_CHAIN_ID);
      // Should set a valid token for the new chain
      expect(result.current.dstToken?.address).toBeDefined();
    });

    it("should preserve token when changing destination chain with same token", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // First set initial chain and token
      const initialChain = supportedChains.find(
        (chain) => chain.chainId === DST_SWAP_CHAIN_ID,
      );
      if (!initialChain) throw new Error("Initial chain not found");

      const initialToken = initialChain.tokens.find(
        (token) =>
          token.address.toLowerCase() === DST_SWAP_TOKEN_ADDR.toLowerCase(),
      );
      if (!initialToken) throw new Error("Initial token not found");

      await act(async () => {
        result.current.setDstChain(initialChain);
        result.current.setDstToken(initialToken);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Now change to a different chain that has the same token (by tokenId)
      const newChain = supportedChains.find(
        (chain) =>
          chain.chainId !== initialChain.chainId &&
          chain.tokens.some(
            (token) =>
              token.tokenId === initialToken.tokenId && token.supported,
          ),
      );
      if (!newChain) throw new Error("New chain with same token not found");

      await act(async () => {
        result.current.setDstChain(newChain);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Should preserve the same tokenId but with new chain's token address
      expect(result.current.dstToken?.tokenId).toBe(initialToken.tokenId);
      expect(result.current.dstToken?.address).not.toBe(initialToken.address);
    });

    it("should reset token when changing destination chain without same token", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // First set initial chain and token
      const initialChain = supportedChains.find(
        (chain) => chain.chainId === DST_SWAP_CHAIN_ID,
      );
      if (!initialChain) throw new Error("Initial chain not found");

      const initialToken = initialChain.tokens.find(
        (token) =>
          token.address.toLowerCase() === DST_SWAP_TOKEN_ADDR.toLowerCase(),
      );
      if (!initialToken) throw new Error("Initial token not found");

      await act(async () => {
        result.current.setDstChain(initialChain);
        result.current.setDstToken(initialToken);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Now change to a chain that doesn't have the same token
      const newChain = supportedChains.find(
        (chain) =>
          chain.chainId !== initialChain.chainId &&
          !chain.tokens.some((token) => token.tokenId === initialToken.tokenId),
      );
      if (!newChain) throw new Error("New chain without same token not found");

      await act(async () => {
        result.current.setDstChain(newChain);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Should have a different token now
      expect(result.current.dstToken?.tokenId).not.toBe(initialToken.tokenId);
      expect(result.current.dstToken?.address).not.toBe(initialToken.address);
      // But should still have a valid token
      expect(result.current.dstToken).toBeDefined();
    });

    it("should handle source token change", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            srcChainId: SRC_SWAP_CHAIN_ID,
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      const activeSrcChain = supportedChains.find(
        (chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
      );
      if (!activeSrcChain) throw new Error("Source chain not set");

      const newToken = activeSrcChain.tokens.find(
        (token) =>
          token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
      );
      if (!newToken) throw new Error("Token not found");

      await act(async () => {
        result.current.setSrcToken(newToken);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcToken?.address).toBe(SRC_SWAP_TOKEN_ADDR);
    });
    it("should handle destination token change", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            dstChainId: DST_SWAP_CHAIN_ID,
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      const newChain = supportedChains.find(
        (chain) => chain.chainId === DST_SWAP_CHAIN_ID,
      );
      if (!newChain) throw new Error("Source chain not set");

      const newToken = newChain.tokens.find(
        (token) =>
          token.address.toLowerCase() === DST_SWAP_TOKEN_ADDR.toLowerCase(),
      );
      if (!newToken) throw new Error("Token not found");

      await act(async () => {
        result.current.setDstToken(newToken);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.dstToken?.address).toBe(DST_SWAP_TOKEN_ADDR);
    });

    it("should prevent same token selection on same chain", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            srcChainId: SRC_SWAP_CHAIN_ID,
            dstChainId: SRC_SWAP_CHAIN_ID,
            srcTokenAddr: SRC_SWAP_TOKEN_ADDR,
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      const chain = supportedChains.find(
        (chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
      );
      if (!chain) throw new Error("Chain not found");

      const token = chain.tokens.find(
        (token) =>
          token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
      );
      if (!token) throw new Error("Token not found");

      await act(async () => {
        result.current.setDstToken(token);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcToken?.address.toLowerCase()).not.toBe(
        result.current.dstToken?.address.toLowerCase(),
      );
    });

    it("should handle token support status", async () => {
      // Create a modified chain with an unsupported token
      const modifiedChain = {
        ...supportedChains[0],
        tokens: [
          ...(supportedChains[0]?.tokens || []),
          {
            address: INVALID_SRC_TOKEN_ADDR,
            supported: false,
            decimals: 18,
            symbol: "INVALID",
            name: "Invalid Token",
            tokenId: "INVALID",
            priority: 0,
            quickPick: false,
          },
        ],
      };

      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          [modifiedChain],
          wagmiConfig,
        ),
      });

      await act(async () => {
        const unsupportedToken = modifiedChain.tokens.find((t) => !t.supported);
        if (!unsupportedToken) throw new Error("Test setup failed");
        result.current.setSrcToken(unsupportedToken);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcToken?.address.toLowerCase()).not.toBe(
        INVALID_SRC_TOKEN_ADDR.toLowerCase(),
      );
    });
  });

  describe("Bridge mode validation", () => {
    it("should enforce bridgeable tokens between chains", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: true },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      const srcChain = supportedChains.find(
        (chain) => chain.chainId === SRC_BRIDGE_CHAIN_ID,
      );
      const dstChain = supportedChains.find(
        (chain) => chain.chainId === DST_BRIDGE_CHAIN_ID,
      );

      if (!srcChain || !dstChain) throw new Error("Bridge chains not found");

      const srcToken = srcChain.tokens.find(
        (token) =>
          token.address.toLowerCase() === SRC_BRIDGE_TOKEN_ADDR.toLowerCase(),
      );

      if (!srcToken) throw new Error("Bridge token not found");

      await act(async () => {
        result.current.setSrcChain(srcChain);
        result.current.setDstChain(dstChain);
        result.current.setSrcToken(srcToken);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Verify destination token is the corresponding bridged token
      expect(result.current.dstToken?.address.toLowerCase()).toBe(
        DST_BRIDGE_TOKEN_ADDR.toLowerCase(),
      );
    });

    it("should prevent selecting non-bridgeable tokens", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: true },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      const srcChain = supportedChains.find(
        (chain) => chain.chainId === SRC_BRIDGE_CHAIN_ID,
      );
      const dstChain = supportedChains.find(
        (chain) => chain.chainId === DST_BRIDGE_CHAIN_ID,
      );

      if (!srcChain || !dstChain) throw new Error("Bridge chains not found");

      const invalidToken = srcChain.tokens.find(
        (token) =>
          token.address.toLowerCase() ===
          INVALID_SRC_BRIDGE_TOKEN_ADDR.toLowerCase(),
      );

      if (!invalidToken) throw new Error("Invalid token not found");

      await act(async () => {
        result.current.setSrcChain(srcChain);
        result.current.setDstChain(dstChain);
        result.current.setSrcToken(invalidToken);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Should not allow setting a non-bridgeable token
      expect(result.current.srcToken?.address.toLowerCase()).not.toBe(
        invalidToken.address.toLowerCase(),
      );
    });

    it("should prevent same chain selection in bridge mode", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: true },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      const chain = supportedChains.find((chain) => chain.bridgeSupported);
      if (!chain) throw new Error("Bridge chain not found");

      await act(async () => {
        result.current.setSrcChain(chain);
        result.current.setDstChain(chain);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Should not allow same chain selection in bridge mode
      expect(result.current.srcChain?.chainId).not.toBe(
        result.current.dstChain?.chainId,
      );
    });

    it("should preserve token when changing destination chain if token exists on both chains", async () => {
      // Set up initial state with Arbitrum as source chain
      const arbitrumChain = supportedChains.find(
        (chain) => chain.chainId === "42161", // Arbitrum One
      );
      if (!arbitrumChain) throw new Error("Arbitrum chain not found");

      // Find WETH token on Base
      const arbitrumWethToken = arbitrumChain.tokens.find(
        (token) => token.tokenId === "WETH",
      );
      if (!arbitrumWethToken)
        throw new Error("WETH token not found on arbitrum");

      // Set Base as initial destination chain
      const baseChain = supportedChains.find(
        (chain) => chain.chainId === "8453", // Base
      );
      if (!baseChain) throw new Error("Base chain not found");

      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: true,
            srcChainId: arbitrumChain.chainId,
            dstChainId: baseChain.chainId,
            srcTokenAddr: arbitrumWethToken.address,
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Find Ethereum chain
      const ethereumChain = supportedChains.find(
        (chain) => chain.chainId === "1", // Ethereum
      );
      if (!ethereumChain) throw new Error("Ethereum chain not found");

      // Change destination chain to Ethereum
      await act(async () => {
        result.current.setDstChain(ethereumChain);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Verify that WETH is still selected but with Ethereum's address
      expect(result.current.dstToken?.tokenId).toBe("WETH");
      expect(result.current.dstChain?.chainId).toBe("1");
    });
  });

  describe("Swap mode validation", () => {
    it("should enforce swap-supported chains", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Find a chain without swap support
      const nonSwapChain = supportedChains.find(
        (chain) => !chain.swapSupported,
      );
      if (!nonSwapChain) throw new Error("Non-swap chain not found");

      await act(async () => {
        result.current.setSrcChain(nonSwapChain);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Should not allow setting a non-swap chain
      expect(result.current.srcChain?.chainId).not.toBe(nonSwapChain.chainId);
    });

    it("should handle disabled destination chains", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Find a chain with disabled destinations
      const srcChain = supportedChains.find(
        (chain) => chain.disabledForDestination?.length || 0 > 0,
      );
      if (!srcChain)
        throw new Error("Chain with disabled destinations not found");

      const disabledChain = supportedChains.find((chain) =>
        srcChain.disabledForDestination?.includes(chain.chainId),
      );
      if (!disabledChain) throw new Error("Disabled chain not found");

      await act(async () => {
        result.current.setSrcChain(srcChain);
        result.current.setDstChain(disabledChain);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Should not allow setting a disabled destination chain
      expect(result.current.dstChain?.chainId).not.toBe(disabledChain.chainId);
    });
  });

  describe("Edge cases", () => {
    it("should handle chain with no valid tokens", async () => {
      const modifiedChain = {
        ...supportedChains[0],
        tokens:
          supportedChains[0]?.tokens?.map((token) => ({
            ...token,
            supported: false,
          })) || [],
      };

      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            srcChainId: modifiedChain.chainId,
          },
          [modifiedChain],
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcToken).toBeUndefined();
    });

    it("should handle empty supported chains list", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          [],
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      // Should handle the case gracefully
      expect(result.current.srcChain).toBe(undefined);
      expect(result.current.dstChain).toBe(undefined);
      expect(result.current.srcToken).toBe(undefined);
      expect(result.current.dstToken).toBe(undefined);
    });
  });

  describe("Token validation", () => {
    it("should handle undefined token addresses", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        result.current.setSrcToken(undefined);
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcToken).toBeDefined();
      expect(result.current.srcToken?.address).toBe(DEFAULT_SOURCE_TOKEN_ADDR);
    });

    it("should handle token decimals correctly", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          { ...integrationConfig, bridge: false },
          supportedChains,
          wagmiConfig,
        ),
      });

      const chain = supportedChains.find(
        (chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
      );
      if (!chain) throw new Error("Chain not found");

      const token = chain.tokens.find(
        (token) =>
          token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
      );
      if (!token) throw new Error("Token not found");

      await act(async () => {
        result.current.setSrcChain(chain);
        result.current.setSrcToken(token);
        result.current.setSrcValue("1.0");
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcValueWei).toBe(
        ethers.utils.parseUnits("1.0", token.decimals).toString(),
      );
    });
  });

  describe("Invalid chain and token handling", () => {
    it("should handle invalid source chain selection", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            srcChainId: INVALID_SRC_CHAIN_ID,
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcChain?.chainId).not.toBe(INVALID_SRC_CHAIN_ID);
    });

    it("should handle invalid destination chain selection", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            dstChainId: INVALID_DST_CHAIN_ID,
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.dstChain?.chainId).not.toBe(INVALID_DST_CHAIN_ID);
    });

    it("should handle invalid source token selection", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            srcChainId: SRC_SWAP_CHAIN_ID,
            srcTokenAddr: INVALID_SRC_TOKEN_ADDR,
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.srcToken?.address.toLowerCase()).not.toBe(
        INVALID_SRC_TOKEN_ADDR.toLowerCase(),
      );
    });

    it("should handle invalid destination token selection", async () => {
      const { result } = renderHook(() => useSwapContext(), {
        wrapper: getWrapper(
          {
            ...integrationConfig,
            bridge: false,
            dstChainId: DST_SWAP_CHAIN_ID,
            dstTokenAddr: INVALID_DST_TOKEN_ADDR,
          },
          supportedChains,
          wagmiConfig,
        ),
      });

      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve, 0));
      });

      expect(result.current.dstToken?.address.toLowerCase()).not.toBe(
        INVALID_DST_TOKEN_ADDR.toLowerCase(),
      );
    });

    describe("Bridge mode invalid cases", () => {
      it("should handle invalid bridge destination chain", async () => {
        const { result } = renderHook(() => useSwapContext(), {
          wrapper: getWrapper(
            {
              ...integrationConfig,
              bridge: true,
              srcChainId: SRC_BRIDGE_CHAIN_ID,
              dstChainId: INVALID_DST_BRIDGE_CHAIN_ID,
            },
            supportedChains,
            wagmiConfig,
          ),
        });

        await act(async () => {
          await new Promise((resolve) => setTimeout(resolve, 0));
        });

        expect(result.current.dstChain?.chainId).not.toBe(
          INVALID_DST_BRIDGE_CHAIN_ID,
        );
      });

      it("should handle invalid bridge destination token", async () => {
        const { result } = renderHook(() => useSwapContext(), {
          wrapper: getWrapper(
            {
              ...integrationConfig,
              bridge: true,
              dstChainId: DST_BRIDGE_CHAIN_ID,
              dstTokenAddr: INVALID_DST_BRIDGE_TOKEN_ADDR,
            },
            supportedChains,
            wagmiConfig,
          ),
        });

        await act(async () => {
          await new Promise((resolve) => setTimeout(resolve, 0));
        });

        expect(result.current.dstToken?.address.toLowerCase()).not.toBe(
          INVALID_DST_BRIDGE_TOKEN_ADDR.toLowerCase(),
        );
      });
    });
  });
});
