import {
  ReactNode,
  createContext,
  useContext,
  useState,
  useEffect,
  useMemo,
  useRef,
  useCallback,
} from "react";
import { Chain, SwapperAppPayload, SwapperCTAs } from "@src/models";
import { useLocation } from "react-router-dom";
import {
  SwapMode,
  RoutePath,
  ProcessingState,
  BalancesSnapshotMap,
} from "@src/interfaces";
import { usePrivySmartWallet } from "../PrivySmartWalletProvider";
import { useHistory } from "../HistoryProvider";
import { useShift4Api } from "./useShift4Api";
import { SwapContext } from "./types";
import { defaultSwapState } from "./defaultState";
import { useTokensState } from "./useTokensState";
import { useRouteState } from "./useRouteState";
import { useTransactionState } from "./useTransactionState";
import { useDebounce } from "@src/hooks";
import { BigNumber } from "ethers";
import { AUTO_REFRESH_INTERVAL } from "@src/config";
import { isETHAddressValid } from "@src/utils";
import { applyReferralCode } from "@src/services";

type SwapProviderProps = {
  children: ReactNode;
  integrationConfig: Partial<SwapperAppPayload>;
  supportedChains: Chain[];
  isFetchingChains: boolean;
  isDirectOnRampSupported: boolean;
  isCheckingDirectOnRamp: boolean;
};

const swapContext = createContext<SwapContext>(defaultSwapState);

export const useSwapContext = () => {
  const context = useContext(swapContext);
  if (context === undefined) {
    throw new Error("useSwapContext must be used within a SwapProvider");
  }
  return context;
};

export const SwapProvider = ({
  children,
  integrationConfig,
  supportedChains,
  isFetchingChains,
  isDirectOnRampSupported,
  isCheckingDirectOnRamp,
}: SwapProviderProps) => {
  const location = useLocation();

  // local state
  const [swapMode, setSwapMode] = useState<SwapMode>(SwapMode.Buy);
  const [integrationTypeOverride, setIntegrationTypeOverride] = useState<
    string | undefined
  >(undefined);
  const [isMultichain, setIsMultichain] = useState(true);
  const [isExternalMode, setIsExternalMode] = useState(true);
  const [ctas, setCTAs] = useState<SwapperCTAs>({});
  const [chain, setChain] = useState<Chain | undefined>(undefined);
  const [cashBridgeChain, setCashBridgeChain] = useState<Chain | undefined>(
    undefined,
  );
  const [cashAmount, setCashAmount] = useState("");
  const [cashBridgeAmount, setCashBridgeAmount] = useState("");
  const [estimatedCashOutAmount, setEstimatedCashOutAmount] = useState(""); // eslint-disable-line @typescript-eslint/no-unused-vars
  const [tokenAmount, setTokenAmount] = useState("");
  const [transferReceiverAddress, setTransferReceiverAddress] = useState("");
  const [transferTokenAmount, setTransferTokenAmount] = useState("");
  const [isUsingChainlinkFunction, setIsUsingChainlinkFunction] =
    useState(false);
  const [isDepositAndBuyFlow, setIsDepositAndBuyFlow] = useState(false);
  const [newPurchaseCounter, setNewPurchaseCounter] = useState(0);
  const [balancesSnapshots, setBalancesSnapshots] =
    useState<BalancesSnapshotMap>({});
  const [transactionState, setTransactionState] = useState<ProcessingState>(
    ProcessingState.Pending,
  );

  // hooks
  const { debounce: getRouteDebounce } = useDebounce({
    delayMs: 400,
  });

  const { smartWalletAddress, signerWalletAddress, executeTransaction } =
    usePrivySmartWallet();

  useEffect(() => {
    const ref = new URLSearchParams(window.location.search).get("ref");
    const shouldSend = ref && smartWalletAddress;

    if (!shouldSend) return;

    applyReferralCode(smartWalletAddress, ref).catch((err) =>
      console.error("Failed to apply referral code", err),
    );
  }, [window.location.search, smartWalletAddress]);

  const {
    addTransactionToLocalHistory,
    fetchHistory,
    history,
    historyLoadedOnce,
  } = useHistory();
  const { getQuote: getShift4Quote } = useShift4Api();

  // Auto refresh route timer reference
  const autoRefreshTimerRef = useRef<NodeJS.Timeout | null>(null);
  const isTabActiveRef = useRef<boolean>(true);

  // Token state
  const {
    token,
    setToken,
    displayToken,
    setDisplayToken,
    cashToken,
    allTokens,
    tokensPrices,
    transferToken,
    setTransferToken,
    tokensBalances,
    setTokensBalances,
    isFetchingBalances,
    fetchAllBalances,
    chainToTokenOptionsMap,
  } = useTokensState(
    chain,
    setChain,
    supportedChains,
    integrationConfig,
    smartWalletAddress,
  );

  const {
    route,
    setRoute,
    isFetchingRoute,
    fetchShift4Quote,
    fetchRoute,
    shift4TokenAmount,
    setShift4TokenAmount,
    shift4Fees,
    setShift4Fees,
    error,
    setError,
    errorDetails,
    setErrorDetails,
    feesToTokenValueRatio,
    setFeesToTokenValueRatio,
    acceptedHighFees,
    setAcceptedHighFees,
    tokenAmountWei,
    estimatedTokenOutAmount,
    estimatedTokenOutAmountWei,
    slippage,
    setSlippage,
    customContractCalls,
    setCustomContractCalls,
  } = useRouteState(
    chain,
    token,
    cashToken,
    cashAmount,
    tokenAmount,
    swapMode,
    tokensBalances,
    integrationConfig,
    smartWalletAddress,
    signerWalletAddress,
    tokensPrices,
    transactionState,
    isUsingChainlinkFunction,
    getShift4Quote,
  );

  const {
    transactionHash,
    setTransactionHash,
    tokenOutAmount,
    setTokenOutAmount,
    cashbackAmount,
    setCashbackAmount,
    pointsAmount,
    setPointsAmount,
    executeSwapTransaction,
    transactionError,
  } = useTransactionState(
    transactionState,
    setTransactionState,
    smartWalletAddress,
    executeTransaction,
    addTransactionToLocalHistory,
    fetchHistory,
    fetchAllBalances,
    route,
    token,
    cashToken,
    swapMode,
    chain,
    setNewPurchaseCounter,
    setBalancesSnapshots,
    tokensBalances,
    tokensPrices,
    isExternalMode,
    integrationConfig,
  );

  useEffect(() => {
    setIsMultichain(supportedChains.length > 1);
  }, [supportedChains]);

  useEffect(() => {
    const rawAddr = integrationConfig?.externalWalletAddress;
    if (rawAddr && isETHAddressValid(rawAddr)) {
      setIsExternalMode(true);
    } else {
      setIsExternalMode(false);
    }
  }, [integrationConfig?.externalWalletAddress]);

  useEffect(() => {
    if (integrationConfig?.cta) {
      setCTAs(integrationConfig?.cta);
    }
  }, [integrationConfig?.cta]);

  // Route pathname based effects
  useEffect(() => {
    if (location.pathname === RoutePath.Buy) {
      setError("");
      setErrorDetails("");
      setRoute(undefined);
      setShift4TokenAmount("");
      setShift4Fees(undefined);
      setCashbackAmount(undefined);
      setPointsAmount(undefined);
      setShift4Fees(undefined);
      setSwapMode(SwapMode.Buy);
      setIsUsingChainlinkFunction(false);
      setFeesToTokenValueRatio(0);
      setAcceptedHighFees(false);
    }
    if (location.pathname === RoutePath.Sell) {
      setError("");
      setErrorDetails("");
      setRoute(undefined);
      setShift4TokenAmount("");
      setShift4Fees(undefined);
      setCashbackAmount(undefined);
      setPointsAmount(undefined);
      setSwapMode(SwapMode.Sell);
      setIsUsingChainlinkFunction(false);
      setFeesToTokenValueRatio(0);
      setAcceptedHighFees(false);
    }
    if (location.pathname === RoutePath.Deposit) {
      setShift4TokenAmount("");
      setError("");
      setErrorDetails("");
      setShift4Fees(undefined);
      setIsUsingChainlinkFunction(false);
    }
    if (location.pathname === RoutePath.Wallet) {
      setTransferReceiverAddress("");
      setTransferTokenAmount("");
      setNewPurchaseCounter(0);
    }
  }, [
    location.pathname,
    setError,
    setErrorDetails,
    setRoute,
    setShift4TokenAmount,
    setShift4Fees,
    setSwapMode,
    setFeesToTokenValueRatio,
    setAcceptedHighFees,
    setPointsAmount,
    setCashbackAmount,
  ]);

  // Chainlink function selection logic
  useEffect(() => {
    // Do not change if we are in the processing page
    if (location.pathname === RoutePath.Processing) {
      return;
    }
    // Not using Chainlink functions in Sell mode
    if (location.pathname === RoutePath.Sell) {
      setIsUsingChainlinkFunction(false);
      return;
    }

    if (!cashToken || !cashAmount) {
      setIsUsingChainlinkFunction(false);
      return;
    }

    try {
      // Get the cash balance as BigNumber
      const cashBalanceWei = BigNumber.from(
        tokensBalances[cashToken.chainId]?.[cashToken.address]?.wei || "0",
      );

      // Convert cash amount to wei for comparison
      const cashAmountWei = BigNumber.from(
        Math.floor(parseFloat(cashAmount) * 1e6),
      )
        .mul(BigNumber.from(10).pow(cashToken.decimals))
        .div(1e6);

      const useChainlink =
        cashAmountWei.gt(cashBalanceWei) &&
        Boolean(token?.chainlinkFunctionSupport);
      setIsUsingChainlinkFunction(useChainlink);
    } catch (error) {
      console.error("Error in Chainlink function selection logic:", error);
      setIsUsingChainlinkFunction(false);
    }
  }, [token, cashToken, tokensBalances, cashAmount, location.pathname]);

  // Derived state
  const supportedChainsMap = useMemo(() => {
    return supportedChains.reduce((prev, curr) => {
      prev[curr.chainId] = curr;
      return prev;
    }, {});
  }, [supportedChains]);

  useEffect(() => {
    getRouteDebounce(fetchRoute);
  }, [fetchRoute, getRouteDebounce]);

  // Setup visibility change listener for auto refresh
  useEffect(() => {
    const handleVisibilityChange = () => {
      isTabActiveRef.current = document.visibilityState === "visible";
    };

    // Initial state
    isTabActiveRef.current = document.visibilityState === "visible";

    // Add event listener
    document.addEventListener("visibilitychange", handleVisibilityChange);

    return () => {
      document.removeEventListener("visibilitychange", handleVisibilityChange);
    };
  }, []);

  // Auto refresh route every 30 seconds when tab is active
  useEffect(() => {
    // Clear any existing timer
    if (autoRefreshTimerRef.current) {
      clearInterval(autoRefreshTimerRef.current);
    }

    // Set up new timer to refresh route
    autoRefreshTimerRef.current = setInterval(() => {
      if (isTabActiveRef.current) {
        fetchRoute(true);
      }
    }, AUTO_REFRESH_INTERVAL);

    // Clean up on unmount
    return () => {
      if (autoRefreshTimerRef.current) {
        clearInterval(autoRefreshTimerRef.current);
        autoRefreshTimerRef.current = null;
      }
    };
  }, [route, fetchRoute]);

  // Reset swap form state
  const resetSwapFormData = useCallback(() => {
    setIsDepositAndBuyFlow(false);
    setTokenAmount("");
    setCashAmount("");
    setTransactionHash("");
    setFeesToTokenValueRatio(0);
  }, [
    setIsDepositAndBuyFlow,
    setTokenAmount,
    setCashAmount,
    setTransactionHash,
    setFeesToTokenValueRatio,
  ]);

  const clearSwapStateOnLogout = useCallback(() => {
    setTokensBalances({});
    resetSwapFormData();
  }, [setTokensBalances, resetSwapFormData]);

  const contextValue = useMemo(
    (): SwapContext => ({
      // Configuration
      slippage,
      setSlippage,
      integrationConfig,
      supportedChains,
      isFetchingChains,
      supportedChainsMap,
      swapMode,
      isMultichain,
      isExternalMode,
      ctas,
      integrationTypeOverride,
      setIntegrationTypeOverride,
      isDirectOnRampSupported,
      isCheckingDirectOnRamp,
      // Token state
      allTokens,
      tokensPrices,
      cashToken,
      token,
      setToken,
      displayToken,
      setDisplayToken,
      transferToken,
      setTransferToken,

      // Balance state
      tokensBalances,
      isFetchingBalances,
      fetchAllBalances,

      // Chain state
      chain,
      setChain,
      cashBridgeChain,
      setCashBridgeChain,
      chainToTokenOptionsMap,

      // Amount state
      tokenAmount,
      setTokenAmount,
      tokenAmountWei,
      cashAmount,
      setCashAmount,
      cashBridgeAmount,
      setCashBridgeAmount,
      estimatedTokenOutAmount,
      estimatedTokenOutAmountWei,
      estimatedCashOutAmount,

      // Transfer state
      transferTokenAmount,
      setTransferTokenAmount,
      transferReceiverAddress,
      setTransferReceiverAddress,

      // Route state
      route,
      isFetchingRoute,
      fetchRoute,
      fetchShift4Quote,
      feesToTokenValueRatio,
      setFeesToTokenValueRatio,
      acceptedHighFees,
      setAcceptedHighFees,
      customContractCalls,
      setCustomContractCalls,

      // Transaction state
      transactionHash,
      setTransactionHash,
      transactionState,
      setTransactionState,
      tokenOutAmount,
      setTokenOutAmount,
      cashbackAmount,
      pointsAmount,
      executeSwapTransaction,
      transactionError,
      isDepositAndBuyFlow,
      setIsDepositAndBuyFlow,
      newPurchaseCounter,
      setNewPurchaseCounter,
      balancesSnapshots,
      setBalancesSnapshots,

      // Shift4 related
      shift4TokenAmount,
      shift4Fees,
      isUsingChainlinkFunction,

      // History
      history,
      fetchHistory,
      historyLoadedOnce,

      // Reset state
      resetSwapFormData,
      clearSwapStateOnLogout,

      // Error handling
      error,
      errorDetails,
    }),
    [
      // add all dependencies here!
      slippage,
      setSlippage,
      integrationConfig,
      supportedChains,
      isFetchingChains,
      supportedChainsMap,
      swapMode,
      isMultichain,
      isExternalMode,
      ctas,
      integrationTypeOverride,
      setIntegrationTypeOverride,
      allTokens,
      tokensPrices,
      cashToken,
      token,
      setToken,
      displayToken,
      setDisplayToken,
      transferToken,
      setTransferToken,
      tokensBalances,
      isFetchingBalances,
      fetchAllBalances,
      customContractCalls,
      setCustomContractCalls,
      chain,
      setChain,
      cashBridgeChain,
      setCashBridgeChain,
      tokenAmount,
      setTokenAmount,
      tokenAmountWei,
      cashAmount,
      setCashAmount,
      cashBridgeAmount,
      setCashBridgeAmount,
      estimatedTokenOutAmount,
      estimatedTokenOutAmountWei,
      estimatedCashOutAmount,
      transferTokenAmount,
      setTransferTokenAmount,
      transferReceiverAddress,
      setTransferReceiverAddress,
      route,
      isFetchingRoute,
      fetchRoute,
      fetchShift4Quote,
      transactionHash,
      setTransactionHash,
      transactionState,
      setTransactionState,
      tokenOutAmount,
      setTokenOutAmount,
      cashbackAmount,
      pointsAmount,
      executeSwapTransaction,
      transactionError,
      isDepositAndBuyFlow,
      setIsDepositAndBuyFlow,
      shift4TokenAmount,
      shift4Fees,
      isUsingChainlinkFunction,
      history,
      fetchHistory,
      resetSwapFormData,
      historyLoadedOnce,
      clearSwapStateOnLogout,
      feesToTokenValueRatio,
      setFeesToTokenValueRatio,
      error,
      errorDetails,
      newPurchaseCounter,
      setNewPurchaseCounter,
      balancesSnapshots,
      setBalancesSnapshots,
      chainToTokenOptionsMap,
      acceptedHighFees,
      setAcceptedHighFees,
      isDirectOnRampSupported,
      isCheckingDirectOnRamp,
    ],
  );

  return (
    <swapContext.Provider value={contextValue}>{children}</swapContext.Provider>
  );
};
