import { useState, useEffect, useMemo } 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 { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatNumber, formatCurrency } from "../../helpers";

import { useBalance, useTotalSupply } from "../../hooks/tokens";
import { useUniswapV2Pair, useUniswapV2PairReserves, addLiquidity } from "../../hooks/uniswapv2";

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

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

    const {
        balance: balanceTop,
        refetch: balanceRefetchTop,
        contractAddress: addressTop,
    } = useBalance(chainId, tokenNameTop, address);
    const {
        balance: balanceBottom,
        refetch: balanceRefetchBottom,
        contractAddress: addressBottom,
    } = useBalance(chainId, tokenNameBottom, address);

    const {
        pairAddress,
        refetch: pairAddressRefetch,
    } = useUniswapV2Pair(chainId, dexAddresses.factory, addressTop, addressBottom);
    const {
        balance: lpBalance,
        refetch: lpRefetch,
    } = useBalance(chainId, pairAddress, address);
    const {
        totalSupply: lpTotalSupply,
        refetch: lpTotalSupplyRefetch
    } = useTotalSupply(chainId, pairAddress);
    const {
        reserves: pairReserves,
        refetch: pairReservesRefetch,
    } = useUniswapV2PairReserves(
        chainId,
        pairAddress,
        balanceTop._decimals,
        balanceBottom._decimals,
        tokenNameTop,
        tokenNameBottom,
    );

    const onSwap = () => {
        const oldAmountTop = amountTop;
        const oldAmountBottom = amountBottom;
        setAmountBottom(oldAmountTop);
        setAmountTop(oldAmountBottom);
        onCardsSwap();
    }

    const setMaxTop = () => setAmountTop(balanceTop.toString());
    const setMaxBottom = () => setAmountBottom(balanceBottom.toString());

    const bigIntSqrt = (n) => {
        if (n < 0n) {
            throw new Error("Cannot compute the square root of a negative number.");
        }
        if (n < 2n) {
            return n; // The square root of 0 or 1 is the number itself
        }

        let low = 0n;
        let high = n;
        let mid;

        while (low <= high) {
            mid = (low + high) / 2n;
            const midSquared = mid * mid;

            if (midSquared === n) {
                return mid; // Found the exact square root
            } else if (midSquared < n) {
                low = mid + 1n; // Move to the right half
            } else {
                high = mid - 1n; // Move to the left half
            }
        }

        return high; // The integer part of the square root
    }

    const estimatedAmountOut = useMemo(() => {
        const zero = new DecimalBigNumber(0n, 0);
        const value0 = new DecimalBigNumber(amountTop, balanceTop._decimals)
        const value1 = new DecimalBigNumber(amountBottom, balanceBottom._decimals)
        const amountToAddA = new DecimalBigNumber(value0._value.toBigInt(), balanceTop._decimals);
        const amountToAddB = new DecimalBigNumber(value1._value.toBigInt(), balanceBottom._decimals);

        if (
            pairReserves.reserve0.gt(zero) &&
            pairReserves.reserve1.gt(zero) &&
            lpTotalSupply.gt(new DecimalBigNumber(0n, 0))
        ) {
            const lpTokensFromA = (amountToAddA.mul(lpTotalSupply).div(pairReserves.reserve0));
            const lpTokensFromB = (amountToAddB.mul(lpTotalSupply).div(pairReserves.reserve1));
            const lpTokensToMint = lpTokensFromA.gt(lpTokensFromB) ? lpTokensFromB : lpTokensFromA;
            return lpTokensToMint;
        } else {
            const tokens = bigIntSqrt(BigInt(amountToAddA.mul(amountToAddB)._value));
            const lpTokensToMint = new DecimalBigNumber(tokens, 18);
            return lpTokensToMint;
        }
    }, [pairReserves, amountTop, amountBottom, balanceTop, balanceBottom]);

    useEffect(() => {
        if (pairReserves && pairReserves.reserve1.gt(new DecimalBigNumber(0n, 0))) {
            const value = new DecimalBigNumber(amountTop, balanceTop._decimals)
            const amountToAdd = new DecimalBigNumber(value._value.toBigInt(), balanceTop._decimals);
            const amount = amountToAdd.mul(pairReserves.reserve0).div(pairReserves.reserve1);
            setAmountBottom(amount.toString());
        }
    }, [amountTop, pairReserves])

    useEffect(() => {
        setAmountTop("");
        setAmountBottom("");
    }, [tokenNameTop, tokenNameBottom])

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

        const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
        const destination = address;

        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 amountAMin = amountADesired * bigIntSlippage / one;
        const amountBMin = amountBDesired * bigIntSlippage / one;

        await addLiquidity(
            chainId,
            tokenNameTop,
            tokenNameBottom,
            amountADesired,
            amountBDesired,
            amountAMin,
            amountBMin,
            destination,
            deadline,
        );

        await balanceRefetchTop();
        await balanceRefetchBottom();
        await pairAddressRefetch();
        await lpRefetch();
        await lpTotalSupplyRefetch();
        await pairReservesRefetch();

        setAmountTop("");
        setAmountBottom("");

        setIsPending(false);
    }

    const emptyPool = useMemo(() => {
        return pairAddress === "" || pairAddress === "0x0000000000000000000000000000000000000000"
    }, [pairAddress])

    return (
        <Box maxWidth="356px" display="flex" flexDirection="column" justifyContent="space-between" height="100%">
            <SwapCollection
                UpperSwapCard={
                    <SwapCard
                        id="from"
                        token={<TokenStack tokens={[tokenNameTop]} sx={{ fontSize: "21px" }} />}
                        tokenName={tokenNameTop}
                        info={(!isSmallScreen ? "Balance: " : "") + formatCurrency(balanceTop, formatDecimals, tokenNameTop)}
                        endString="Max"
                        endStringOnClick={setMaxTop}
                        value={amountTop}
                        onChange={event => setAmountTop(event.currentTarget.value)}
                        tokenOnClick={() => setTopTokenListOpen(true)}
                        inputProps={{ "data-testid": "fromInput" }}
                    />
                    }
                    LowerSwapCard={
                        <SwapCard
                            id="to"
                            token={<TokenStack tokens={[tokenNameBottom]} sx={{ fontSize: "21px" }} />}
                            tokenName={tokenNameBottom}
                            value={amountBottom }
                            onChange={event => emptyPool ? setAmountBottom(event.currentTarget.value) : {}}
                            endString={emptyPool ? "Max" : undefined}
                            endStringOnClick={emptyPool ? setMaxBottom : {}}
                            inputProps={{ "data-testid": "toInput" }}
                            tokenOnClick={() => setBottomTokenListOpen(true)}
                            info={(!isSmallScreen ? "Balance: " : "") + formatCurrency(balanceBottom, formatDecimals, tokenNameBottom)}
                        />
                    }
                    arrowOnClick={onSwap}
            />
            {!isSmallScreen && <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">Current Balance:</Typography>
                    <Typography fontSize="12px" lineHeight="15px">{formatNumber(lpBalance, formatDecimals)} LP</Typography>
                </Box>
                <Box width="100%" display="flex" justifyContent="space-between">
                    <Typography fontSize="12px" lineHeight="15px">Total Supply:</Typography>
                    <Typography fontSize="12px" lineHeight="15px">{formatNumber(lpTotalSupply, formatDecimals)} LP</Typography>
                </Box>
                <Box width="100%" display="flex" justifyContent="space-between">
                    <Typography fontSize="12px" lineHeight="15px">Extra Balance:</Typography>
                    <Typography fontSize="12px" lineHeight="15px">~{formatNumber(estimatedAmountOut, formatDecimals)} LP</Typography>
                </Box>
            </Box>}
            <TokenAllowanceGuard
                spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
                tokenName={tokenNameTop}
                owner={address}
                spender={dexAddresses.router}
                decimals={balanceTop._decimals}
                approvalText={"Approve " + tokenNameTop}
                approvalPendingText={"Approving..."}
                connect={connect}
                width="100%"
                height="60px"
            >
                <TokenAllowanceGuard
                    spendAmount={new DecimalBigNumber(amountBottom, balanceBottom._decimals)}
                    tokenName={tokenNameBottom}
                    owner={address}
                    spender={dexAddresses.router}
                    decimals={balanceBottom._decimals}
                    approvalText={"Approve " + tokenNameBottom}
                    approvalPendingText={"Approving..."}
                    connect={connect}
                    width="100%"
                    height="60px"
                >
                    <SecondaryButton
                        fullWidth
                        disabled={
                            address !== "" && (
                                (new DecimalBigNumber(amountTop, balanceTop._decimals)).eq(new DecimalBigNumber("0", 18)) ||
                                (new DecimalBigNumber(amountBottom, balanceBottom._decimals)).eq(new DecimalBigNumber("0", 18)) ||
                                balanceTop?.lt(new DecimalBigNumber(amountTop, balanceTop._decimals)) ||
                                balanceBottom?.lt(new DecimalBigNumber(amountBottom, balanceBottom._decimals)) ||
                                isPending
                            )
                        }
                        loading={isPending}
                        onClick={() => address === "" ? connect() : addLiquidityInner()}
                    >
                        {address === "" ?
                            "Connect"
                            :
                            pairAddress === "0x0000000000000000000000000000000000000000" ?
                                "Create Pool"
                                :
                                "Add Liquidity"
                        }
                    </SecondaryButton>
                </TokenAllowanceGuard>
            </TokenAllowanceGuard>
        </Box>
    )
}

export default PoolContainer;
