import { faCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CloseIcon, CopyIcon, SearchIcon } from "@src/assets/icons";
import {
  Button,
  Spinner,
  TextInput,
  TokenLogoWithChain,
  UnknownTokenLogo,
} from "@src/components";
import { useSwapContext } from "@src/context";
import { ERC20Abi } from "@src/contracts";
import { useEvmContractApi } from "@src/hooks";
import { Chain, Ecosystem, ImportedTokenData, TokenOption } from "@src/models";
import { isETHAddressValid, isServer, shortAddress } from "@src/utils";
import { BigNumber, ethers } from "ethers";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { GroupedVirtuoso } from "react-virtuoso";
import { QuickPickTokenItem } from "./QuickPickTokenItem";
import { TokenItem } from "./TokenItem";

type Props = {
  chain: Chain;
  tokens: TokenOption[];
  otherTokens: TokenOption[];
  selectedTokenAddress?: string;
  isOpen: boolean;
  onSelect: (token: TokenOption) => void;
  setModalOpen: (isModalOpen: boolean) => void;
};
export const TokenPicker = ({
  chain,
  tokens,
  otherTokens,
  selectedTokenAddress,
  isOpen,
  onSelect,
  setModalOpen,
}: Props) => {
  const [searchValue, setSearchValue] = useState("");
  const [acceptRisks, setAcceptRisks] = useState(false);
  const [showImportWarning, setShowImportWarning] = useState(false);
  const [loadingCustomTokenData, setLoadingCustomTokenData] = useState(false);
  const [customTokenData, setCustomTokenData] =
    useState<ImportedTokenData | null>(null);
  const currentRouteRequestNonce = useRef<number>(0);
  const tokenSearchRef = useRef<HTMLInputElement | null>(null);

  const { supportedChains, findTokenDataByAddressAndChain } = useSwapContext();
  const evmContractApi = useEvmContractApi();

  useEffect(() => {
    setShowImportWarning(false);
    setAcceptRisks(false);
  }, [isOpen]);

  const importCustomToken = useCallback(() => {
    if (customTokenData && isETHAddressValid(searchValue)) {
      let customTokens = {};
      if (!isServer) {
        customTokens = JSON.parse(
          localStorage.getItem("custom-tokens") || "{}",
        );
      }

      const newCustomToken = {
        address: customTokenData.address,
        symbol: customTokenData.symbol,
        name: customTokenData.name,
        tokenId: customTokenData.address,
        decimals: customTokenData.decimals,
        quickPick: false,
        supported: true,
        priority: 0,
        chainId: chain.chainId,
      };

      if (!isServer) {
        customTokens[chain.chainId] = {
          ...(customTokens[chain.chainId] || {}),
          [searchValue.toLowerCase()]: newCustomToken,
        };
        localStorage.setItem("custom-tokens", JSON.stringify(customTokens));
      }
      // recalculateSupportedTokens(); TODO?
      onSelect(newCustomToken);
      setModalOpen(false);
    }
  }, [chain, searchValue, customTokenData, onSelect, setModalOpen]);

  const getCustomTokenData = useCallback(async () => {
    const nonce = Date.now();
    currentRouteRequestNonce.current = nonce;
    const rpcUrl = supportedChains.find(
      ({ ecosystem, chainId }) =>
        ecosystem === Ecosystem.EVM && chainId === chain.chainId,
    )?.publicRpcUrls[0];
    const customTokenContract = new ethers.Contract(
      searchValue.toLowerCase(),
      ERC20Abi,
      ethers.getDefaultProvider(rpcUrl),
    );
    setLoadingCustomTokenData(true);
    try {
      const responses = await Promise.all([
        evmContractApi.query(customTokenContract, "decimals"),
        evmContractApi.query(customTokenContract, "name"),
        evmContractApi.query(customTokenContract, "symbol"),
      ]);
      if (nonce !== currentRouteRequestNonce.current) {
        return;
      }
      const [decimals, name, symbol] = responses;
      setCustomTokenData({
        address: searchValue.toLowerCase(),
        decimals,
        name,
        symbol,
      });
    } catch (_) {
      setCustomTokenData(null);
    } finally {
      if (nonce === currentRouteRequestNonce.current) {
        setLoadingCustomTokenData(false);
      }
    }
  }, [chain, searchValue, evmContractApi, supportedChains]);

  useEffect(() => {
    if (
      isETHAddressValid(searchValue) &&
      !findTokenDataByAddressAndChain(searchValue.toLowerCase(), chain.chainId)
    ) {
      getCustomTokenData();
    } else {
      setLoadingCustomTokenData(false);
      setCustomTokenData(null);
    }
  }, [searchValue, getCustomTokenData, findTokenDataByAddressAndChain, chain]);

  useEffect(() => {
    setSearchValue("");
  }, [isOpen]);

  const isMatch = (value, searchValue) =>
    value.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;

  const filteredTokens = useMemo(
    () =>
      tokens?.filter(
        (token) =>
          isMatch(token.symbol, searchValue) ||
          isMatch(token.name, searchValue) ||
          token.address.toLowerCase() === searchValue.toLowerCase(),
      ),
    [searchValue, tokens],
  );

  const otherFilteredTokens = useMemo(
    () =>
      otherTokens?.filter(
        (token) =>
          isMatch(token.symbol, searchValue) ||
          isMatch(token.name, searchValue) ||
          token.address.toLowerCase() === searchValue.toLowerCase(),
      ),
    [searchValue, otherTokens],
  );

  useEffect(() => {
    if (isOpen && tokenSearchRef.current) {
      setTimeout(() => {
        tokenSearchRef.current?.focus();
      }, 10); // delay focus a tiny bit to ensure it works
    }
  }, [isOpen]);

  const allTokens = useMemo(() => {
    const filteredTokensSorted = [
      ...filteredTokens
        .filter((token) => BigNumber.from(token?.balance || "0").gt(0))
        .sort((tokenDataA, tokenDataB) => {
          return tokenDataB.priority - tokenDataA.priority;
        }),
      ...filteredTokens
        .filter((token) => BigNumber.from(token?.balance || "0").eq(0))
        .sort((tokenDataA, tokenDataB) => {
          return tokenDataB.priority - tokenDataA.priority;
        }),
    ];
    const otherFilteredTokensSorted = [
      ...otherFilteredTokens
        .filter((token) => BigNumber.from(token?.balance || "0").gt(0))
        .sort((tokenDataA, tokenDataB) => {
          return tokenDataB.priority - tokenDataA.priority;
        }),
      ...otherFilteredTokens
        .filter((token) => BigNumber.from(token?.balance || "0").eq(0))
        .sort((tokenDataA, tokenDataB) => {
          return tokenDataB.priority - tokenDataA.priority;
        }),
    ];

    return [...filteredTokensSorted, ...otherFilteredTokensSorted];
  }, [filteredTokens, otherFilteredTokens]);

  const tokenGroups = [filteredTokens, otherFilteredTokens];

  return showImportWarning ? (
    <>
      <div className="flex justify-between">
        <div className="text-base/4 mb-4">Import token</div>
        <div
          className="w-4 h-4 fill-t_text_primary cursor-pointer"
          onClick={() => setModalOpen(false)}
        >
          <CloseIcon />
        </div>
      </div>
      <div className="flex flex-col h-full gap-2">
        {customTokenData && (
          <div
            className={`token-picker-container grow-0 bg-t_bg_tertiary bg-opacity-10 border border-solid border-t_text_primary border-opacity-10 rounded-xl py-2 pr-2 pl-4 hover:cursor-pointer`}
          >
            <UnknownTokenLogo
              tokenName={customTokenData.name}
              className="w-9 h-9 text-[15px]"
            />
            <div className="flex justify-between items-center gap-1 overflow-hidden grow">
              <div className="overflow-hidden whitespace-nowrap text-ellipsis text-sm font-medium">
                {customTokenData.name}
              </div>
              <div className="token-picker__all__symbol">
                {customTokenData.symbol}
              </div>
            </div>
            <div className="text-sm flex items-center">
              {shortAddress(searchValue.toLowerCase())}
              <button
                type="button"
                onClick={() =>
                  navigator.clipboard.writeText(
                    customTokenData.address.toLowerCase(),
                  )
                }
                aria-label={`Copy ${customTokenData.symbol} token address`}
                className="flex items-center p-2 fill-white cursor-pointer hover:fill-t_theme_color"
              >
                <CopyIcon className="w-5 h-5" />
              </button>
            </div>
          </div>
        )}
        <div className="px-2 py-4 rounded-xl border border-t_warning_light text-sm">
          Ensure that you have verified the token address and confirmed its
          correctness
        </div>
        <div className="bg-t_bg_secondary border border-solid border-t_text_primary border-opacity-10 rounded-xl p-2">
          <div className="text-sm mb-2">
            Be mindful that anyone can create tokens, even counterfeit versions
            of existing ones. Certain tokens and their technical specifications
            might not be compatible with Swapper services. By importing such
            custom tokens, you acknowledge and consent to the associated risks.
          </div>
          <button
            type="button"
            onClick={() => setAcceptRisks((state) => !state)}
            className="flex items-center"
          >
            <div className="flex justify-center items-center p-1 w-6 h-6 border-2 border-white rounded-md mr-2">
              {acceptRisks && (
                <FontAwesomeIcon icon={faCheck} className="w-4 h-4" />
              )}
            </div>

            <div className="font-bold">Accept</div>
          </button>
        </div>
        <div className="flex grow items-end justify-center">
          <Button
            type="button"
            disabled={!acceptRisks}
            className={`token-picker__import-button w-full ${
              !acceptRisks ? "!cursor-not-allowed" : ""
            }`}
            onClick={importCustomToken}
          >
            Import
          </Button>
        </div>
      </div>
    </>
  ) : (
    <>
      <div className="flex justify-between">
        <div className="text-base/4 text-t_text_primary mb-4">Pick token</div>
        <div
          className="w-4 h-4 fill-t_text_primary cursor-pointer"
          onClick={() => setModalOpen(false)}
        >
          <CloseIcon />
        </div>
      </div>
      <TextInput
        ref={tokenSearchRef}
        inputIcon={
          <div className="fill-t_text_primary">
            <SearchIcon />
          </div>
        }
        placeholder="Search name or paste address"
        value={searchValue}
        handleChange={setSearchValue}
      />
      <div className="flex flex-wrap gap-2 my-4 mx-0">
        {tokens
          .filter((token) => token?.quickPick)
          .map((token) => (
            <QuickPickTokenItem
              key={token.address}
              token={token}
              selectedTokenAddress={selectedTokenAddress}
              onClick={() => {
                onSelect(token);
                setModalOpen(false);
              }}
            />
          ))}
      </div>
      <div className="flex flex-col mx-0 h-full">
        {loadingCustomTokenData ? (
          <div className="flex justify-center w-full py-4 px-0">
            <Spinner className="w-9 h-9" />
          </div>
        ) : (
          <>
            {customTokenData ? (
              <div className={`token-picker-container hover:cursor-default`}>
                <TokenLogoWithChain
                  token={customTokenData}
                  chain={chain}
                  tokenLogoClassName="w-9"
                  chainLogoClassName="w-5"
                />
                <div className="flex justify-between items-center gap-1 overflow-hidden grow">
                  <div>
                    <div className="text-sm font-medium text-ellipsis">
                      {customTokenData.name}
                    </div>
                    <div className="text-xs text-t_text_primary !text-opacity-50 font-medium">
                      {chain?.displayName}
                    </div>
                  </div>
                  <div className="token-picker__all__symbol">
                    {customTokenData.symbol}
                  </div>
                </div>
                <div>
                  <Button
                    type="button"
                    className="h-8 py-0 px-4 text-t_text_primary leading-[14px] rounded-[32px] bg-t_theme_color"
                    onClick={() => setShowImportWarning(true)}
                  >
                    Import
                  </Button>
                </div>
              </div>
            ) : (
              <div className="flex flex-col h-full gap-8 w-[calc(100%+32px)] mx-[-16px]">
                <GroupedVirtuoso
                  style={{ height: "100%" }}
                  groupCounts={tokenGroups.map((group) => group?.length)}
                  groupContent={(index) => (
                    <div className="py-1 bg-t_bg_primary text-t_text_primary">
                      <div className="px-4 text-left">
                        {index === 0
                          ? `${chain.displayName} network`
                          : "Other networks"}
                      </div>
                      <div className="horizontal-separator" />
                      {!tokenGroups[index]?.length && (
                        <div className="text-center py-1">No token found</div>
                      )}
                    </div>
                  )}
                  itemContent={(index) => {
                    return (
                      allTokens[index] && (
                        <TokenItem
                          token={allTokens[index]}
                          selectedTokenAddress={selectedTokenAddress}
                          onClick={() => {
                            onSelect(allTokens[index]!);
                            setModalOpen(false);
                          }}
                        />
                      )
                    );
                  }}
                />
              </div>
            )}
          </>
        )}
      </div>
    </>
  );
};
