import React, { useEffect, useState, useMemo } from "react"
import { Nut, NutOff, Users, PiggyBank, RefreshCcw } from "lucide-react"
import { ss58Decode } from "@polkadot-labs/hdkd-helpers"

import {
    Select,
    SelectValue,
    SelectTrigger,
    SelectContent,
    SelectGroup,
    SelectItem,
} from "../components/ui/select"
import {
    Accordion,
    AccordionContent,
    AccordionItem,
    AccordionTrigger,
} from "../components/ui/accordion"
import { Checkbox } from "../components/ui/checkbox"
import { Input } from "../components/ui/input"
import { Button } from "../components/ui/button"

import {
    useChainSpecV1,
    useEraIndex,
    useNominations,
    useLedger,
    usePayee,
    useSlasingSpans,
    useEraRewardPoints,
    useCurrentValidators,
    useValidatorsOverview,
    useBondedAddress,
    useNominateCalldata,
    useWithdrawCalldata,
    usePayeeCalldata,
    useSystemAccount,
    useBondCalldata,
    useUnbondCalldata,
    useBondExtraCalldata,
    useUnstableProvider,
    useTransactionStatusProvider,
    RewardPoints,
    Unlocking,
    RewardDestination,
} from "../hooks"

import { AddressBookRecord } from "../types"
import { Sender } from "./Accounts"
import { Row } from "./Row"

interface ItemProps {
    name: string | undefined
    address: string
    symbol: string
    points: number
    commission: number
    nominatorCount: number
    decimals: number
    totalStake: bigint
    ownStake: bigint
    blocked: boolean
    nominated: boolean
    checkedAddresses: string[]
    setIsCheckedAddresses: React.Dispatch<React.SetStateAction<string[]>>
}

const Item: React.FC<ItemProps> = (props) => {
    const {
        name,
        address,
        points,
        commission,
        blocked,
        nominatorCount,
        totalStake,
        ownStake,
        decimals,
        symbol,
        nominated,
        checkedAddresses,
        setIsCheckedAddresses,
    } = props

    const [copied, setCopied] = useState<boolean>(false)
    const convertToFixed = (value: bigint, decimals: number) => {
        if (!value || !decimals) {
            return parseFloat("0").toFixed(5)
        }
        const number = Number(value) / Math.pow(10, decimals)
        return parseFloat(number.toString()).toFixed(5)
    }

    const handleCopy = (textToCopy: string) => {
        if (!textToCopy) return
        navigator.clipboard.writeText(textToCopy).then(() => {
            setCopied(true)
            setTimeout(() => setCopied(false), 2000)
        })
    }

    const handleOnCheck = () => {
        setIsCheckedAddresses((prev: string[]) => {
            if (address && prev.includes(address ?? "")) {
                return prev.filter(item => item !== address)
            } else {
                return [...prev, address]
            }
        })
    }

    return (
        <AccordionItem className="bg-muted rounded px-4 flex flew-row items-center justify-between" value={address}>
            <Checkbox checked={checkedAddresses.includes(address)} onCheckedChange={handleOnCheck} />
            <div>
                <AccordionTrigger>
                    <div className="w-100 flex flex-row items-center justify-start gap-4 space-x-2 cursor-pointer">
                        <div className={`w-[300px] overflow-hidden whitespace-nowrap text-ellipsis text-left ${nominated ? "text-foreground" : ""}`}>
                            {name ?? (address.slice(0, 15) + "..." + address.slice(-15))}
                        </div>
                        <div>{points}</div>
                    </div>
                </AccordionTrigger>
                <AccordionContent className="flex flex-col gap-2">
                    <hr className="my-4" />
                    <Row title="Commission" element={<Input
                        readOnly
                        aria-label="Validator Commission"
                        type="text"
                        className="sm:w-[320px] w-full"
                        placeholder={`${(commission / 10000000).toFixed(2)}%`}
                    />} />
                    <Row title="Nominators" element={<Input
                        readOnly
                        aria-label="Validator Nominators"
                        type="text"
                        className="sm:w-[320px] w-full"
                        placeholder={nominatorCount.toString()}
                    />} />
                    <Row title="Status" element={<Input
                        readOnly
                        aria-label="Validator Status"
                        type="text"
                        className="sm:w-[320px] w-full"
                        placeholder={blocked ? "Blocked" : "Available"}
                    />} />
                    <hr className="my-4" />
                    <Row title="Total Stake" element={<Input
                        readOnly
                        aria-label="Validator Total Stake"
                        type="text"
                        className="sm:w-[320px] w-full"
                        placeholder={`${convertToFixed(totalStake, decimals)} ${symbol}`}
                    />} />
                    <Row title="Own Stake" element={<Input
                        readOnly
                        aria-label="Validator Own Stake"
                        type="text"
                        className="sm:w-[320px] w-full"
                        placeholder={`${convertToFixed(ownStake, decimals)} ${symbol}`}
                    />} />
                    <hr className="my-4" />
                    <div
                        onClick={() => handleCopy(address)}
                        className="flex justify-center items-center cursor-pointer hover:text-foreground"
                    >
                        {copied ? "Address copid to clipboard" : address}
                    </div>
                </AccordionContent>
            </div>
        </AccordionItem>
    )
}

const HeaderInfo = ({ text, value }: { text: string, value: string }) => {
    return (
        <div className="w-[40%] flex flex-col justify-center items-center">
            <span>{text}</span>
            <span>{value}</span>
        </div>
    )
}

export const Nominations = () => {
    const addressBook: AddressBookRecord[] = JSON.parse(localStorage.getItem('addressBook') ?? '[]')
    const [checkedAddresses, setIsCheckedAddresses] = useState<string[]>([])

    const [interestingValidator, setInterestingValidator] = useState<string | undefined>(undefined)
    const [amount, setAmount] = useState<string>("")
    const [destinationReceiver, setDestinationReceiver] = useState<string>("")
    const [expectedPayee, setExpectedPayee] = useState<string>("")

    const {
        setTransactionStatus,
        setError,
        isSubmittingTransaction,
        handleTransaction,
        renderStatus,
    } = useTransactionStatusProvider()
    const { account, accounts, connectAccount } = useUnstableProvider()

    const chainSpecV1 = useChainSpecV1()
    const eraIndex = useEraIndex()
    const nominations = useNominations({ address: account?.address })
    const ledger = useLedger({ address: account?.address })
    const payee = usePayee({ address: account?.address })
    const slashingSpans = useSlasingSpans({ address: account?.address })
    const eraRewardPoints = useEraRewardPoints({ eraIndex: eraIndex?.index })
    const currentValidators = useCurrentValidators({ address: interestingValidator })
    const validatorOverview = useValidatorsOverview({ eraIndex: eraIndex?.index, address: interestingValidator })

    console.log(eraRewardPoints)

    const tokenDecimals: number = chainSpecV1?.properties?.tokenDecimals ?? 0
    const tokenSymbol: string = chainSpecV1?.properties?.tokenSymbol ?? ""
    const ss58Format: number = chainSpecV1?.properties?.ss58Format ?? 1995

    const senderAccount = useSystemAccount({
        account: account
            ? account.address
            : undefined
    })

    const destinationReceiverIsValid = useMemo(() => {
        try {
            const [, prefix] = ss58Decode(destinationReceiver)
            if (prefix !== ss58Format) {
                throw new Error("bad prefix")
            }
            return true
        } catch {
            return false
        }
    }, [destinationReceiver, ss58Format])

    const convertedAmount = useMemo(() => {
        try {
            return BigInt(Number(amount) * Math.pow(10, tokenDecimals))
        } catch {
            return 0n
        }
    }, [amount, tokenDecimals])

    const bondedAddress = useBondedAddress({ address: account?.address })

    const payeeCalldata = usePayeeCalldata(expectedPayee, destinationReceiver)
    const withdrawCalldata = useWithdrawCalldata((slashingSpans?.prior?.length ?? 0) + 1)
    const nominateCalldata = useNominateCalldata(checkedAddresses)
    const bondExtraCalldata = useBondExtraCalldata(
        convertedAmount > 0n ? convertedAmount : undefined
    )
    const bondCalldata = useBondCalldata(
        convertedAmount > 0n ? convertedAmount : undefined
    )
    const unbondCalldata = useUnbondCalldata(
        convertedAmount > 0n ? convertedAmount : undefined
    )

    const payeeDescription = (destination: string | undefined) => {
        let description = "Unknown reward destination"
        switch (destination) {
            case "Staked":
                description = "Re-stake upcoming rewards"
                break
            case "Stash":
                description = "Withdraw rewards to free"
                break
            case "Account":
                description = `Rewards to account`
                break
            case "None":
                description = "Refuse to receive rewards"
                break
        }
        return description
    }

    const readyToWithdraw = useMemo(() => {
        return ledger?.unlocking.reduce((acc: bigint, item: Unlocking) => {
            if ((eraIndex?.index ?? 0) >= item.era) {
                return item.value
            }
            return 0n
        }, 0n)
    }, [ledger, eraIndex])

    const waitingForWithdraw = useMemo(() => {
        return ledger?.unlocking.reduce((acc: bigint, item: Unlocking) => {
            if ((eraIndex?.index ?? 0) < item.era) {
                return item.value
            }
            return 0n
        }, 0n)
    }, [ledger, eraIndex])

    const latestWithdrawEra = useMemo(() => {
        if (!ledger || ledger.unlocking.length === 0) {
            return 0
        }
        const index = eraIndex?.index ?? 0
        return Math.max(...ledger.unlocking.map((el: Unlocking) => el.era - index), 0)
    }, [eraIndex, ledger])

    useEffect(() => {
        setIsCheckedAddresses([])
    }, [account])

    useEffect(() => {
        if (expectedPayee === "") setExpectedPayee(payee?.type ?? "")
    }, [expectedPayee, setExpectedPayee, payee])

    useEffect(() => {
        if (checkedAddresses.length === 0 && nominations) {
            setIsCheckedAddresses(nominations?.targets ?? [])
        }
    }, [checkedAddresses, nominations])

    useEffect(() => {
        if (amount !== "") {
            setError(undefined)
            setTransactionStatus(undefined)
        }
    }, [amount, setError, setTransactionStatus])

    useEffect(() => {
        if (!isSubmittingTransaction) {
            setAmount("")
        }
    }, [isSubmittingTransaction])

    useEffect(() => {
        setDestinationReceiver("")
    }, [expectedPayee])

    const applyDecimals = (value = 0n, decimals = 0, tokenSymbol = "CSPR") => {
        if (!value) return `0 ${tokenSymbol}`
        const numberValue = Number(value) / Math.pow(10, decimals)
        const formatter = new Intl.NumberFormat("en-US", {
            minimumFractionDigits: 6,
            maximumFractionDigits: 6,
        })
        return `${formatter.format(numberValue)} ${tokenSymbol}`
    }

    const handleOnBond = () => handleTransaction({ calldata: bondCalldata, txName: "bond" })
    const handleOnExtraBond = () => handleTransaction({ calldata: bondExtraCalldata, txName: "bond_extra" })
    const handleOnUnbond = () => handleTransaction({ calldata: unbondCalldata, txName: "unbond" })
    const handleOnNominate = () => handleTransaction({ calldata: nominateCalldata, txName: "nominate" })
    const handleOnWithdraw = () => handleTransaction({ calldata: withdrawCalldata, txName: "withdraw" })
    const handleOnSetPayee = () => handleTransaction({ calldata: payeeCalldata, txName: "set_payee" })

    return (
        <div className="sm:w-[500px] w-[85%] h-fit flex flex-col flex-1 gap-8 justify-center self-center rounded py-2">
            <div className="bg-muted p-4 rounded flex flex-col gap-4">
                <div className="flex flex-row justify-between items-center gap-2">
                    <HeaderInfo text="Current Era" value={`#${eraIndex?.index.toString() ?? "..."}`} />
                    <HeaderInfo text="Total Points" value={eraRewardPoints?.total.toString() ?? "..."} />
                    {nominations && (
                        <HeaderInfo text="Submitted at" value={`#${nominations?.submitted_in.toString() ?? "..."}`} />
                    )}
                </div>
                <hr />
                <Sender
                    account={account?.address ?? ""}
                    accounts={accounts?.map(acc => acc?.address ?? "") ?? []}
                    senderAccount={senderAccount}
                    senderBalance={applyDecimals(senderAccount?.data.free ?? 0n, tokenDecimals, tokenSymbol)}
                    tokenDecimals={tokenDecimals}
                    tokenSymbol={tokenSymbol}
                    connectAccount={connectAccount}
                    applyDecimals={applyDecimals}
                />
                {ledger && ledger.total > 0n && (<Accordion type="multiple" className="w-full flex flex-col gap-4 mb-4">
                    <AccordionItem className="bg-muted rounded text-sm" value="Bonding Details">
                        <AccordionTrigger>
                            <div className="flex items-center gap-2 space-x-2 cursor-pointer">
                                Bonding Details
                            </div>
                        </AccordionTrigger>
                        <AccordionContent className="flex flex-col gap-2 pl-8">
                            {payee && (<Row title="Destination" element={<Select
                                value={expectedPayee}
                                onValueChange={(payeeType) => setExpectedPayee(payeeType)}
                            >
                                <SelectTrigger
                                    className={"text-muted-foreground sm:w-[300px] w-full"}
                                    data-testid="destination-select"
                                >
                                    <SelectValue placeholder="Select Destination" />
                                </SelectTrigger>
                                <SelectContent>
                                    <SelectGroup>
                                        {Object.keys(RewardDestination()).map((destinationType, index) => (
                                            <SelectItem
                                                key={index}
                                                data-testid={`destination-${destinationType}`}
                                                value={destinationType}
                                            >
                                                {payeeDescription(destinationType)}
                                            </SelectItem>
                                        ))}
                                    </SelectGroup>
                                </SelectContent>
                            </Select>
                            } />)}
                            {payee?.type === "Account" && (
                                <Row title="" element={<Input
                                    readOnly
                                    aria-label="Destination Account Address"
                                    type="text"
                                    className="sm:w-[300px] w-full"
                                    placeholder={payee?.value}
                                />} />
                            )}
                            {expectedPayee !== payee?.type && expectedPayee === "Account" && (
                                <Row title="Receiver" element={<Input
                                    aria-label="Destination Account"
                                    type="text"
                                    className="sm:w-[300px] w-full"
                                    placeholder="Input destination account"
                                    onChange={e => setDestinationReceiver(e.target.value)}
                                    value={destinationReceiver}
                                />} />
                            )}
                            {expectedPayee !== payee?.type && (
                                <Button
                                    type="button"
                                    variant="secondary"
                                    className="text-sm my-4 w-full"
                                    onClick={handleOnSetPayee}
                                    disabled={
                                        isSubmittingTransaction
                                        || !payeeCalldata
                                        || (expectedPayee === "Account" && !destinationReceiverIsValid)
                                    }
                                >
                                    <RefreshCcw className="w-4 h-4 inline-block mr-2" />
                                    Change Destination
                                </Button>
                            )}
                            <Row title="Total Bond" element={<Input
                                readOnly
                                aria-label="Total Bond"
                                type="text"
                                className="sm:w-[300px] w-full"
                                placeholder={applyDecimals(ledger?.total, tokenDecimals, tokenSymbol)}
                            />} />
                            <Row title="Active Bond" element={<Input
                                readOnly
                                aria-label="Active Bond"
                                type="text"
                                className="sm:w-[300px] w-full"
                                placeholder={applyDecimals(ledger?.active, tokenDecimals, tokenSymbol)}
                            />} />
                            {ledger && ledger.unlocking.length > 0 && (
                                <>
                                    <hr className="my-2" />
                                    <Row title="Ready" element={<Input
                                        readOnly
                                        aria-label="Ready"
                                        type="text"
                                        className="sm:w-[300px] w-full"
                                        placeholder={applyDecimals(readyToWithdraw, tokenDecimals, tokenSymbol)}
                                    />} />
                                    {latestWithdrawEra > 0 && (<Row title={`After ${latestWithdrawEra} era${latestWithdrawEra > 1 ? "s" : ""}`} element={<Input
                                        readOnly
                                        aria-label="Pending"
                                        type="text"
                                        className="sm:w-[300px] w-full"
                                        placeholder={applyDecimals(waitingForWithdraw, tokenDecimals, tokenSymbol)}
                                    />} />)}
                                    <Button
                                        type="button"
                                        variant="secondary"
                                        className="text-sm my-4 w-full"
                                        onClick={handleOnWithdraw}
                                        disabled={isSubmittingTransaction || readyToWithdraw === 0n}
                                    >
                                        <PiggyBank className="w-4 h-4 inline-block mr-2" />
                                        Withdraw
                                    </Button>
                                </>
                            )}
                        </AccordionContent>
                    </AccordionItem>
                </Accordion>)}
                <Input
                    value={amount}
                    onChange={e => setAmount(e.target.value)}
                    disabled={isSubmittingTransaction}
                    aria-label="Transfer Amount"
                    type="text"
                    className="w-full"
                    placeholder="Input amount to bond or unbond"
                />
                <div className="flex justify-between gap-2">
                    <Button
                        type="button"
                        variant="secondary"
                        className="text-sm p-4 w-full"
                        onClick={bondedAddress ? handleOnExtraBond : handleOnBond}
                        disabled={isSubmittingTransaction || convertedAmount === 0n}
                    >
                        <Nut className="w-4 h-4 inline-block mr-2" />
                        Bond
                    </Button>
                    <Button
                        type="button"
                        variant="secondary"
                        className="text-sm p-4 w-full"
                        onClick={handleOnUnbond}
                        disabled={isSubmittingTransaction || convertedAmount === 0n}
                    >
                        <NutOff className="w-4 h-4 inline-block mr-2" />
                        Unbond
                    </Button>
                </div>
                <div>
                    {bondedAddress && (
                        <Button
                            type="button"
                            variant="secondary"
                            className="text-sm p-4 w-full"
                            onClick={handleOnNominate}
                            disabled={isSubmittingTransaction || checkedAddresses.length === 0}
                        >
                            <Users className="w-4 h-4 inline-block mr-2" />
                            Nominate
                        </Button>
                    )}
                </div>
                {eraRewardPoints?.individual.some((indivial: RewardPoints) => indivial?.at(0) === account?.address) && (
                    <p className="text-xs text-destructive">
                        You are attempting to use the nomination functionality from the current validator account, which is mutually exclusive. Please switch accounts or proceed at your own risk.
                    </p>
                )}
                {renderStatus()}
            </div>
            {eraRewardPoints && (
                <Accordion collapsible onValueChange={setInterestingValidator} type="single" className="sm:w-[500px] w-[85%] h-fit flex flex-col flex-1 gap-4 justify-center self-center sm:text-base text-xs">
                    {eraRewardPoints?.individual.map((indivial: RewardPoints, idx: number) => (
                        <Item
                            key={idx}
                            name={addressBook?.find((record: AddressBookRecord) => record.address === indivial.at(0))?.name}
                            address={indivial.at(0) as string ?? ""}
                            points={indivial.at(1) as number ?? 0}
                            commission={currentValidators?.commission ?? 0}
                            blocked={currentValidators?.blocked ?? false}
                            totalStake={validatorOverview?.total ?? 0n}
                            ownStake={validatorOverview?.own ?? 0n}
                            nominatorCount={validatorOverview?.nominator_count ?? 0}
                            decimals={tokenDecimals ?? 18}
                            symbol={tokenSymbol ?? "CSPR"}
                            setIsCheckedAddresses={setIsCheckedAddresses}
                            checkedAddresses={checkedAddresses}
                            nominated={nominations?.targets.includes(indivial.at(0)) ?? false}
                        />
                    ))}
                </Accordion>
            )}
            {!eraRewardPoints && (
                <div className="flex justify-center items-center">
                    Waiting for validators list...
                </div>
            )}
        </div>
    )
}
