import {
    NotepadText,
    Send,
    Trash,
    Settings2,
    ArrowBigRightDash,
    ArrowBigDownDash
} from "lucide-react"
import React, { useState, useEffect, useCallback, useMemo } from "react"
import { useLocation } from "react-router-dom"
import { lastValueFrom, tap } from "rxjs"

import { ss58Decode } from "@polkadot-labs/hdkd-helpers"
import { toHex } from "@polkadot-api/utils"

import {
    useChainSpecV1,
    useSystemAccount,
    useUnstableProvider,
    useMetadata,
    useTransferCalldata,
    useExistentialDeposit
} from "../hooks"

import { submitTransaction$ } from "../api"
import {
    FollowTransaction,
    TransactionError,
    TransactionHistory,
} from "../types"

import {
    Accordion,
    AccordionContent,
    AccordionItem,
    AccordionTrigger,
} from "../components/ui/accordion"
import { Input } from "../components/ui/input"
import { Button } from "../components/ui/button"

import { Sender, Receiver } from "./Accounts"

export const Transactions = () => {
    const location = useLocation()
    const queryParams = new URLSearchParams(location.search)
    const initialReceiver = queryParams.get("address") ?? ""

    const [transactionHistory, setTransactionHistory] = useState<TransactionHistory[]>(
        JSON.parse(localStorage.getItem('transactionHistory') ?? '[]') || []
    )
    const [historyLifetimeDuration, setHistoryLifetimeDuration] = useState<number>(
        Number(localStorage.getItem("historyLifetimeDuration") ?? 259200) // default is 3 days
    )
    const [historyMaxRecords, setHistoryMaxRecords] = useState<number>(
        Number(localStorage.getItem("historyMaxRecords") ?? 5)
    )
    const [defaultTransactAmount, setDefaultTransactAmount] = useState<string>(
        localStorage.getItem("defaultTransactAmount") ?? ""
    )

    const [transactionStatus, setTransactionStatus] = useState<FollowTransaction | undefined>()
    const [error, setError] = useState<TransactionError | undefined>()
    const [isSubmittingTransaction, setIsSubmittingTransaction] = useState<boolean>(false)

    const [activeTab, onActiveTabChanged] = useState<string>("transact")
    const [receiver, setReceiver] = useState<string>(initialReceiver)
    const [amount, setAmount] = useState<string>(defaultTransactAmount)

    const metadata = useMetadata()
    const existentialDeposit = useExistentialDeposit()
    const chainSpecV1 = useChainSpecV1()
    const tokenDecimals: number = chainSpecV1?.properties?.tokenDecimals ?? 0
    const tokenSymbol: string = chainSpecV1?.properties?.tokenSymbol ?? ""

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

    const convertedTimestamp = ({ timestamp }: { timestamp: number }) => {
        const secondsInMinute = 60;
        const secondsInHour = secondsInMinute * 60;
        const secondsInDay = secondsInHour * 24;

        const days = Math.floor(timestamp / secondsInDay);
        const hours = Math.floor((timestamp % secondsInDay) / secondsInHour);
        const minutes = Math.floor((timestamp % secondsInHour) / secondsInMinute);
        const seconds = timestamp % secondsInMinute;

        return `${days} days, ${hours} hours, ${minutes} minutes, ${seconds} seconds`;
    }

    const {
        provider,
        clientFull,
        chainId,
        account,
        accounts,
        connectAccount
    } = useUnstableProvider()

    const receiverObject = useMemo(() => {
        try {
            const [, prefix] = ss58Decode(receiver)
            if (prefix !== 1995 && prefix !== 1996) {
                throw new Error("bad prefix")
            }
            return { isValid: true, address: receiver }
        } catch (e) {
            return { isValid: false, address: receiver }
        }
    }, [receiver])

    const calldata = useTransferCalldata(
        receiverObject.isValid ? receiverObject.address : undefined,
        convertedAmount > 0n ? convertedAmount : undefined
    )

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

    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}`
    }

    useEffect(() => {
        const deadline = Math.floor(Date.now() / 1000) + historyLifetimeDuration
        const cleanedTransactionHistory = transactionHistory.slice(-historyMaxRecords).filter(transaction =>
            (transaction.timestamp ?? 0) < deadline && transaction.txHash
        )
        localStorage.setItem("transactionHistory", JSON.stringify(cleanedTransactionHistory))
    }, [transactionHistory, historyLifetimeDuration, historyMaxRecords])

    useEffect(() => {
        localStorage.setItem("historyLifetimeDuration", historyLifetimeDuration.toString())
    }, [historyLifetimeDuration])

    useEffect(() => {
        localStorage.setItem("historyMaxRecords", historyMaxRecords.toString())
    }, [historyMaxRecords])

    useEffect(() => {
        localStorage.setItem("defaultTransactAmount", defaultTransactAmount)
    }, [defaultTransactAmount])

    const handleOnTransfer = useCallback(async () => {
        setIsSubmittingTransaction(true)
        setTransactionStatus(undefined)
        setError(undefined)

        const transactStory: TransactionHistory = {
            sender: account?.address ?? "",
            receiver: receiverObject?.address ?? "",
            amount,
            timestamp: Math.floor(Date.now() / 1000),
            calldata: calldata ?? "",
            tokenSymbol: tokenSymbol ?? "",
            status: "Initiated"
        }

        try {
            const tx = await provider!.createTx(
                chainId ?? "",
                account ? toHex(ss58Decode(account.address)[0]) : "",
                calldata ?? ""
            )
            await lastValueFrom(
                submitTransaction$(clientFull, tx)
                    .pipe(
                        tap(({ txEvent }) => {
                            let status: string = ""
                            switch (txEvent.type) {
                                case "broadcasted":
                                    status = "broadcasted to available peers"
                                    transactStory.status = "Broadcasted"
                                    break
                                case "txBestBlocksState":
                                    status = `included in block #${txEvent.block.number}`
                                    transactStory.blockNumber = txEvent.block.number
                                    transactStory.status = "Mined"
                                    break
                                case "finalized":
                                    status = `finalized at block #${txEvent.block.number}`
                                    transactStory.blockNumber = txEvent.block.number
                                    transactStory.status = "Finalized"
                                    break
                                case "throttled":
                                    status = "throttling to detect chain head..."
                                    transactStory.status = "Throttled"
                                    break
                            }
                            transactStory.txHash = txEvent.txHash
                            transactStory.blockHash = txEvent.block?.hash
                            setTransactionStatus({
                                status,
                                hash: txEvent.block?.hash,
                            })
                        }),
                    ),
            )
        } catch (err) {
            if (err instanceof Error) {
                const currentError = { type: "error", error: err.message }
                transactStory.error = currentError
                setError(currentError)
            }
            console.error(err)
        } finally {
            setAmount(defaultTransactAmount ?? "")
            setIsSubmittingTransaction(false)
            setTransactionHistory([...transactionHistory, transactStory])
        }
    }, [
        provider,
        chainId,
        account,
        amount,
        calldata,
        clientFull,
        defaultTransactAmount,
        receiverObject?.address,
        tokenSymbol,
        transactionHistory
    ])

    return (
        <div className="sm:w-[500px] w-[85%] h-fit flex flex-col flex-1 gap-2 justify-center self-center rounded py-2">
            <div className="bg-muted p-4 rounded flex flex-col gap-2">
                <div className="w-full flex flex-row justify-between">
                    <div className="flex sm:flex-row flex-col gap-2">
                        <Button
                            type="button"
                            variant="ghost"
                            disabled={isSubmittingTransaction}
                            onClick={() => onActiveTabChanged("transact")}
                            className={`text-sm p-4 ${activeTab === "transact" ? "font-semibold underline" : ""}`}
                        >
                            <Send className="w-4 h-4 inline-block mr-2" />
                            Transact
                        </Button>
                        <Button
                            type="button"
                            variant="ghost"
                            disabled={isSubmittingTransaction}
                            onClick={() => onActiveTabChanged("history")}
                            className={`text-sm p-4 ${activeTab === "history" ? "font-semibold underline" : ""}`}
                        >
                            <NotepadText className="w-4 h-4 inline-block mr-2" />
                            History
                        </Button>
                    </div>
                    <Button
                        type="button"
                        variant="ghost"
                        disabled={isSubmittingTransaction}
                        onClick={() => onActiveTabChanged("settings")}
                        className="sm:block hidden"
                    >
                        <Settings2 className="w-4 h-4 inline-block" />
                    </Button>
                </div>
                <hr className="w-full my-2" />
                <div className="flex flex-col gap-2">
                    {activeTab === "transact" && (
                        <>
                            <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}
                            />
                            <Receiver
                                receiver={receiver}
                                receiverAccount={receiverAccount}
                                amount={amount}
                                tokenDecimals={tokenDecimals}
                                tokenSymbol={tokenSymbol}
                                isSubmittingTransaction={isSubmittingTransaction}
                                setReceiver={setReceiver}
                                setAmount={setAmount}
                                applyDecimals={applyDecimals}
                            />
                            <Button
                                onClick={handleOnTransfer}
                                disabled={
                                    isSubmittingTransaction || !provider || !senderAccount ||
                                    !receiverObject.isValid || !tokenDecimals || !calldata ||
                                    !existentialDeposit || convertedAmount < existentialDeposit ||
                                    !metadata || senderAccount.data.free < convertedAmount
                                }
                                className="mt-4"
                                variant="secondary"
                                size="full"
                            >
                                {isSubmittingTransaction ? "Submitting" : "Transfer"}
                            </Button>
                            {!error && transactionStatus && (
                                <div className="flex flex-col">
                                    <p className="text-xs text-accent overflow-hidden whitespace-nowrap text-ellipsis">
                                        Transaction status: {`${transactionStatus.status}`}
                                    </p>
                                    {transactionStatus.hash && (<p className="text-xs text-accent overflow-hidden whitespace-nowrap text-ellipsis">
                                        {transactionStatus.hash}
                                    </p>)}
                                </div>

                            )}
                            {!error && !metadata && (
                                <p className="text-xs text-accent overflow-hidden whitespace-nowrap text-ellipsis">
                                    Downloading chain metadata...
                                </p>
                            )}
                            {error && (
                                <p className="text-xs text-destructive overflow-hidden whitespace-nowrap text-ellipsis">
                                    Error: {error.error}
                                </p>
                            )}
                        </>
                    )}
                    {activeTab === "history" && (
                        <div className="min-h-[248px]">
                            {transactionHistory.length === 0
                                ? (
                                    <div className="text-sm">
                                        There are currently no stored transactions.
                                    </div>
                                )
                                : (
                                    <Accordion type="multiple" className="w-full h-fit flex flex-col flex-1 gap-4 justify-center self-center py-2">
                                        {transactionHistory.map((props: TransactionHistory) => (
                                            <AccordionItem key={props.txHash} className="bg-muted rounded px-4" value={props.txHash ?? ""}>
                                                <AccordionTrigger className="w-full hover:no-underline">
                                                    <div className="flex sm:flex-row flex-col gap-2 items-center w-[90%] justify-between cursor-pointer">
                                                        <Input
                                                            readOnly
                                                            className="overflow-hidden whitespace-nowrap text-ellipsis sm:w-[45%] w-full"
                                                            value={props.sender}
                                                            placeholder="Sender not found"
                                                        />
                                                        <ArrowBigRightDash className="w-6 h-6 sm:block hidden" />
                                                        <ArrowBigDownDash className="w-6 h-6 sm:hidden block" />
                                                        <Input
                                                            readOnly
                                                            className="overflow-hidden whitespace-nowrap text-ellipsis sm:w-[45%] w-full"
                                                            value={props.receiver}
                                                            placeholder="Receiver not found"
                                                        />
                                                    </div>
                                                </AccordionTrigger>
                                                <AccordionContent className="flex flex-col gap-4">
                                                    <div className="text-sm text-accent flex flex-col">
                                                        {props.error && (
                                                            <span className="overflow-hidden whitespace-nowrap text-ellipsis text-destructive">Error: {props.error?.error ?? ""}</span>
                                                        )}
                                                        {!props.error && (
                                                            <>
                                                                <span className="overflow-hidden whitespace-nowrap text-ellipsis">{props.status} at block #{props.blockNumber}</span>
                                                                <span className="overflow-hidden whitespace-nowrap text-ellipsis">Transfered amount: {props.amount} {props.tokenSymbol}</span>
                                                            </>
                                                        )}
                                                        <span className="overflow-hidden whitespace-nowrap text-ellipsis">
                                                            Execution datetime: {new Date(props.timestamp * 1000).toLocaleString("en-US")}
                                                        </span>
                                                        <hr className="my-4" />
                                                        <div className="flex flex-col gap-2">
                                                            <div className="flex sm:flex-row flex-col justify-between sm:items-center items-left gap-2">
                                                                Tx hash:
                                                                <Input
                                                                    readOnly
                                                                    value={props.txHash}
                                                                    className="text-accent sm:w-[350px] w-full h-[30px]"
                                                                />
                                                            </div>
                                                            <div className="flex sm:flex-row flex-col justify-between sm:items-center items-left gap-2">
                                                                Block hash:
                                                                <Input
                                                                    readOnly
                                                                    value={props.blockHash}
                                                                    className="text-accent sm:w-[350px] w-full h-[30px]"
                                                                />
                                                            </div>
                                                            <div className="flex sm:flex-row flex-col justify-between sm:items-center items-left gap-2">
                                                                Calldata:
                                                                <Input
                                                                    readOnly
                                                                    value={props.calldata}
                                                                    className="text-accent sm:w-[350px] w-full h-[30px]"
                                                                />
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <Button
                                                        variant="destructive"
                                                        size="full"
                                                        className="h-[35px] text-accent"
                                                        onClick={() => {
                                                            setTransactionHistory(transactionHistory.filter((obj: TransactionHistory) =>
                                                                obj.txHash !== props.txHash
                                                            ))
                                                        }}
                                                    >
                                                        <Trash className="w-4 h-4 mr-2" />
                                                        Remove
                                                    </Button>
                                                </AccordionContent>
                                            </AccordionItem>
                                        ))}
                                    </Accordion>
                                )
                            }
                        </div>
                    )}
                    {activeTab === "settings" && (
                        <div className="min-h-[248px] text-sm flex flex-col gap-2">
                            <div className="flex sm:flex-row flex-col gap-2 justify-between sm:items-center items-left">
                                <span className="sm:pb-4 pb-0">Max lifetime:</span>
                                <div className="sm:w-[350px] w-full flex flex-col">
                                    <Input
                                        value={historyLifetimeDuration}
                                        onChange={(e) => {
                                            const newValue = +e.target.value
                                            if (newValue) setHistoryLifetimeDuration(newValue)
                                        }}
                                        className="sm:w-[350px] w-full"
                                    />
                                    <span className="text-xs text-accent my-2">
                                        {convertedTimestamp({ timestamp: historyLifetimeDuration})}
                                    </span>
                                </div>
                            </div>
                            <div className="flex sm:flex-row flex-col gap-2 justify-between sm:items-center items-left">
                                <span>Max records:</span>
                                <Input
                                    value={historyMaxRecords}
                                    onChange={(e) => {
                                        const newValue = +e.target.value
                                        if (newValue) setHistoryMaxRecords(newValue)
                                    }}
                                    className="sm:w-[350px] w-full"
                                />
                            </div>
                            <div className="flex sm:flex-row flex-col gap-2 justify-between sm:items-center items-left">
                                <span>Default amount:</span>
                                <Input
                                    value={defaultTransactAmount}
                                    onChange={(e) => {
                                        setDefaultTransactAmount(e.target.value)
                                        setAmount(e.target.value)
                                    }}
                                    className="sm:w-[350px] w-full"
                                    placeholder="Amount will be empty"
                                />
                            </div>
                        </div>
                    )}
                </div>
            </div>
        </div>
    )
}
