import { useQuery } from "@tanstack/react-query";
import { useEffect, useMemo, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";

import type { Token } from "@/types/index";
import { canViewFeature } from "@components/FeatureFlag/config";
import { useAccounts } from "@hooks/useAccounts";
import { useCheckPermit2Approval } from "@hooks/useCheckPermit2Approval";
import { useConfig } from "@hooks/useConfig";
import { useSmartAccountType } from "@hooks/useIsSmartAccount";
import { useLogEvent } from "@hooks/useLogEvent";
import { useSupportsERC1271 } from "@hooks/useSupportsERC1271";
import { useWalletCapabilities } from "@hooks/useWalletCapabilities";
import { setIsFetchingDisabled } from "@store/quoteParamSlice";
import { setLoading, setRefetching } from "@store/routesSlice";
import type { ReduxStateType } from "@store/types";
import { bungeeApiFetcher } from "@utils/fetcher";
import { getValidAddress } from "@utils/getValidAddress";
import { isSolana } from "@utils/isSolana";
import { onQuoteResult, onRequestQuote } from "../../../../analytics/events";
import type { BungeeQuote } from "./types";
import { formatAutoRoutes, formatManualRoutes } from "./utils";

function getTokenAddresses(tokens: Token[]) {
	return tokens?.map((token) => token.address);
}

export const useFetchQuotes = () => {
	const { apiKey, baseUrl, affiliateId } = useConfig();
	const { accounts } = useAccounts();
	const dispatch = useDispatch();
	const { logEvent } = useLogEvent();
	const {
		fromChainId,
		fromTokens,
		toChainId: primitiveToChainId,
		toTokens,
		sender,
		amounts,
		receiver,
		addOnRefuel,
		isFetchingDisabled,
	} = useSelector((state: ReduxStateType) => state.quoteParams);
	const { slippage, dynamicSlippage } = useSelector(
		(state: ReduxStateType) => state.settings,
	);
	const manualModeFeatureEnabled = canViewFeature("MANUAL_MODE");

	const toChainId = primitiveToChainId ?? toTokens?.[0]?.chainId;

	const queryEnabled = Boolean(
		amounts?.length &&
			amounts?.every((amount) => Number(amount) > 0) &&
			fromChainId !== null &&
			toChainId !== null &&
			fromTokens?.length &&
			toTokens?.length,
	);

	const { supportsAtomicBatching, isLoading: isLoadingCapabilities } =
		useWalletCapabilities(fromChainId ?? undefined);
	const { smartBatching } = useSelector(
		(state: ReduxStateType) => state.settings,
	);
	const { hasPermit2Approval, isCheckingPermit2Approval, shouldCheckPermit2 } =
		useCheckPermit2Approval({
			tokenAddress: fromTokens?.[0]?.address,
			tokenChainId: fromChainId ?? undefined,
			amount: amounts?.[0]?.toString(),
			sender: sender ?? undefined,
		});

	// Get the sender address for smart account detection
	const senderAddress = useMemo(() => {
		if (sender) return sender;
		const evmAccount = accounts.find((account) => account.chainType === "EVM");
		return evmAccount?.address;
	}, [sender, accounts]);

	// Detect if sender is a smart account or EIP-7702 account
	const accountType = useSmartAccountType(
		senderAddress ?? "",
		fromChainId ?? 1,
	);

	// Check ERC-1271 support only if it's a smart account or EIP-7702 account
	const shouldCheckERC1271 = useMemo(
		() =>
			Boolean(
				senderAddress &&
					fromChainId &&
					!isSolana(fromChainId) &&
					accountType.isEIP7702,
			),
		[senderAddress, fromChainId, accountType.isEIP7702],
	);

	const { supportsERC1271, isLoading: isCheckingERC1271 } = useSupportsERC1271(
		shouldCheckERC1271 ? senderAddress : undefined,
		shouldCheckERC1271 && fromChainId ? fromChainId : undefined,
	);

	/**
	 * Should be set to true for following cases:
	 * - If smartAccount and EIP-7702:
	 *   - If ERC1271 is not supported: return true (must use inbox, cannot sign)
	 *   - If ERC1271 is supported and approval is given: return false (can sign)
	 *   - If ERC1271 is supported but approval is not given:
	 *     - If supports batching & batchingEnabled: return true (use inbox for batching)
	 *     - Otherwise: return false (can sign without approval)
	 * - If smartAccount but not EIP-7702: return true (must use inbox)
	 * - Otherwise: return false
	 */
	const useInbox = useMemo(() => {
		if (isLoadingCapabilities) return false;

		// If not a smart account, return false
		if (!accountType.isSmartContract) return false;

		// If smart account and EIP-7702
		if (accountType.isEIP7702) {
			// EIP-7702 wallets can sign only if ERC1271 is supported
			// If ERC1271 is not supported, user must go via inbox
			if (!supportsERC1271) return true;

			// If ERC1271 is supported and approval is given, wallet can sign
			if (hasPermit2Approval) return false;

			// If ERC1271 is supported but no approval, check if we can batch
			if (supportsAtomicBatching && smartBatching) return true;

			// Otherwise, wallet can sign (ERC1271 supported, no approval needed)
			return false;
		}

		// If smart account but not EIP-7702, must use inbox
		return true;
	}, [
		isLoadingCapabilities,
		accountType.isSmartContract,
		accountType.isEIP7702,
		hasPermit2Approval,
		supportsERC1271,
		supportsAtomicBatching,
		smartBatching,
	]);

	const shouldStopPolling = useRef(false);
	const lastErrorKey = useRef<string | null>(null);

	const errorKey = JSON.stringify({
		fromChainId,
		toChainId,
		input: amounts?.[0],
		tokenIn: fromTokens?.[0]?.address,
		tokenOut: toTokens?.[0]?.address,
	});

	// biome-ignore lint/correctness/useExhaustiveDependencies: tracking `dispatch` is redundant for this effect
	useEffect(() => {
		dispatch(setIsFetchingDisabled(false));
	}, []);

	const queryParams = useMemo(() => {
		const userAddress = getValidAddress({
			chainId: fromChainId,
			address: sender,
			fallbackAddress: null,
		});

		const addressWithSameNetwork = accounts.find(
			(account) =>
				account.chainType === (isSolana(toChainId || 0) ? "SVM" : "EVM"),
		)?.address;

		const receiverAddress = getValidAddress({
			chainId: toChainId,
			address: receiver,
			fallbackAddress: addressWithSameNetwork || sender,
		});

		const params = new URLSearchParams({
			userAddress,
			originChainId: fromChainId?.toString() ?? "",
			destinationChainId: toChainId?.toString() ?? "",
			inputAmount: amounts?.[0]?.toString() ?? "",
			inputToken: fromTokens?.[0]?.address ?? "",
			// enable manual if feature is enabled - can be extended to user setting (not implemented yet)
			enableManual: manualModeFeatureEnabled ? "true" : "false",
			receiverAddress,
			refuel: addOnRefuel ? "true" : "false",
			excludeBridges: "cctp",
			useInbox: useInbox ? "true" : "false",
			enableMultipleAutoRoutes: "true",
		});

		// appending output tokens
		if (toTokens?.length) {
			params.append("outputToken", getTokenAddresses(toTokens).join(","));
		}

		if (!dynamicSlippage) {
			params.append("slippage", slippage);
		}

		return params;
	}, [
		accounts,
		fromChainId,
		toChainId,
		sender,
		fromTokens,
		toTokens,
		amounts,
		receiver,
		slippage,
		addOnRefuel,
		manualModeFeatureEnabled,
		dynamicSlippage,
		useInbox,
	]);

	const query = `${baseUrl}/bungee/quote?${queryParams.toString()}`;

	const { data, isLoading, refetch, isRefetching, isError } = useQuery({
		queryKey: [
			"bungee-auto-quote",
			apiKey,
			amounts,
			fromChainId,
			toChainId,
			fromTokens,
			toTokens,
			sender,
			receiver,
			slippage,
			addOnRefuel,
			dynamicSlippage,
			useInbox,
			queryParams,
			queryEnabled,
		],
		queryFn: async () => {
			onRequestQuote({
				source_chain_id: fromChainId as number,
				destination_chain_id: toChainId as number,
				source_token_symbol: fromTokens?.[0]?.symbol ?? "",
				destination_token_symbol: toTokens?.[0]?.symbol ?? "",
				input_amount: Number(amounts?.[0] ?? 0),
			});
			const response = await bungeeApiFetcher({
				query,
				apiKey,
				affiliateId,
			});
			return response;
		},
		enabled: queryEnabled,
		refetchInterval:
			shouldStopPolling.current || isFetchingDisabled ? false : 30000,
		refetchOnWindowFocus: false,
		refetchOnMount: false,
		refetchOnReconnect: true,
		refetchIntervalInBackground: false,
		retry: (value) => {
			if (value > 1) {
				shouldStopPolling.current = true;
				return false;
			}
			return true;
		},
		staleTime: 30000,
		throwOnError: () => {
			if (
				!fromTokens?.[0] ||
				!toTokens?.[0] ||
				!amounts?.[0] ||
				!fromChainId ||
				!toChainId
			) {
				return false;
			}

			onQuoteResult({
				bestoutput_option: undefined,
				fastest_option: undefined,
				default_selected: undefined,
				quote_count: 0,
				status: "error",
				source_chain_id: fromChainId,
				destination_chain_id: toChainId,
				source_token_symbol: fromTokens[0].symbol,
				destination_token_symbol: toTokens[0].symbol,
				input_amount: Number(amounts[0]),
			});
			return false;
		},
	});

	const formattedAutoRoutes = formatAutoRoutes(data?.result);

	// Sync loading states to Redux store
	useEffect(() => {
		dispatch(setLoading(isLoading));
	}, [dispatch, isLoading]);

	useEffect(() => {
		dispatch(setRefetching(isRefetching));
	}, [dispatch, isRefetching]);

	useEffect(() => {
		if (isError && errorKey !== lastErrorKey.current) {
			lastErrorKey.current = errorKey;
			logEvent("quote_result", {
				bestoutput_option: undefined,
				fastest_option: undefined,
				default_selected: undefined,
				quote_count: 0,
				status: "error",
				source_chain_id: fromChainId,
				destination_chain_id: toChainId,
				source_token_symbol: fromTokens[0]?.symbol,
				destination_token_symbol: toTokens[0]?.symbol,
				input_amount: Number(amounts[0]),
				input_amount_usd: undefined,
				refuel: undefined,
			});
		}
	}, [
		isError,
		errorKey,
		logEvent,
		fromChainId,
		toChainId,
		amounts,
		fromTokens,
		toTokens,
	]);

	return {
		data: data?.result as BungeeQuote,
		formattedAutoRoute: formattedAutoRoutes?.[0],
		formattedAutoRoutes,
		formattedManualRoutes: addOnRefuel
			? null
			: formatManualRoutes(data?.result),
		isLoading,
		isRefetching,
		refetch,
		isRefetchIntervalPaused: shouldStopPolling.current || isFetchingDisabled,
	};
};

// function getMultiplePairs(key: string, values: string[] | number[]) {
// 	const _arr = [];

// 	for (const value of values) {
// 		_arr.push(`${key}=${value}`);
// 	}

// 	return _arr.join("&");
// }
