import { AnimatePresence, motion } from "framer-motion";
import { type ReactNode, useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";

import type { Input, Output } from "@/types/index";
import { Approval } from "@components/Approval";
import { chainColors } from "@components/TokenItem";
import { Button } from "@components/common/Button";
import { ModalCloseBtn } from "@components/common/ModalHeader";
import { SquaredCheckbox } from "@components/common/SquaredCheckbox";
import { Tooltip } from "@components/common/Tooltip";
import type { FormattedQuoteResponse } from "@hooks/apis/common/quote/types";
import { useActiveRoute } from "@hooks/apis/common/quote/useActiveRoute";
import { isAutoQuote, isManualQuote } from "@hooks/apis/common/quote/useQuote";
import { useMappedChainData } from "@hooks/useMappedChainData";
import { useTheme } from "@hooks/useTheme";
import { ScreenTab, setScreenTab } from "@store/globalSlice";
import type { ReduxStateType } from "@store/types";
import { adjustDecimals } from "@utils/adjustDecimals";
import { calculatePriceImpact } from "@utils/calculatePriceImpact";
import { formatValue } from "@utils/formatValue";
import { getBorderRadius } from "@utils/getBorderRadius";
import { isDummyAddress } from "@utils/isDummyAddress";
import { onViewSwapReviewScreen } from "../../analytics/events";
import { TokenDisplay } from "../common/TokenDisplay";
import { ActionButton } from "./ActionButton";
import { useActionStatusContext } from "./Auto/ActionStatusProvider";
import { ReviewSection } from "./ReviewSection";

export const Review = () => {
  const dispatch = useDispatch();
  const { data: newQuoteData } = useActiveRoute();
  const { status: actionStatus } = useActionStatusContext();

  const [quoteData, setQuoteData] = useState<FormattedQuoteResponse>();
  const [isQuoteUpdated, setIsQuoteUpdated] = useState<boolean>(false);
  const [acceptHighSlippage, setAcceptHighSlippage] = useState<boolean>(false); // for price impact warning

  const firstRender = useRef(true);

  // To check if the quote is updated
  // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
  useEffect(() => {
    if (!quoteData && firstRender.current && newQuoteData) {
      setQuoteData(newQuoteData);
      const protocol = isManualQuote(newQuoteData)
        ? newQuoteData.protocol
        : undefined;
      // Emit event
      onViewSwapReviewScreen({
        source_chain_id: newQuoteData.primaryInput.token.chainId,
        destination_chain_id: newQuoteData.primaryOutput.token.chainId,
        source_token_symbol: newQuoteData.primaryInput.token.symbol,
        destination_token_symbol: newQuoteData.primaryOutput.token.symbol,
        input_amount_usd: Number(newQuoteData.primaryInput.amount),
        estimated_output_amount: Number(
          newQuoteData.minOutput?.formattedAmount
        ),
        provider_name: protocol?.dexDetails?.protocol.name ?? "",
        estimated_time: newQuoteData.estimatedFulfilTime,
      });
      firstRender.current = false;
    }

    if (quoteData && newQuoteData) {
      const isAuto = isAutoQuote(quoteData) && isAutoQuote(newQuoteData);
      const isManual = isManualQuote(quoteData) && isManualQuote(newQuoteData);

      if (isAuto) {
        setIsQuoteUpdated(quoteData.requestHash !== newQuoteData.requestHash);
      } else if (isManual) {
        setIsQuoteUpdated(quoteData.routeId !== newQuoteData.routeId);
      }
    }
  }, [newQuoteData]);

  const { primaryOutput, input, output, receiver } = quoteData || {};

  function closeReview() {
    dispatch(setScreenTab(ScreenTab.HOME));
  }

  function acceptNewPrice() {
    setQuoteData(newQuoteData); // do we use unified type here?
    setIsQuoteUpdated(false);
  }
  const title = useGetTitle(input, output);
  const priceDiffInUsd = useMemo(() => {
    if (primaryOutput && newQuoteData) {
      const { primaryOutput: primaryOutputNew } = newQuoteData;
      if (primaryOutput?.valueInUsd && primaryOutputNew?.valueInUsd) {
        return primaryOutputNew.valueInUsd - primaryOutput.valueInUsd;
      }
    }
  }, [primaryOutput, newQuoteData]);

  // if receiver address is same as dummy, go back to homescreen
  if (isDummyAddress(receiver ?? "")) {
    dispatch(setScreenTab(ScreenTab.HOME));
  }

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

  return (
    <AnimatePresence>
      <motion.div
        layout
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        exit={{ opacity: 0, y: -20 }}
        transition={{ duration: 0.3, ease: "easeInOut" }}
        key="review"
      >
        <div className="bn-h-6 bn-flex bn-items-center bn-justify-center bn-relative bn-mb-4">
          <p className="bn-text-primary bn-text-lg bn-font-medium">Review</p>

          <ModalCloseBtn
            closeModal={closeReview}
            classNames="bn-absolute bn-right-0"
          />
        </div>

        <motion.div
          initial={{ opacity: 0, scale: 0.9 }}
          animate={{ opacity: 1, scale: 1 }}
          transition={{ delay: 0.1, duration: 0.2 }}
          className="bn-mb-4 bn-w-fit bn-mx-auto"
        >
          {input && output && <TokenDisplay input={input} output={output} />}
        </motion.div>

        <motion.div
          initial={{ opacity: 0, y: 10 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ delay: 0.2, duration: 0.2 }}
        >
          <p className="bn-text-secondary bn-font-normal bn-font-base bn-text-center bn-mb-1">
            You're Swapping
          </p>
          <p className="bn-text-lg bn-font-medium bn-aeonik-font bn-text-center bn-text-primary">
            {title}
          </p>
        </motion.div>

        <motion.div
          initial={{ opacity: 0, scale: 0.95 }}
          animate={{ opacity: 1, scale: 1 }}
          transition={{ delay: 0.3, duration: 0.2 }}
          className="bn-border-[1.5px] bn-border-dashed bn-border-subtle bn-rounded-xl bn-my-4"
        >
          <ReviewSection full />
        </motion.div>

        {priceImpact && (
          <div className="bn-flex bn-items-center bn-space-x-2 bn-mb-4">
            <SquaredCheckbox
              id="bungee-price-impact"
              active={acceptHighSlippage}
              setActive={(value) => setAcceptHighSlippage(value)}
              classNames="bn-size-6"
            />
            <button
              className="bn-text-secondary bn-text-sm bn-font-normal bn-text-left"
              type="button"
              onClick={() => setAcceptHighSlippage(!acceptHighSlippage)}
            >
              This swap has a high price impact. You will receive{" "}
              <span className="bn-text-accent-4">-${priceImpact}</span> less than
              the expected market value.
            </button>
          </div>
        )}

        {priceImpact && !acceptHighSlippage ? (
          <Button label="Swap" onClick={() => {}} disabled />
        ) : (
          <motion.div
            initial={{ opacity: 0, y: 10 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.2 }}
          >
            <QuoteUpdated
              isQuoteUpdated={
                isQuoteUpdated &&
                actionStatus !== "awaiting_wallet_confirmation" &&
                actionStatus !== "loading"
              }
              endpoint={primaryOutput}
              acceptNewPrice={acceptNewPrice}
              priceDiffInUsd={priceDiffInUsd}
            >
              <ActionButton />
            </QuoteUpdated>
          </motion.div>
        )}
      </motion.div>

      <Approval key="approval" />
    </AnimatePresence>
  );
};

const QuoteUpdated = ({
  children,
  endpoint,
  isQuoteUpdated = false,
  acceptNewPrice,
  priceDiffInUsd,
}: {
  children: ReactNode;
  endpoint?: Input;
  isQuoteUpdated: boolean;
  acceptNewPrice: () => void;
  priceDiffInUsd?: number;
}) => {
  const { borderRadius } = useTheme();
  const token = endpoint?.token;
  const outputAmount = token && formatValue(endpoint.amount, token.decimals);

  // biome-ignore format:
  const containerStyle = `${isQuoteUpdated ? "bn-bg-layer-2" : ""}`;

  return (
    <motion.div
      layout
      initial={{ opacity: 0, scale: 0.9, padding: 0 }}
      animate={{ opacity: 1, scale: 1, padding: isQuoteUpdated ? "14px" : 0 }}
      exit={{ opacity: 0, scale: 0.9, padding: 0 }}
      transition={{ duration: 0.3 }}
      className={containerStyle}
      style={{ borderRadius: getBorderRadius(borderRadius, 8) }}
    >
      {isQuoteUpdated && outputAmount ? (
        <motion.div
          initial={{ opacity: 0, y: -10 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -10 }}
          transition={{ duration: 0.3, delay: 0.3 }}
        >
          <p className="bn-text-[13px] bn-font-normal bn-text-secondary bn-mb-1.5">
            Quote updated:{" "}
          </p>
          <p className="bn-text-base bn-font-medium bn-aeonik-font bn-text-secondary bn-mb-3 bn-space-x-1.5">
            <span className="bn-text-primary">
              <Tooltip content={outputAmount}>
                <span>{adjustDecimals(outputAmount, 3)}</span>
              </Tooltip>{" "}
              {token?.symbol}
            </span>
            {/* biome-ignore format: */}
            {priceDiffInUsd ? (
              <span
                className={`${
                  priceDiffInUsd > 0 ? "bn-text-accent-3" : "bn-text-accent-4"
                }`}
              >
                ({formatCurrency(priceDiffInUsd)} from last quote)
              </span>
            ) : null}
          </p>
          <Button onClick={acceptNewPrice} label="Accept New Price" />
        </motion.div>
      ) : (
        children
      )}
    </motion.div>
  );
};

function formatCurrency(num: number) {
  const sign = num < 0 ? "-" : "+";
  const absValue = Math.abs(num);
  // Format with 5 decimals, then remove trailing zeros
  const formatted = adjustDecimals(absValue, 3);
  return `${sign}$${formatted}`;
}

function getTokenDetail(endpoint: Input[], locale: string) {
  return endpoint.map((_endpoint, index) => {
    const token = _endpoint.token;
    const formattedValue = formatValue(_endpoint.amount, token.decimals);

    // biome-ignore format:
    const tokenValueInText = `${adjustDecimals(formattedValue, 3, locale)} ${
      token.symbol
    }`;

    if (index === 0) {
      return tokenValueInText;
    }

    // biome-ignore lint/style/useTemplate: <explanation>
    return " & " + tokenValueInText;
  });
}

function useGetTitle(input?: Input[], output?: Output[]) {
  const mappedChainData = useMappedChainData();
  const locale = useSelector(
    (state: ReduxStateType) => state.settings.language
  );

  if (!input || !output) return;

  const inputToken = input[0].token;
  const outputToken = output[0].token;

  const inputColor = chainColors[input[0].token.chainId];
  const outputColor = chainColors[output[0].token.chainId];

  const inputChainName = mappedChainData?.[inputToken?.chainId]?.name;
  const outputChainName = mappedChainData?.[outputToken?.chainId]?.name;

  return (
    <>
      <span className={inputColor}>{getTokenDetail(input, locale)}</span> on{" "}
      {inputChainName} to receive{" "}
      <span className={outputColor}>{getTokenDetail(output, locale)}</span> on{" "}
      {outputChainName}
    </>
  );
}
