import { useEffect, useState } from "react"
import { MdDeleteOutline } from "react-icons/md"
import * as environment from "@/environment"

import "./Bootnodes.css"
import { Title, Switch } from "."
import { helper } from "@substrate/light-client-extension-helpers/extension-page"
import { wellKnownGenesisHashByChainId, networks } from "@/constants"

import {
    Select,
    SelectValue,
    SelectTrigger,
    SelectContent,
    SelectGroup,
    SelectItem,
} from "@/components/ui/select"

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"

interface BootnodesType {
    checked: boolean
    bootnode: string
}

const getBootNodes = async (chainId: string) =>
    (await helper.getChains()).find(
        ({ genesisHash }) => genesisHash === wellKnownGenesisHashByChainId[chainId],
    )?.bootNodes ?? []

const setBootNodes = async (chainId: string, bootNodes: string[]) =>
    helper.setBootNodes(wellKnownGenesisHashByChainId[chainId], bootNodes)

// Add to localstorage the given bootnode for the given chain
const saveToLocalStorage = async (
    chainName: string,
    bootnode: string,
    add: boolean,
    def: string[],
) => {
    if (def.length === 0) throw new Error("Default Bootnodes should exist.")
    let res: string[]
    const chainBootnodes = await getBootNodes(chainName)
    res = chainBootnodes && Object.keys(chainBootnodes).length > 0
        ? [...chainBootnodes]
        : [...def]
    add ? res.push(bootnode) : res.splice(res.indexOf(bootnode), 1)
    await setBootNodes(chainName, res)
}

export const Bootnodes = () => {
    const [selectedChain, setSelectedChain] = useState<string>(networks[0].value)
    const [defaultBn, setDefaultBn] = useState<BootnodesType[]>([])
    const [customBn, setCustomBn] = useState<BootnodesType[]>([])
    const [customBnInput, setCustomBnInput] = useState<string>("")
    const [selectedChainDefaultBn, setSelectedChainDefaultBn] = useState<string[]>([])

    const [addMessage, setAddMessage] = useState<any>(undefined)
    const [bootnodeMsgClass, setBootnodeMsgClass] = useState<string>()

    useEffect(() => {
        if (addMessage && !addMessage?.error) {
            setBootnodeMsgClass("pb-2 text-accent")
            setCustomBnInput("")
        } else {
            setBootnodeMsgClass("pb-2 text-destructive")
        }
    }, [addMessage])

    useEffect(() => {
        Promise.all([
            getBootNodes(selectedChain),
            environment.getDefaultBootnodes(selectedChain),
        ]).then(([bootnodes, defaultBootnodes]) => {
            console.assert(defaultBootnodes, `Invalid chain name: ${selectedChain}`)
            defaultBootnodes ??= []
            setSelectedChainDefaultBn(defaultBootnodes)
            const tmpDef: BootnodesType[] = []
            const tmpCust: BootnodesType[] = []
            // When bootnodes do not exist assign and save the local ones
            if (!bootnodes?.length) {
                setBootNodes(selectedChain, defaultBootnodes)
                defaultBootnodes.forEach((b) => {
                    tmpDef.push({ bootnode: b, checked: true })
                })
            } else {
                bootnodes?.forEach((b) => {
                    defaultBootnodes?.length && defaultBootnodes?.includes(b)
                        ? tmpDef.push({ bootnode: b, checked: true })
                        : tmpCust.push({ bootnode: b, checked: true })
                })
            }
            setDefaultBn(tmpDef)
            setCustomBn(tmpCust)
        })
    }, [selectedChain])

    const checkMultiAddr = (addr: string) => {
        const ws = /\/(ip4|ip6|dns4|dns6|dns)\/([a-zA-Z0-9.-]+)\/tcp\/[0-9]{1,5}(\/(ws|wss|tls\/ws))?\/p2p\/[a-zA-Z1-9^Il0O]+/i
        const webrtc = /\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc\/certhash\/(.*?)\/p2p\/[a-zA-Z1-9^Il0O]+/i

        if (!ws.test(addr) && !webrtc.test(addr))
        throw new Error("Provided multiaddress is not correct.")
    }

    const alterBootnodes = async (
        bootnode: string,
        add: boolean,
        defaultBootnode: boolean,
    ) => {
        // if bootnode belongs to the list (default) then it does not need to be validated as it
        // comes from the chainspecs. It can be saved to the local storage at once.
        try {
            if (!defaultBootnode) {
                // verify bootnode validity
                checkMultiAddr(customBnInput)
            }
            // Check if bootnode already exists in the default and custom lists
            if (
                selectedChainDefaultBn?.includes(customBnInput) ||
                customBn.map((c) => c.bootnode).includes(customBnInput)
            ) {
                setAddMessage({ error: true, message: "Bootnode already exists in the list." })
            } else {
                await saveToLocalStorage(
                    selectedChain,
                    bootnode,
                    add,
                    selectedChainDefaultBn,
                )
            }
            const tmp = defaultBootnode ? [...defaultBn] : [...customBn]
            const i = tmp.findIndex((b) => b.bootnode === bootnode)
            if (i !== -1) {
                tmp[i].checked = add
            } else {
                tmp.push({ bootnode, checked: true })
            }
            defaultBootnode ? setDefaultBn(tmp) : setCustomBn(tmp)
            setCustomBnInput("")
        } catch (err) {
            setAddMessage({ error: true, message: (err as Error).message.replace(/^\w/, (c) => c.toUpperCase()) })
        }
    }

    return (
        <section className="container">
            <h2 className="pb-2 space-y-2 text-3xl font-semibold md:pb-4">
                Networks
            </h2>
            <div className="p-4 bg-muted rounded">
                <Select
                    onValueChange={(networkName) => {
                        setSelectedChain(networkName)
                        setCustomBnInput("")
                        setAddMessage(undefined)
                    }}
                    name={selectedChain}
                    value={selectedChain}
                >
                    <SelectTrigger className="sm:w-[250px] w-[100%]" data-testid="scheme-select">
                        <SelectValue placeholder="Select Network" />
                    </SelectTrigger>
                    <SelectContent className="sm:w-[250px] w-[100%]">
                        <SelectGroup>
                            {networks.map(network => (
                                <SelectItem
                                    key={network.chainId}
                                    data-testid={`scheme-${network.value}`}
                                    value={network.value}
                                >
                                    {network.label}
                                </SelectItem>
                            ))}
                        </SelectGroup>
                    </SelectContent>
                </Select>

                <Title className="mt-8">Bootnodes</Title>
                <Title titleType="small">Default</Title>
                <div className="mb-8">
                    {selectedChainDefaultBn?.map((bn) => (
                        <div className="flex items-center mb-2 leading-4 wrap">
                            <div className="sm:w-11/12 w-1/2 overflow-hidden text-ellipsis whitespace-nowrap">
                                {bn}
                            </div>
                            <Switch
                                className="w-1/2 sm:w-1/12"
                                bootnode={bn}
                                alterBootnodes={alterBootnodes}
                                defaultBootnode={true}
                                isChecked={defaultBn.map((d) => d.bootnode).includes(bn)}
                            />
                        </div>
                    ))}
                </div>
                <Title titleType="small" className="text-accent">Custom</Title>
                <div className="mb-8">
                    {customBn.map((c) => (
                        <div className="flex items-center mb-2 leading-4">
                            <div className="sm:w-11/12 w-1/2 overflow-hidden text-ellipsis whitespace-nowrap">
                                {c.bootnode}
                            </div>
                            <div className="flex w-1/2 sm:w-1/12 ml-8">
                                <Button
                                    variant="secondary"
                                    className="flex items-center text-white h-6 bg-red-600 rounded-full hover:bg-red-700"
                                    onClick={async () => {
                                        try {
                                            await saveToLocalStorage(
                                                selectedChain,
                                                c.bootnode,
                                                false,
                                                selectedChainDefaultBn,
                                            )
                                        } catch (e) {
                                            console.log(e)
                                        }
                                        setCustomBn(customBn.filter((f) => f.bootnode !== c.bootnode))
                                    }}
                                >
                                    <MdDeleteOutline className="text-lg" />
                                </Button>
                            </div>
                        </div>
                    ))}
                </div>
                <Title>Add custom Bootnode</Title>
                <div className="flex flex-col">
                    <div className="flex sm:flex-row flex-col gap-6 justify-between mb-4">
                        <Input
                            type="text"
                            placeholder="Enter bootnode address"
                            className="sm:w-5/6 w-full"
                            value={customBnInput}
                            onChange={(v) => {
                                addMessage && setAddMessage(undefined)
                                setCustomBnInput(v.target.value)
                            }}
                        />
                        <Button
                            variant="default"
                            size="full"
                            className="sm:w-1/6 w-full"
                            disabled={!customBnInput}
                            onClick={() => {
                                if (
                                    selectedChainDefaultBn?.includes(customBnInput) ||
                                    customBn.map((c) => c.bootnode).includes(customBnInput)
                                ) {
                                    setAddMessage({
                                        error: true,
                                        message: "Bootnode already exists in the list.",
                                    })
                                } else {
                                    alterBootnodes(
                                        customBnInput,
                                        true,
                                        selectedChainDefaultBn?.includes(customBnInput),
                                    )
                                }
                            }}
                        >
                            Add
                        </Button>
                    </div>
                    <p className={bootnodeMsgClass}>
                        {addMessage && Object.keys(addMessage) ? addMessage.message : ""}
                    </p>
                </div>
            </div>
        </section>
    )
}
