import { useReadContract, useReadContracts } from "wagmi";
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast";

import { config } from "../../config";

import {
    BOND_DEPOSITORY_ADDRESSES,
    DAO_TREASURY_ADDRESSES,
    BONDING_CALCULATOR_ADDRESSES,
} from "../../constants/addresses";
import { abi as BondAbi } from "../../abi/GhostBondDepository.json";
import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
import { abi as BondingCalculatorAbi } from "../../abi/GhostBondingCalculator.json";

import { useFtsoPrice } from "../prices";
import { useTokenSymbol, useTokenSymbols } from "../tokens";
import { getTokenAddress, getTokenIcons, getBondNameDisplayName, getTokenPurchaseLink } from "../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { shorten } from "../../helpers";

export const useLiveBonds = (chainId) => {
    const baseTokenPerUsd = useFtsoPrice(chainId);

    const { data: liveIndexesRaw, refetch } = useReadContract({
        abi: BondAbi,
        address: BOND_DEPOSITORY_ADDRESSES[chainId],
        functionName: "liveMarkets",
        scopeKey: `liveMarkets-${chainId}`,
        chainId: chainId,
    });

    const { data: markets } = useReadContracts({
        contracts: liveIndexesRaw?.map((id) => ({
            abi: BondAbi,
            address: BOND_DEPOSITORY_ADDRESSES[chainId],
            functionName: "markets",
            args: [id],
            scopeKey: `markets-${id.toString()}-${chainId}`,
            chainId: chainId,
        }))
    });

    const { data: terms } = useReadContracts({
        contracts: liveIndexesRaw?.map((id) => ({
            abi: BondAbi,
            address: BOND_DEPOSITORY_ADDRESSES[chainId],
            functionName: "terms",
            args: [id],
            scopeKey: `terms-${id.toString()}-${chainId}`,
            chainId: chainId,
        }))
    });

    const { data: metas } = useReadContracts({
        contracts: liveIndexesRaw?.map((id) => ({
            abi: BondAbi,
            address: BOND_DEPOSITORY_ADDRESSES[chainId],
            functionName: "metadatas",
            args: [id],
            scopeKey: `metadatas-${id.toString()}-${chainId}`,
            chainId: chainId,
        }))
    });

    const { data: marketPrices } = useReadContracts({
        contracts: liveIndexesRaw?.map((id) => ({
            abi: BondAbi,
            address: BOND_DEPOSITORY_ADDRESSES[chainId],
            functionName: "marketPrice",
            args: [id],
            scopeKey: `marketPrice-${id.toString()}-${chainId}`,
            chainId: chainId,
        }))
    });

    const { data: quotePrices } = useReadContracts({
        contracts: markets?.map((_, index) => {
            const quoteTokenDecimals = metas?.at(index).result?.at(5) ? metas.at(index).result.at(5) : 18;
            const quoteTokenAddress = markets?.at(index).result?.at(1) ? markets.at(index).result.at(1) : "";
            const one = 10n ** BigInt(quoteTokenDecimals);

            return {
                abi: TreasuryAbi,
                address: DAO_TREASURY_ADDRESSES[chainId],
                functionName: "tokenValue",
                args: [quoteTokenAddress, one],
                scopeKey: `tokenValue-${quoteTokenAddress}-${one.toString()}-${chainId}`,
                chainId: chainId,
            }
        })
    });

    const { data: markdowns } = useReadContracts({
        contracts: markets?.map((_, index) => {
            const quoteTokenAddress = markets?.at(index).result?.at(1) ? markets.at(index).result.at(1) : "";
            return {
                abi: BondingCalculatorAbi,
                address: BONDING_CALCULATOR_ADDRESSES[chainId],
                functionName: "markdown",
                args: [quoteTokenAddress],
                scopeKey: `markdown-${quoteTokenAddress}-${chainId}`,
                chainId: chainId,
            }
        })
    });

    const { symbols: quoteTokenSymbols } = useTokenSymbols(chainId, markets?.map(m => m.result?.at(1)));
    const { symbol: baseTokenSymbol } = useTokenSymbol(chainId, "FTSO");

    const liveBonds = liveIndexesRaw ? liveIndexesRaw.map((bondIndex, index) => {
        const id = Number(bondIndex);

        const marketPrice = marketPrices?.at(index).result         ? marketPrices.at(index).result  : 0n;
        const marketCapacity = markets?.at(index).result?.at(0)    ? markets.at(index).result.at(0) : 0n;
        const quoteTokenAddress = markets?.at(index).result?.at(1) ? markets.at(index).result.at(1) : "";
        const capacityInQuote = markets?.at(index).result?.at(2)   ? markets.at(index).result.at(2) : 0n;
        const marketMaxPayout = markets?.at(index).result?.at(4)   ? markets.at(index).result.at(4) : 0n;
        const quoteTokenDecimals = metas?.at(index).result?.at(5)  ? metas.at(index).result.at(5)   : 18;
        const quoteTokenPerUsd = quotePrices?.at(index).result
            ? new DecimalBigNumber(quotePrices.at(index).result * (10n ** 9n), quoteTokenDecimals)
            : new DecimalBigNumber(0n, quoteTokenDecimals);
        const markdown = markdowns?.at(index).result
            ? new DecimalBigNumber(markdowns.at(index).result, quoteTokenDecimals)
            : new DecimalBigNumber(1n, 0);
        const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : "";

        const quoteTokenPerBaseToken = new DecimalBigNumber(marketPrice, 9);
        const priceInUsd = quoteTokenPerUsd.mul(quoteTokenPerBaseToken).mul(markdown);
        const discount = baseTokenPerUsd._value > 0n
            ? baseTokenPerUsd.sub(priceInUsd).div(baseTokenPerUsd)
            : new DecimalBigNumber("0", baseTokenPerUsd._decimals);

        const capacityInBaseToken = capacityInQuote
            ? new DecimalBigNumber(marketPrice > 0n ? marketCapacity / marketPrice : 0n, 9)
            : new DecimalBigNumber(marketCapacity, 9);
        const capacityInQuoteToken = capacityInQuote
            ? new DecimalBigNumber(marketCapacity, quoteTokenDecimals)
            : new DecimalBigNumber(marketCapacity * marketPrice, quoteTokenDecimals);

        const maxPayoutInBaseToken = new DecimalBigNumber(marketMaxPayout, 9);
        const maxPayoutInQuoteToken = new DecimalBigNumber(
            marketMaxPayout * marketPrice,
            quoteTokenDecimals
        );

        const zero = new DecimalBigNumber(0n, 0);
        const bondName = `${baseTokenSymbol}/${quoteTokenSymbol}`;

        return {
            id,
            discount,
            displayName: getBondNameDisplayName(chainId, bondName, quoteTokenAddress),
            baseToken: {
                name: quoteTokenSymbol,
                purchaseUrl: getTokenPurchaseLink(chainId, ""),
                icons: ["FTSO"],
                tokenAddress: getTokenAddress(chainId, "FTSO")
            },
            quoteToken: {
                name: quoteTokenName,
                purchaseUrl: getTokenPurchaseLink(chainId, quoteTokenAddress),
                icons: getTokenIcons(chainId, quoteTokenAddress),
                decimals: quoteTokenDecimals,
                quoteTokenAddress,
            },
            duration: terms?.at(index).result?.at(3) ? terms.at(index).result.at(3) : 0,
            vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,
            isFixedTerm: terms?.at(index).result?.at(0) ? terms.at(index).result.at(0) : true,
            isSoldOut: capacityInBaseToken.eq(zero) || capacityInBaseToken.eq(zero),
            price: {
                inUsd: priceInUsd,
                inBaseToken: quoteTokenPerBaseToken,
                marketPriceInUsd: baseTokenPerUsd
            },
            capacity: {
                inBaseToken: capacityInBaseToken,
                inQuoteToken: capacityInQuoteToken,
            },
            maxPayout: {
                inBaseToken: maxPayoutInBaseToken,
                inQuoteToken: maxPayoutInQuoteToken,
            },
        };
    }).sort((a, b) => (a.id > b.id ? -1 : 1)) : [];
    return { liveBonds, refetch };
}

export const useNotes = (chainId, address) => {
    const { data: indexesFor, refetch } = useReadContract({
        abi: BondAbi,
        address: BOND_DEPOSITORY_ADDRESSES[chainId],
        functionName: "indexesFor",
        args: [address],
        scopeKey: `indexesFor-${address}-${chainId}`,
        chainId: chainId,
    });

    const { data: notesRaw } = useReadContracts({
        contracts: indexesFor?.map((id) => ({
            abi: BondAbi,
            address: BOND_DEPOSITORY_ADDRESSES[chainId],
            functionName: "notes",
            args: [address, id],
            scopeKey: `notes-${id.toString()}-${address}-${chainId}`,
            chainId: chainId,
        }))
    });

    const { data: markets } = useReadContracts({
        contracts: notesRaw?.map((note) => {
            const marketId = note.result?.at(4) ? note.result.at(4) : "";
            return {
                abi: BondAbi,
                address: BOND_DEPOSITORY_ADDRESSES[chainId],
                functionName: "markets",
                args: [marketId],
                scopeKey: `markets-${marketId.toString()}-${chainId}`,
                chainId: chainId,
            }
        }),
    });

    const { data: terms } = useReadContracts({
        contracts: notesRaw?.map((note) => {
            const marketId = note.result?.at(4) ? note.result.at(4) : "";
            return {
                abi: BondAbi,
                address: BOND_DEPOSITORY_ADDRESSES[chainId],
                functionName: "terms",
                args: [marketId],
                scopeKey: `terms-${marketId.toString()}-${chainId}`,
                chainId: chainId,
            }
        }),
    });

    const { symbols: quoteTokenSymbols } = useTokenSymbols(chainId, markets?.map(m => m.result?.at(1)));

    const notes = indexesFor ? indexesFor.map((noteIndex, index) => {
        const id = Number(noteIndex);
        const quoteTokenAddress = markets?.at(index).result?.at(1) ? markets.at(index).result.at(1)  : "";
        const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : "";

        return {
            id,
            quoteToken: {
                name: quoteTokenSymbol,
                icons: getTokenIcons(chainId, quoteTokenAddress),
            },
            vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,
            created: notesRaw?.at(index).result?.at(1) ? notesRaw.at(index).result.at(1) : 0,
            matured: notesRaw?.at(index).result?.at(2) ? notesRaw.at(index).result.at(2) : 0,
            payout: new DecimalBigNumber(
                notesRaw?.at(index).result?.at(0) ? notesRaw.at(index).result.at(0) : 0n,
                18
            ),
        }
    }) : [];

    return { notes };
}

export const purchaseBond = async (chainId, bondId, amount, maxPrice, user, referral) => {
    const args = [
        bondId,
        amount,
        maxPrice,
        user,
        referral
    ];
    const messages = {
        replacedMsg: "Bond transaction was replaced. Wait for inclusion please.",
        successMsg: `Bond successfully purchased for ${shorten(user)}! Wait until it'll mature before claim.`,
        errorMsg: "Bond transaction failed. Check logs for error detalization.",
    };
    await executeOnChainTransaction(
        chainId,
        "deposit",
        args,
        user,
        messages
    );
}

export const redeem = async (chainId, user, isGhst, indexes) => {
    const args = [
        user,
        isGhst,
        indexes
    ];
    const messages = {
        replacedMsg: "Redeem transaction was replaced. Wait for inclusion please.",
        successMsg: `Address ${shorten(user)} redeemed ${indexes.length} bonds in ${isGhst ? "GHST" : "STNK"}!`,
        errorMsg: `Redeem of ${indexes.length} bonds failed. Check logs for error detalization.`,
    };
    await executeOnChainTransaction(
        chainId,
        "redeem",
        args,
        user,
        messages
    );
}

const executeOnChainTransaction = async (
    chainId,
    functionName,
    args,
    account,
    messages
) => {
    try {
        const { request } = await simulateContract(config, {
            abi: BondAbi,
            address: BOND_DEPOSITORY_ADDRESSES[chainId],
            functionName,
            args,
            account,
            chainId
        });

        const txHash = await writeContract(config, request);
        await waitForTransactionReceipt(config, {
            hash: txHash,
            onReplaced: () => toast(messages.replacedMsg),
            chainId
        });

        toast.success(messages.successMsg);
    } catch (err) {
        console.error(err);
        toast.error(messages.errorMsg)
    }
}
