import { useState, useMemo, useEffect } from "react";
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
import toast from "react-hot-toast";

import TokenStack from "../../components/TokenStack/TokenStack";
import SwapCard from "../../components/Swap/SwapCard";
import SwapCollection from "../../components/Swap/SwapCollection";
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
import { SecondaryButton } from "../../components/Button";

import { formatCurrency } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { prettifySecondsInDays } from "../../helpers/timeUtil";

import { getTokenAddress } from "../../hooks/helpers";
import { useBalance, depositNative, withdrawWeth } from "../../hooks/tokens";
import {
    useUniswapV2Pair,
    useUniswapV2PairReserves,
    swapExactTokensForTokens,
    swapExactETHForTokens,
    swapExactTokensForETH,
} from "../../hooks/uniswapv2";

import { EMPTY_ADDRESS } from "../../constants/addresses";

const SwapContainer = ({
    tokenNameTop,
    tokenNameBottom,
    address,
    chainId,
    dexAddresses,
    connect,
    onCardsSwap,
    setTopTokenListOpen,
    setBottomTokenListOpen,
    slippage,
    destination,
    secondsToWait,
    setIsSwap,
    formatDecimals,
    isWrapping,
    isUnwrapping,
}) => {
    const theme = useTheme();
    const isSmallScreen = useMediaQuery("(max-width: 456px)");

    const [isPending, setIsPending] = useState(false);
    const [amountBottom, setAmountBottom] = useState("");
    const [amountTop, setAmountTop] = useState("");

    const [currentPrice, setCurrentPrice] = useState(new DecimalBigNumber(0n, 0));
    const [nextPrice, setNextPrice] = useState(new DecimalBigNumber(0n, 0));

    const {
        balance: balanceTop,
        refetch: balanceRefetchTop,
        contractAddress: addressTop,
        isNative: topIsNative,
    } = useBalance(chainId, tokenNameTop, address);

    const {
        balance: balanceBottom,
        refetch: balanceRefetchBottom,
        contractAddress: addressBottom,
        isNative: bottomIsNative,
    } = useBalance(chainId, tokenNameBottom, address);

    const {
        pairAddress,
    } = useUniswapV2Pair(chainId, dexAddresses.factory, addressTop, addressBottom);

    const {
        reserves: pairReserves,
        tokens: tokenAddresses,
        refetch: pairReservesRefetch,
    } = useUniswapV2PairReserves(
        chainId,
        pairAddress,
    );

    const onSwap = () => {
        setAmountTop("");
        setAmountBottom("");
        onCardsSwap();
    }
    const setMax = () => setAmountTop(balanceTop.toString());

    useEffect(() => {
        if (isWrapping || isUnwrapping) {
            setAmountBottom(amountTop.toString());
            setNextPrice("1");
            setCurrentPrice("1");
            return;
        }

        const zero = new DecimalBigNumber(0n, 0);
        const raw = new DecimalBigNumber(amountTop, balanceTop._decimals);
        const amountInRaw = new DecimalBigNumber(raw._value.toBigInt(), balanceTop._decimals);
        const amountInWithFee = amountInRaw.mul(new DecimalBigNumber(997n, 3));

        const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() ? pairReserves.reserve0 : pairReserves.reserve1;
        const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase() ? pairReserves.reserve1 : pairReserves.reserve0;

        if (amountOut.eq(zero)) {
            setCurrentPrice("");
        } else {
            setCurrentPrice(amountIn.div(amountOut).toString());
        }

        if (amountOut.eq(zero) || amountInWithFee.eq(zero)) {
            setAmountBottom("");
            setNextPrice("");
        } else {
            const nominator = amountOut.mul(amountInWithFee);
            const denominator = amountIn.add(amountInWithFee);
            const newAmountOut = nominator.div(denominator);

            const newReserveIn = amountIn.add(amountInWithFee);
            const newReserveOut = amountOut.sub(newAmountOut);
            const nextPrice = newReserveIn.div(newReserveOut);

            setAmountBottom(newAmountOut.toString());
            setNextPrice(nextPrice.toString());
        }
    }, [pairReserves, addressBottom, amountTop, addressTop, isWrapping, isUnwrapping]);

    const minReceived = useMemo(() => {
        const decimals = 7;
        const shares = Math.pow(10, decimals);
        const one = BigInt(shares * 100);

        const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
        const bigIntSlippage = one - BigInt(Math.round(floatSlippage * shares));
        const slippageDecimalBigNumber = new DecimalBigNumber(bigIntSlippage, 2);

        const bigIntAmount = BigInt(Math.round(amountBottom * shares));
        const amountDecimalBigNumber = new DecimalBigNumber(bigIntAmount, decimals);

        const tmpResult = amountDecimalBigNumber.mul(slippageDecimalBigNumber);
        const result = new DecimalBigNumber(tmpResult?._value, tmpResult?._decimals + decimals);
        return result?.toString();
    }, [amountBottom, amountBottom, slippage, balanceBottom]);

    const buttonText = useMemo(() => {
        let text = "Swap";
        if (isWrapping) text = "Wrap";
        else if (isUnwrapping) text = "Unwrap";
        else if (pairAddress === EMPTY_ADDRESS) text = "Create Pool";
        return text;
    }, [isWrapping, isUnwrapping, pairAddress]);

    const swapTokens = async () => {
        setIsPending(true);

        const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
        const shares = 100000;
        const one = BigInt(shares * 100);
        const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
        const bigIntSlippage = one - BigInt(Math.round(floatSlippage * shares));

        if (floatSlippage < 3) toast("Slippage is too low, transaction highly likely will fail.");

        const amountADesired = BigInt(Math.round(parseFloat(amountTop) * Math.pow(10, balanceTop._decimals)));
        const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
        const amountBMin = amountBDesired * bigIntSlippage / one;

        if (isWrapping) {
            await depositNative(chainId, address, amountADesired);
        } else if (isUnwrapping) {
            await withdrawWeth(chainId, address, amountADesired);
        } else {
            const params = {
                chainId,
                amountADesired,
                amountBMin,
                tokenNameTop,
                tokenNameBottom,
                destination,
                address,
                deadline
            };

            if (topIsNative) {
                await swapExactETHForTokens(params)
            } else if (bottomIsNative) {
                await swapExactTokensForETH(params)
            } else {
                await swapExactTokensForTokens(params);
            }
        }

        await balanceRefetchTop();
        await balanceRefetchBottom();
        await pairReservesRefetch();

        setAmountTop("");
        setIsPending(false);
    }

    return (
        <Box maxWidth="356px" display="flex" flexDirection="column" justifyContent="space-between" height="100%">
            <SwapCollection
                UpperSwapCard={
                    <SwapCard
                        maxWidth="356px"
                        id="from"
                        token={<TokenStack tokens={[tokenNameTop]} sx={{ fontSize: "21px" }} />}
                        tokenName={tokenNameTop}
                        info={
                            (!isSmallScreen ? "Balance: " : "") +
                                formatCurrency(balanceTop ? balanceTop : "0", formatDecimals, tokenNameTop)
                        }
                        endString="Max"
                        endStringOnClick={setMax}
                        value={amountTop}
                        onChange={event => setAmountTop(event.currentTarget.value)}
                        tokenOnClick={() => setTopTokenListOpen(true)}
                        inputProps={{ "data-testid": "fromInput" }}
                    />
                    }
                    LowerSwapCard={
                        <SwapCard
                            maxWidth="356px"
                            id="to"
                            token={<TokenStack tokens={[tokenNameBottom]} sx={{ fontSize: "21px" }} />}
                            tokenName={tokenNameBottom}
                            value={amountBottom}
                            inputProps={{ "data-testid": "toInput" }}
                            tokenOnClick={() => setBottomTokenListOpen(true)}
                            info={
                                (!isSmallScreen ? "Balance: " : "") +
                                    formatCurrency(balanceBottom ? balanceBottom : "0", formatDecimals, tokenNameBottom)
                            }
                        />
                    }
                    arrowOnClick={onSwap}
                />
            <Box
                m="10px 0"
                display="flex"
                flexDirection="column"
                justifyContent="space-between"
                alignItems="center"
                gap="0px"
                maxWidth="356px"
                style={{ fontSize: "12px", color: theme.colors.gray[40] }}
            >
                <Box width="100%" display="flex" justifyContent="space-between">
                    <Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom} (Current)`}</Typography>
                    <Typography fontSize="12px" lineHeight="15px">{formatCurrency(currentPrice, formatDecimals, tokenNameTop)}</Typography>
                </Box>
                <Box width="100%" display="flex" justifyContent="space-between">
                    <Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom} (Next)`}</Typography>
                    <Typography fontSize="12px" lineHeight="15px">{formatCurrency(nextPrice === "" ? currentPrice : nextPrice, formatDecimals, tokenNameTop)}</Typography>
                </Box>
                <Box width="100%" display="flex" justifyContent="space-between">
                    <Typography fontSize="12px" lineHeight="15px">Min. Receive:</Typography>
                    <Typography fontSize="12px" lineHeight="15px">{formatCurrency(minReceived, formatDecimals, tokenNameBottom)}</Typography>
                </Box>
                <Box width="100%" display="flex" justifyContent="space-between">
                    <Typography fontSize="12px" lineHeight="15px">Tx. Deadline:</Typography>
                    <Typography fontSize="12px" lineHeight="15px">~{prettifySecondsInDays(secondsToWait)}</Typography>
                </Box>
            </Box>
            <TokenAllowanceGuard
                spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
                tokenName={tokenNameTop}
                owner={address}
                spender={dexAddresses.router}
                decimals={balanceTop._decimals}
                isNative={topIsNative}
                approvalText={"Approve " + tokenNameTop}
                approvalPendingText={"Approving..."}
                connect={connect}
                width="100%"
                height="60px"
            >
                <SecondaryButton
                    fullWidth
                    disabled={
                        address !== "" && (
                            (new DecimalBigNumber(amountTop, balanceTop._decimals)).eq(new DecimalBigNumber("0", 18)) ||
                            balanceTop?.lt(new DecimalBigNumber(amountTop, balanceTop._decimals)) ||
                            isPending
                        )
                    }
                    loading={isPending}
                    onClick={() => address === "" ?
                        connect()
                        :
                        (!isWrapping && !isUnwrapping) && pairAddress === EMPTY_ADDRESS ? setIsSwap(false) : swapTokens()
                    }
                >
                    {address === "" ? "Connect" : buttonText }
                </SecondaryButton>
            </TokenAllowanceGuard>
        </Box>
    )
}

export default SwapContainer;
