import { useEffect, useMemo, useState } from "react";
import { useDispatch } from "react-redux";
import type { Address } from "viem";
import {
  type UseCallsStatusReturnType,
  useCallsStatus,
  useTransactionReceipt,
} from "wagmi";

import { type Output, RequestStatusEnum } from "@/types/index";
import type { ManualOrderData } from "@hooks/apis/auto/useAutoHistory";
import { useStatus } from "@hooks/apis/common/useStatus";
import { useValidateSolanaTransaction } from "@hooks/useValidateSolanaTransaction";
import { updateManualHistory } from "@store/globalSlice";
import { isSolana } from "@utils/isSolana";
import {
  type TrackOrderStatus,
  onCompleteSwap,
  trackOrderStatus,
} from "../../../analytics/events";
import { trackOnceIfNotAlready } from "../../../analytics/trackOnceIfNotAlready";
import { type InflightInfo, ManualData } from "../common/data";
import { InFlightLayout } from "../layout";

export const ManualInFlight = ({
  initialData,
}: {
  initialData: ManualOrderData;
}) => {
  const dispatch = useDispatch();
  // const [routeData, setRouteData] = useState<ManualOrderData>();

  const isBatchingFlow = initialData.flowType === "bundled";

  const isSolanaTx = isSolana(initialData.primaryInput.token.chainId);

  const [isCompleted, setIsCompleted] = useState(false);

  const { data: callsData, error: callsStatusError } = useCallsStatus({
    id: initialData?.requestHash,
    query: {
      refetchInterval: 3000,
      //   enabled:
      //     routeData?.status !== "COMPLETED" && isBatchingFlow && !isSolanaTx,
      enabled: !isCompleted && isBatchingFlow && !isSolanaTx,
    },
  });

  const {
    data: txResolved,
    error: txError,
    isLoading: isTxReceiptLoading,
  } = useTransactionReceipt({
    hash: initialData?.requestHash as Address,
    query: {
      retry: 5,
      retryDelay: 3000,
      enabled: !isSolanaTx && !isBatchingFlow,
    },
  });

  const { data: solanaTxData, error: solanaTxError } =
    useValidateSolanaTransaction({
      signature: initialData?.requestHash,
      query: {
        retry: 5,
        retryDelay: 3000,
        enabled: isSolanaTx,
      },
    });

  const [updatedRouteData, setUpdatedRouteData] = useState<ManualOrderData>();

  // for batched tx, requestHash will be overriden by requestHash from receipts
  // initially set to batchId
  const { data: statusData, isLoading: isStatusLoading } = useStatus({
    requestHash: updatedRouteData?.requestHash ?? initialData.requestHash,
  });

  /**
   * FOR REGULAR FLOW
   * - Setting statusCode to 2 (extracted) when source tx confirmed or when status is returned as pending
   * - Setting statusCode to 3 (completed) when dest tx is confirmed
   * - In case of swaps, marking as complete after the tx is confirmed on source
   *
   * FOR BUNDLED FLOW
   * - Setting statusCode to 2 when effectiveCallsStatus is success (or tx confirmed on source)
   * - Setting status to reverted when affectiveCallStatus is reverted (checking tx receipts)
   * - For completed, relies on regular status check (via api)
   */
  const routeData = useMemo((): ManualOrderData => {
    const isTxComplete =
      initialData.statusCode >= RequestStatusEnum.COMPLETED &&
      initialData.statusCode < RequestStatusEnum.EXPIRED;
    if (isTxComplete) {
      setIsCompleted(true);
      return initialData;
    }

    const effectiveCallStatus = isBatchingFlow
      ? deriveCallsStatus(callsData)
      : undefined;

    // order is important - for manual flow, last transaction is bridging transaction
    const lastReceipt = callsData?.receipts?.[callsData.receipts.length - 1];

    // If batching and reverted
    // @todo: Need to handle the reverted/cancelled screens
    if (
      isBatchingFlow &&
      (effectiveCallStatus === "reverted" || callsStatusError)
    ) {
      return {
        ...initialData,
        status: "REVERTED",
        statusCode: RequestStatusEnum.REVERTED,
      };
    }

    // If batching and success, update requestHash (for both same chain swap & cross chain swap)
    let updatedData: ManualOrderData = initialData;
    if (
      isBatchingFlow &&
      effectiveCallStatus === "success" &&
      lastReceipt?.transactionHash
    ) {
      updatedData = {
        ...initialData,
        requestHash: lastReceipt.transactionHash, // requestHash is overriden when status is set to extracted (check below - pending or extracted if block)
        srcTimestamp:
          initialData?.srcTimestamp || Math.floor(Date.now() / 1000),
      };

      // If it's a swap route, mark as completed immediately
      if (initialData.isSwapRoute) {
        setIsCompleted(true);
        return {
          ...updatedData,
          status: "COMPLETED",
          statusCode: RequestStatusEnum.COMPLETED,
        };
      }
      // For cross-chain swap, fall through to statusData check (pending or extracted if block - further checking via status api)
    }

    // For both flows: rely on statusData if available
    if (statusData?.destinationData?.status === "COMPLETED") {
      const _output: Output = {
        ...initialData.primaryOutput,
        fulfilledAmount:
          statusData.destinationData.output?.[0]?.fulfilledAmount,
      };

      setIsCompleted(true);

      return {
        ...updatedData,
        status: "COMPLETED",
        statusCode: RequestStatusEnum.COMPLETED,
        output: [_output],
        primaryOutput: _output,
      };
    }

    // Regular flow: tx failure
    const hasFailed =
      (txResolved && txResolved.status === "reverted") || !!solanaTxData?.error;
    if (!isBatchingFlow && hasFailed) {
      return {
        ...initialData,
        status: "REVERTED",
        statusCode: RequestStatusEnum.REVERTED,
      };
    }

    // Regular flow: swap tx success
    if (
      !isBatchingFlow &&
      initialData.isSwapRoute &&
      (txResolved?.status === "success" || !!solanaTxData?.success)
    ) {
      setIsCompleted(true);
      return {
        ...initialData,
        status: "COMPLETED",
        statusCode: RequestStatusEnum.COMPLETED,
      };
    }

    // Pending or extracted
    if (
      ((!isBatchingFlow && statusData?.destinationData?.status === "PENDING") ||
        txResolved?.status === "success" ||
        effectiveCallStatus === "success" ||
        !!solanaTxData?.success) &&
      !txError &&
      !callsStatusError &&
      !solanaTxData?.error &&
      !solanaTxError
    ) {
      return {
        ...updatedData,
        status: "EXTRACTED",
        statusCode: RequestStatusEnum.EXTRACTED,
      };
    }

    return updatedData;
  }, [
    initialData,
    isBatchingFlow,
    callsData,
    txResolved,
    txError,
    callsStatusError,
    statusData,
    solanaTxData,
    solanaTxError,
  ]);

  useEffect(() => {
    if (routeData) {
      // this is done so that status api works for manual batching
      // updating the requestHash from callsdata
      setUpdatedRouteData(routeData);

      dispatch(
        updateManualHistory({
          address: routeData.sender.toLowerCase(),
          data: routeData,
        })
      );
    }

    if (["COMPLETED", "REVERTED"].includes(routeData.status)) {
      const fromToken = routeData.primaryInput.token;
      const toToken = routeData.primaryOutput.token;
      const isSameSwap = fromToken?.chainId === toToken?.chainId;
      const routingMode = `${routeData.routingMode}-${
        isSameSwap ? "same-chain" : "cross-chain"
      }`;

      const orderData: TrackOrderStatus = {
        status: routeData.status === "COMPLETED" ? "success" : "failed",
        from_token: fromToken.symbol,
        to_token: toToken.symbol,
        from_chain: fromToken.chainId,
        to_chain: toToken?.chainId,
        slippage: routeData.slippage,
        routingMode: routingMode,
        flowType: routeData.flowType,
        suggestedClientSlippage: false,
      };

      const trackFn = () => trackOrderStatus(orderData);

      trackOnceIfNotAlready({
        type: "manual",
        requestHash: routeData.requestHash,
        trackFn: () => {
          trackFn();
          onCompleteSwap({
            status: orderData.status,
            request_hash: routeData.requestHash,
            provider_name: routeData.protocol.name,
            actual_time_seconds: routeData.orderTimestamp,
            actual_output_amount: Number(routeData.primaryOutput.amount),
            estimated_time_seconds:
              +new Date(routeData.estimatedFulfilTime) / 1000,
            estimated_output_amount: Number(routeData.primaryOutput.amount),
          });
        },
      });
    }
  }, [routeData, dispatch]);

  const data: InflightInfo = {
    title: ManualData.title,
    subTitle: ManualData.subTitle,
    info: routeData ?? initialData,
  };

  return <InFlightLayout data={data} />;
};

export const deriveCallsStatus = (
  callsData: UseCallsStatusReturnType["data"]
): "success" | "reverted" | "pending" | undefined => {
  if (!callsData) return undefined;

  const receipts = callsData.receipts ?? [];

  if (receipts.length === 0) {
    if (callsData.status === "success") return "success";
    if (callsData.status === "failure") return "reverted";
    if (callsData.status === "pending") return "pending";
    return undefined;
  }

  const receiptStatuses = receipts.map((r) => r.status);
  if (receiptStatuses.every((s) => s === "success")) return "success";
  if (receiptStatuses.some((s) => s === "reverted")) return "reverted";
  if (callsData.status === "pending") return "pending";

  return undefined;
};
