import React, {useEffect, useRef, useState} from "react"
import {Button, Checkbox, Col, Divider, Form, Input, Row, Space, Spin, Tabs, Tag, Tooltip} from "antd"
import {InputInteger, InputItem, ManyMultiSelectForString, SelectOne, SwitchItem} from "../../utils/inputUtil"
import {randomString} from "../../utils/randomUtil"
import {ExecResult, YakScript} from "../invoker/schema"
import {failed, info, success} from "../../utils/notification"
import {writeExecResultXTerm, xtermClear} from "../../utils/xtermUtils"
import {OpenPortTableViewer} from "./PortTable"
import {ExtractExecResultMessageToYakitPort, YakitPort} from "../../components/yakitLogSchema"
import {PortAssetTable} from "../assetViewer/PortAssetPage"
import {PortAsset} from "../assetViewer/models"
import {PresetPorts} from "./schema"
import {useGetState, useMemoizedFn} from "ahooks"
import {queryYakScriptList} from "../yakitStore/network"
import {PluginList} from "../../components/PluginList"
import {showModal} from "../../utils/showModal"
import {PluginResultUI} from "../yakitStore/viewers/base"
import useHoldingIPCRStream from "../../hook/useHoldingIPCRStream"
import {CVXterm} from "../../components/CVXterm"
import {ContentUploadInput} from "../../components/functionTemplate/ContentUploadTextArea"
import {ReloadOutlined} from "@ant-design/icons"

import "./PortScanPage.css"
import {SimplePluginList} from "../../components/SimplePluginList";

const {ipcRenderer} = window.require("electron")
const ScanPortTemplate = "scan-port-template"

export interface PortScanPageProp {
    sendTarget?: string
}

export interface PortScanParams {
    Targets: string
    Ports: string
    Mode: "syn" | "fingerprint" | "all"
    Proto: ("tcp" | "udp")[]
    Concurrent: number
    Active: boolean
    FingerprintMode: "service" | "web" | "all"
    SaveToDB: boolean
    SaveClosedPorts: boolean
    TargetsFile?: string
    ScriptNames: string[]
    Proxy: string[]
    ProbeTimeout: number
    ProbeMax: number
    EnableCClassScan: boolean

    SkippedHostAliveScan?: boolean
    HostAlivePorts?: string
}

const ScanKind: { [key: string]: string } = {
    syn: "SYN",
    fingerprint: "指纹",
    all: "SYN+指纹"
}
const ScanKindKeys: string[] = Object.keys(ScanKind)

const defaultPorts = "21,22,443,445,80,8000-8004,3306,3389,5432,6379,8080-8084,7000-7005,9000-9002,8443,7443,9443,7080,8070"

export const PortScanPage: React.FC<PortScanPageProp> = (props) => {
    const [loading, setLoading] = useState(false)
    const [params, setParams, getParams] = useGetState<PortScanParams>({
        Ports: defaultPorts,
        Mode: "fingerprint",
        Targets: props.sendTarget ? JSON.parse(props.sendTarget || "[]").join(",") : "",
        Active: true,
        Concurrent: 50,
        FingerprintMode: "all",
        Proto: ["tcp"],
        SaveClosedPorts: false,
        SaveToDB: true,
        Proxy: [],
        ProbeTimeout: 7,
        ScriptNames: [],
        ProbeMax: 3,
        EnableCClassScan: false,
        HostAlivePorts: "22,80,443",
    })
    const [token, setToken] = useState(randomString(40))
    const xtermRef = useRef(null)
    const [openPorts, setOpenPorts] = useState<YakitPort[]>([])
    // const [closedPorts, setClosedPorts] = useState<YakitPort[]>([])
    const [port, setPort] = useState<PortAsset>()

    const [uploadLoading, setUploadLoading] = useState(false)
    const [templatePort, setTemplatePort] = useState<string>()
    const openPort = useRef<YakitPort[]>([])
    // const closedPort = useRef<YakitPort[]>([])

    const [infoState, {reset}] = useHoldingIPCRStream(
        "scan-port",
        "PortScan",
        token,
        () => {
        },
        () => {
        },
        (obj, content) => content.data.indexOf("isOpen") > -1 && content.data.indexOf("port") > -1
    )

    useEffect(() => {
        setLoading(true)
        ipcRenderer
            .invoke("get-value", ScanPortTemplate)
            .then((value: any) => {
                if (value) {
                    setTemplatePort(value || "")
                    // setTimeout(() => {
                    //     setParams({...getParams(), Ports: value || ""})
                    // }, 300)
                }
            })
            .catch(() => {
            })
            .finally(() => {
                setTimeout(() => setLoading(false), 100)
            })
    }, [])

    useEffect(() => {
        if (!xtermRef) {
            return
        }

        ipcRenderer.on(`${token}-data`, async (e: any, data: ExecResult) => {
            if (data.IsMessage) {
                try {
                    let messageJsonRaw = Buffer.from(data.Message).toString("utf8")
                    let logInfo = ExtractExecResultMessageToYakitPort(JSON.parse(messageJsonRaw))
                    if (!logInfo) return

                    if (logInfo.isOpen) {
                        openPort.current.unshift(logInfo)
                    } else {
                        // closedPort.current.unshift(logInfo)
                    }
                } catch (e) {
                    failed("解析端口扫描结果失败...")
                }
            }
            writeExecResultXTerm(xtermRef, data)
        })
        ipcRenderer.on(`${token}-error`, (e: any, error: any) => {
            failed(`[PortScan] error:  ${error}`)
        })
        ipcRenderer.on(`${token}-end`, (e: any, data: any) => {
            info("[PortScan] finished")
            setLoading(false)
        })

        const syncPorts = () => {
            if (openPort.current) setOpenPorts([...openPort.current])
            // if (closedPort.current) setClosedPorts([...closedPort.current])
        }

        syncPorts()
        let id = setInterval(syncPorts, 1000)
        return () => {
            clearInterval(id)
            ipcRenderer.invoke("cancel-PortScan", token)
            ipcRenderer.removeAllListeners(`${token}-data`)
            ipcRenderer.removeAllListeners(`${token}-error`)
            ipcRenderer.removeAllListeners(`${token}-end`)
        }
    }, [xtermRef])

    return (
        <div style={{width: "100%", height: "100%"}}>
            <Tabs className='scan-port-tabs' tabBarStyle={{marginBottom: 5}}>
                <Tabs.TabPane tab={"扫描端口操作台"} key={"scan"}>
                    <div className='scan-port-body'>
                        <div style={{width: 360, height: "100%"}}>
                            <SimplePluginList
                                pluginTypes={"port-scan,mitm"}
                                initialSelected={params.ScriptNames}
                                onSelected={l => {
                                    setParams({...params, ScriptNames: [...l]})
                                }}
                            />
                        </div>

                        <div className='right-container'>
                            <div style={{width: "100%"}}>
                                <Form
                                    labelAlign='right'
                                    labelCol={{span: 5}}
                                    onSubmitCapture={(e) => {
                                        e.preventDefault()

                                        if (!token) {
                                            failed("No Token Assigned")
                                            return
                                        }
                                        if (!params.Targets && !params.TargetsFile) {
                                            failed("需要设置扫描目标")
                                            return
                                        }

                                        setLoading(true)
                                        openPort.current = []
                                        // closedPort.current = []
                                        reset()
                                        xtermClear(xtermRef)
                                        ipcRenderer.invoke("PortScan", params, token)
                                    }}
                                >
                                    <Spin spinning={uploadLoading}>
                                        <ContentUploadInput
                                            type="textarea"
                                            beforeUpload={(f) => {
                                                if (f.type !== "text/plain") {
                                                    failed(`${f.name}非txt文件,请上传txt格式文件!`)
                                                    return false
                                                }

                                                setUploadLoading(true)
                                                ipcRenderer.invoke("fetch-file-content", (f as any).path).then((res) => {
                                                    setParams({...params, Targets: res})
                                                    setTimeout(() => setUploadLoading(false), 100)
                                                })
                                                return false
                                            }}
                                            item={{
                                                style: {textAlign: "left"},
                                                label: "扫描目标",
                                            }}
                                            textarea={{
                                                isBubbing: true,
                                                setValue: (Targets) => setParams({...params, Targets}),
                                                value: params.Targets,
                                                rows: 1,
                                                placeholder: "域名/主机/IP/IP段均可,逗号分隔或按行分割"
                                            }}
                                            suffixNode={
                                                loading ? (
                                                    <Button
                                                        className="form-submit-style"
                                                        type='primary'
                                                        danger
                                                        onClick={(e) => ipcRenderer.invoke("cancel-PortScan", token)}
                                                    >
                                                        停止扫描
                                                    </Button>
                                                ) : (
                                                    <Button
                                                        className="form-submit-style"
                                                        type='primary'
                                                        htmlType='submit'
                                                    >
                                                        开始扫描
                                                    </Button>
                                                )
                                            }
                                        />
                                    </Spin>

                                    <Form.Item label='预设端口' colon={false} className='form-item-margin'>
                                        <Checkbox.Group
                                            onChange={(value) => {
                                                let res: string = (value || [])
                                                    .map((i) => {
                                                        if (i === "template") return templatePort
                                                        // @ts-ignore
                                                        return PresetPorts[i] || ""
                                                    })
                                                    .join(",")
                                                if (!!res) {
                                                    setParams({...params, Ports: res})
                                                }
                                            }}
                                        >
                                            <Checkbox value={"top100"}>常见100端口</Checkbox>
                                            <Checkbox value={"topweb"}>常见 Web 端口</Checkbox>
                                            <Checkbox value={"top1000+"}>常见一两千</Checkbox>
                                            <Checkbox value={"topdb"}>常见数据库与 MQ</Checkbox>
                                            <Checkbox value={"topudp"}>常见 UDP 端口</Checkbox>
                                            {templatePort && <Checkbox value={"template"}>默认模板</Checkbox>}
                                        </Checkbox.Group>
                                    </Form.Item>

                                    <Form.Item label='扫描端口' colon={false} className='form-item-margin'>
                                        <Input.TextArea
                                            style={{width: "75%"}}
                                            rows={2}
                                            value={params.Ports}
                                            onChange={(e) => setParams({...params, Ports: e.target.value})}
                                        />
                                        <Space size={"small"} style={{marginBottom: 4}}>
                                            <Tooltip title={"保存为模版"}>
                                                <a className="link-button-bfc"
                                                   onClick={() => {
                                                       if (!params.Ports) {
                                                           failed("请输入端口后再保存")
                                                           return
                                                       }
                                                       ipcRenderer.invoke("set-value", ScanPortTemplate, params.Ports).then(() => {
                                                           setTemplatePort(params.Ports)
                                                           success("保存成功")
                                                       })
                                                   }}>保存</a>
                                            </Tooltip>
                                            <Tooltip title={"重置为默认扫描端口"}>
                                                <a href={"#"} onClick={() => {
                                                    setParams({...params, Ports: defaultPorts})
                                                }}><ReloadOutlined/></a>
                                            </Tooltip>
                                        </Space>
                                    </Form.Item>

                                    <Form.Item label=' ' colon={false} className='form-item-margin'>
                                        <Space>
                                            <Tag>扫描模式:{ScanKind[params.Mode]}</Tag>
                                            <Tag>并发:{params.Concurrent}</Tag>
                                            <Checkbox onClick={e => {
                                                setParams({
                                                    ...params,
                                                    SkippedHostAliveScan: !params.SkippedHostAliveScan
                                                })
                                            }} checked={params.SkippedHostAliveScan}>
                                                跳过主机存活检测
                                            </Checkbox>
                                            <Button
                                                type='link'
                                                size='small'
                                                onClick={() => {
                                                    showModal({
                                                        title: "设置高级参数",
                                                        width: "50%",
                                                        content: (
                                                            <>
                                                                <ScanPortForm
                                                                    defaultParams={params}
                                                                    setParams={setParams}
                                                                />
                                                            </>
                                                        )
                                                    })
                                                }}
                                            >
                                                更多参数
                                            </Button>
                                        </Space>
                                    </Form.Item>
                                </Form>
                            </div>
                            <Divider style={{margin: "5px 0"}}/>
                            <div style={{flex: 1, overflow: "hidden"}}>
                                <Tabs className='scan-port-tabs' tabBarStyle={{marginBottom: 5}}>
                                    <Tabs.TabPane tab={"扫描端口列表"} key={"scanPort"} forceRender>
                                        <div style={{width: "100%", height: "100%", overflow: "hidden auto"}}>
                                            <div style={{textAlign: "right", marginBottom: 8}}>
                                                {loading ? (
                                                    <Tag color={"green"}>正在执行...</Tag>
                                                ) : (
                                                    <Tag>闲置中...</Tag>
                                                )}
                                            </div>

                                            <div style={{width: "100%", height: 178, overflow: "hidden"}}>
                                                <CVXterm
                                                    ref={xtermRef}
                                                    options={{
                                                        convertEol: true,
                                                        disableStdin: true
                                                    }}
                                                />
                                            </div>

                                            <Row style={{marginTop: 6}} gutter={6}>
                                                <Col span={24}>
                                                    <OpenPortTableViewer data={openPorts}/>
                                                </Col>
                                                {/*<Col span={8}>*/}
                                                {/*    <ClosedPortTableViewer data={closedPorts}/>*/}
                                                {/*</Col>*/}
                                            </Row>
                                        </div>
                                    </Tabs.TabPane>
                                    <Tabs.TabPane tab={"插件日志"} key={"pluginPort"} forceRender>
                                        <div style={{width: "100%", height: "100%", overflow: "hidden auto"}}>
                                            <PluginResultUI
                                                loading={loading}
                                                progress={infoState.processState}
                                                results={infoState.messageState}
                                                featureType={infoState.featureTypeState}
                                                feature={infoState.featureMessageState}
                                                statusCards={infoState.statusState}
                                            />
                                        </div>
                                    </Tabs.TabPane>
                                </Tabs>
                            </div>
                        </div>
                    </div>
                </Tabs.TabPane>
                <Tabs.TabPane tab={"端口资产管理"} key={"port"}>
                    <div style={{height: "100%", overflowY: "auto", padding: "0 6px"}}>
                        <PortAssetTable
                            onClicked={(i) => {
                                setPort(i)
                            }}
                        />
                    </div>
                </Tabs.TabPane>
            </Tabs>
        </div>
    )
}

interface ScanPortFormProp {
    defaultParams: PortScanParams
    setParams: (p: PortScanParams) => any
}

const ScanPortForm: React.FC<ScanPortFormProp> = (props) => {
    const [params, setParams] = useState<PortScanParams>(props.defaultParams)

    useEffect(() => {
        if (!params) return
        props.setParams({...params})
    }, [params])

    return (
        <Form
            onSubmitCapture={(e) => {
                e.preventDefault()
            }}
            labelCol={{span: 5}}
            wrapperCol={{span: 14}}
        >
            <SelectOne
                label={"扫描模式"}
                data={ScanKindKeys.map((item) => {
                    return {value: item, text: ScanKind[item]}
                })}
                help={"SYN 扫描需要 yak 启动时具有root"}
                setValue={(Mode) => setParams({...params, Mode})}
                value={params.Mode}
            />
            <SelectOne
                label={"扫描协议"}
                data={[
                    {text: "TCP", value: "tcp"},
                    {text: "UDP", value: "udp"},
                ]}
                setValue={i => setParams({...params, Proto: [i]})}
                value={(params.Proto || []).length > 0 ? params.Proto[0] : "tcp"}
            >

            </SelectOne>
            <SwitchItem label={"自动扫相关C段"} help={"可以把域名 /IP 转化为 C 段目标,直接进行扫描"}
                        value={params.EnableCClassScan}
                        setValue={EnableCClassScan => setParams({...params, EnableCClassScan})}
            />
            <SwitchItem label={"跳过主机存活检测"} help={"主机存活检测,根据当前用户权限使用 ICMP/TCP Ping 探测主机是否存活"}
                        value={params.SkippedHostAliveScan}
                        setValue={SkippedHostAliveScan => setParams({...params, SkippedHostAliveScan})}
            />
            {!params.SkippedHostAliveScan && (
                <>
                    <InputItem
                        label={"TCP Ping 端口"} help={"配置 TCP Ping 端口:以这些端口是否开放作为 TCP Ping 依据"}
                        value={params.HostAlivePorts}
                        setValue={HostAlivePorts => setParams({...params, HostAlivePorts})}
                    />
                </>
            )}
            {params.Mode != "syn" && params.Active && <SelectOne
                label={"服务指纹级别"}
                help={"级别越高探测的详细程度越多,主动发包越多,时间越长"}
                data={[
                    {value: 1, text: "基础"},
                    {value: 3, text: "适中"},
                    {value: 7, text: "详细"},
                    {value: 100, text: "全部"},
                ]}
                value={params.ProbeMax} setValue={ProbeMax => setParams({...params, ProbeMax})}
            >

            </SelectOne>}
            <InputInteger
                label={"并发"}
                help={"最多同时扫描200个端口"}
                value={params.Concurrent}
                min={1}
                max={200}
                setValue={(e) => setParams({...params, Concurrent: e})}
            />
            <SwitchItem
                label={"主动模式"}
                help={"允许指纹探测主动发包"}
                setValue={(Active) => setParams({...params, Active})}
                value={params.Active}
            />
            <InputInteger
                label={"主动发包超时时间"}
                help={"某些指纹的检测需要检查目标针对某一个探针请求的响应,需要主动发包"}
                value={params.ProbeTimeout} setValue={ProbeTimeout => setParams({...params, ProbeTimeout})}
            />
            <ManyMultiSelectForString
                label={"TCP 代理"}
                help={"支持 HTTP/Sock4/Sock4a/Socks5 协议,例如 http://127.0.0.1:7890  socks5://127.0.0.1:7890"}
                data={[
                    "http://127.0.0.1:7890", "http://127.0.0.1:8082",
                    "socks5://127.0.0.1:8082", "http://127.0.0.1:8083",
                ].map(i => {
                    return {value: i, label: i}
                })}
                value={(params.Proxy || []).join(",")}
                mode={"tags"}
                setValue={e => setParams({...params, Proxy: (e || "").split(",").filter(i => !!i)})}
            />
            <SwitchItem
                label={"扫描结果入库"}
                setValue={(SaveToDB) => {
                    setParams({...params, SaveToDB, SaveClosedPorts: false})
                }}
                value={params.SaveToDB}
            />
            {params.SaveToDB && (
                <SwitchItem
                    label={"保存关闭的端口"}
                    setValue={(SaveClosedPorts) => setParams({...params, SaveClosedPorts})}
                    value={params.SaveClosedPorts}
                />
            )}
            {params.Mode !== "syn" && (
                <SelectOne
                    label={"高级指纹选项"}
                    data={[
                        {value: "web", text: "仅web指纹"},
                        {value: "service", text: "仅nmap指纹"},
                        {value: "all", text: "全部指纹"}
                    ]}
                    setValue={(FingerprintMode) => setParams({...params, FingerprintMode})}
                    value={params.FingerprintMode}
                />
            )}
        </Form>
    )
}