import { useCallback, useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";

import { useIsApprovalRequired } from "@components/Review/Approval/CheckApproval";
import type { QueryStatus } from "@components/Review/Auto/SignOrder";
import { useBatchingTransaction } from "@components/Review/Manual/useBatchingTransaction";
import { Button } from "@components/common/Button";
import type { ManualOrderData } from "@hooks/apis/auto/useAutoHistory";
import type {
	FormattedManualQuote,
	OrderFlowType,
	TxData,
} from "@hooks/apis/common/quote/types";
import { useBuildTransaction } from "@hooks/apis/manual/useBuildTransaction";
import { useAccounts } from "@hooks/useAccounts";
import { useChainSwitcher } from "@hooks/useChainSwitcher";
import { useConfig as useBungeeConfig } from "@hooks/useConfig";
import { USER_OPERATIONS, useLogError } from "@hooks/useLogError";
import { useLogEvent } from "@hooks/useLogEvent";
import { getQueryKey as getTokenBalanceQueryKey } from "@hooks/useTokenBalance";
import { useWalletCapabilities } from "@hooks/useWalletCapabilities";
import { useToast } from "@providers/ToastProvider";
import { useWallet } from "@solana/wallet-adapter-react";
import {
	ScreenTab,
	setScreenTab,
	updateManualHistory,
} from "@store/globalSlice";
import { setInflightData } from "@store/inflightSlice";
import { setIsFetchingDisabled } from "@store/quoteParamSlice";
import type { ReduxStateType } from "@store/types";
import { useQueryClient } from "@tanstack/react-query";
import { calculatePriceImpact } from "@utils/calculatePriceImpact";
import {
	NATIVE_TOKEN_ADDRESS,
	SLOW_ROUTE_THRESHOLD,
	type SOLANA_CHAIN_ID,
} from "@utils/constants";
import { getCommonErrors } from "@utils/getCommonErrors";
import { isSolana } from "@utils/isSolana";
import { signAndSendTransaction } from "@utils/sendTransaction";
import { onInitiateSwap } from "../../../analytics/events";
import { useRegularTransaction } from "./useRegularTransaction";

export const ManualFlow = ({
	data,
	disabled = false,
}: {
	data: FormattedManualQuote;
	disabled?: boolean;
}) => {
	const dispatch = useDispatch();
	const queryClient = useQueryClient();
	const { logEvent } = useLogEvent();
	const { logError } = useLogError();
	const { rpcs, eventHandlers } = useBungeeConfig();
	const isApprovalRequired = useIsApprovalRequired();

	const {
		data: buildData,
		isLoading: isBuildingtx,
		isError: isBuildErr,
		error: buildErr,
	} = useBuildTransaction({
		quoteId: data.quoteId,
	});
	const { accounts } = useAccounts();

	const ethereumAccount = accounts.find(
		(account) => account.chainType === "EVM",
	);
	const solanaAccount = accounts.find((account) => account.chainType === "SVM");

	const targetChainId = data?.primaryInput?.token?.chainId;
	const isSolanaTx = isSolana(targetChainId);

	const { supportsAtomicBatching } = useWalletCapabilities(targetChainId);
	const { smartBatching } = useSelector(
		(state: ReduxStateType) => state.settings,
	);

	const isNativeToken = useMemo(
		() =>
			data?.primaryInput?.token?.address?.toLowerCase() ===
			NATIVE_TOKEN_ADDRESS,
		[data?.primaryInput?.token?.address],
	);

	const { switchToChain } = useChainSwitcher();
	const { showToast } = useToast();
	const [status, setStatus] = useState<QueryStatus>("idle");

	const { sendTransaction: sendSolanaTransaction } = useWallet();

	/**
	 * goes via batching if the following conditions are met:
	 * 1. supportsAtomicBatching - wallet supports atomic batching
	 * 2. smartBatching - smart batching is enabled in settings
	 * 3. !isNativeToken - requires non-native token (native tokens don't need approval)
	 * 4. isApprovalRequired - the token requires approval (if false, then we don't need to batch)
	 * @returns boolean
	 */
	const isBatchingTransaction = useMemo(() => {
		return (
			supportsAtomicBatching &&
			smartBatching &&
			!isNativeToken &&
			isApprovalRequired
		);
	}, [
		supportsAtomicBatching,
		smartBatching,
		isNativeToken,
		isApprovalRequired,
	]);

	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	useEffect(() => {
		if (isBuildErr && buildErr) {
			const { name, shortMessage } = getCommonErrors(buildErr);
			setStatus("idle");
			showToast(name, shortMessage, "error");
			eventHandlers?.onEvent?.("error", {
				title: name,
				message: shortMessage,
				action: "TRANSACTION",
			});
		}
	}, [isBuildErr, buildErr]);

	const {
		triggerAsync: triggerRegular,
		isSuccess: regularSuccess,
		error: regularError,
		txData: regularTxData, // txhash
	} = useRegularTransaction({ txData: buildData?.txData });
	const {
		triggerAsync: triggerBatching,
		isSuccess: batchingSuccess,
		error: batchingError,
		txData: batchingTxData, // id and capabilities
	} = useBatchingTransaction({
		approvalData: buildData?.approvalData,
		txData: buildData?.txData,
		chainId: data?.primaryInput?.token?.chainId,
		isApprovalRequired,
	});

	const [solanaTxData, setSolanaTxData] = useState<string | null>(null);

	const enableQuoteRefresh = useCallback(() => {
		dispatch(setIsFetchingDisabled(false));
	}, [dispatch]);

	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	const initiateTransaction = useCallback(async () => {
		let txHash = "";
		let type: OrderFlowType<"solana"> = "regular";
		let chainSwitchSucceeded = false;
		try {
			dispatch(setIsFetchingDisabled(true));

			if (isSolanaTx) {
				// Solana transactions don't require chain switching
				chainSwitchSucceeded = true;
				setStatus("awaiting_wallet_confirmation");
				const tx = await signAndSendTransaction({
					address: solanaAccount?.address || data.sender.toLowerCase(),
					txData: buildData.txData as TxData<typeof SOLANA_CHAIN_ID>,
					sendTransaction: sendSolanaTransaction,
					rpc: rpcs?.solana,
				});
				setSolanaTxData(tx);
				txHash = tx;
				type = "solana";
				return;
			}

			const targetChainId = data?.primaryInput?.token?.chainId;
			const chainSwitched = await switchToChain(
				targetChainId,
				ethereumAccount?.chainId,
			);
			if (!chainSwitched) {
				dispatch(setIsFetchingDisabled(false));
				return;
			}

			chainSwitchSucceeded = true;
			setStatus("awaiting_wallet_confirmation");

			if (isBatchingTransaction) {
				const tx = await triggerBatching();
				txHash = tx.id;
				type = "bundled";
			} else {
				const tx = await triggerRegular();
				txHash = tx || "";
				type = "regular";
			}
		} catch (err) {
			setStatus("idle");
			const _errTitle = "Failed to initiate transaction";

			showToast(_errTitle, "Please try again", "error");
			console.error(_errTitle, err);
			logError(err, {
				operation: USER_OPERATIONS.TRANSACTION,
				action: "manual-transaction",
			});
		} finally {
			// Only execute analytics/balance invalidation if chain switch succeeded (for EVM) or it's a Solana transaction
			// Note: chainSwitchSucceeded is set to true for Solana transactions before the return
			if (chainSwitchSucceeded) {
				// Refetch balances
				eventHandlers?.onSwapInitiated?.({
					chainId: data?.primaryInput?.token?.chainId,
					hash: txHash,
					type,
				});

				let userAddress = "";
				if (isSolanaTx) {
					userAddress = solanaAccount?.address || data?.sender?.toLowerCase();
				} else {
					userAddress = ethereumAccount?.address || data?.sender?.toLowerCase();
				}

				if (
					userAddress &&
					data?.primaryInput?.token?.chainId &&
					data?.primaryInput?.token?.address
				) {
					//   Invalidate the src token balance (Cause the dest token would not have changed)
					queryClient.invalidateQueries({
						queryKey: getTokenBalanceQueryKey({
							userAddress,
							chainId: data?.primaryInput?.token?.chainId,
							tokenAddress: data?.primaryInput?.token?.address,
							solanaRpc: rpcs?.solana,
						}),
					});
				}

				const isReceiverConnectedWallet = [
					ethereumAccount?.address?.toLowerCase(),
					solanaAccount?.address?.toLowerCase(),
				].includes(data.receiver.toLowerCase());

				// Emit event
				onInitiateSwap(
					{
						source_chain_id: data?.primaryInput?.token?.chainId,
						destination_chain_id: data?.primaryOutput?.token?.chainId,
						source_token_symbol: data?.primaryInput?.token?.symbol,
						destination_token_symbol: data?.primaryOutput?.token?.symbol,
						input_amount_usd: Number(data?.primaryInput?.amount),
						request_hash: txHash,
						provider_name: data?.protocol?.dexDetails?.protocol.name ?? "",
						has_refuel: !!data?.refuel,
						has_custom_recipient: !isReceiverConnectedWallet,
					},
					logEvent,
				);
			}
		}
	}, [
		buildData,
		data,
		dispatch,
		ethereumAccount,
		solanaAccount,
		isBatchingTransaction,
		switchToChain,
		triggerBatching,
		triggerRegular,
		rpcs?.solana,
		logEvent,
	]);

	const modifiedOutput = data.output.map((item) => ({
		...item,
		minAmountOut: data.minOutput?.amount || "", // used to show output in history and inflight
	}));

	const formattedData: ManualOrderData = {
		...data,
		requestHash: regularTxData as string,
		output: modifiedOutput,
		orderTimestamp: Math.floor(Date.now() / 1000), // when the order is placed
		srcTimestamp: 0, // after extraction, so add it when tx receipt returns success
		swapTime: data.estimatedFulfilTime,
		primaryOutput: modifiedOutput?.[0] || data.primaryOutput,
		statusCode: 0, // setting to pending
		status: "PENDING",
		isSwapRoute: data.isSwapRoute,
		flowType: isBatchingTransaction ? "bundled" : "regular",
	};

	// On Success
	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	useEffect(() => {
		if (regularSuccess || batchingSuccess || solanaTxData) {
			const payload = {
				...formattedData,
			};
			enableQuoteRefresh();
			setStatus("idle");

			if (regularTxData) {
				payload.requestHash = regularTxData;
			}

			if (batchingTxData) {
				payload.batchId = batchingTxData.id;
			}

			if (solanaTxData) {
				payload.requestHash = solanaTxData;
			}

			dispatch(setInflightData({ data: payload }));
			dispatch(setScreenTab(ScreenTab.INFLIGHT));

			const address = isSolanaTx
				? solanaAccount?.address
				: ethereumAccount?.address;

			// updating the localstorage - only for manual txs
			dispatch(
				updateManualHistory({
					data: formattedData,
					address: address?.toLowerCase() || data.sender.toLowerCase(),
				}),
			);
		}
	}, [
		isSolanaTx,
		dispatch,
		solanaAccount,
		ethereumAccount,
		regularTxData,
		batchingTxData,
		data.sender,
		regularSuccess,
		batchingSuccess,
		solanaTxData,
	]);

	const slowRoute = useMemo(() => {
		return (
			data.estimatedFulfilTime &&
			data.estimatedFulfilTime >= SLOW_ROUTE_THRESHOLD
		);
	}, [data.estimatedFulfilTime]);

	const priceImpact = calculatePriceImpact(
		data.primaryInput?.valueInUsd,
		data.primaryOutput?.valueInUsd,
	);

	// On Error
	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	useEffect(() => {
		const error = regularError || batchingError;

		if (error) {
			setStatus("idle");
			const { name, shortMessage } = getCommonErrors(error);
			showToast(name, shortMessage, "error");
			enableQuoteRefresh();
			logError(error, {
				operation: USER_OPERATIONS.TRANSACTION,
				action: "manual-tx",
				flowType: supportsAtomicBatching ? "bundled" : "regular",
				routeType: "manual",
			});
		}
	}, [regularError, batchingError]);

	function getButtonLabel() {
		if (status === "awaiting_wallet_confirmation") return "Confirm in wallet";
		// if (status === "loading") return "Submitting request";
		if (isBuildingtx) return "Building";

		if (slowRoute || priceImpact) {
			if (isBatchingTransaction) {
				return "Approve & Swap Anyway";
			}
			return "Swap Anyway";
		}

		// Check if this is a batching transaction (needs approval + swap)
		if (isBatchingTransaction) {
			return "Approve & Swap";
		}

		return "Swap";
	}

	const disable =
		isBuildingtx || status === "awaiting_wallet_confirmation" || disabled;

	return (
		<Button
			label={getButtonLabel()}
			onClick={initiateTransaction}
			isLoading={disable}
			disabled={disable}
			classNames={slowRoute || priceImpact ? "bn-bg-accent-4-text" : ""}
		/>
	);
};
