import { useEffect, useRef, useState } from "react"
import { Trans, useLingui } from "@lingui/react/macro"
import { useStore } from "@nanostores/react"
import { pb } from "@/lib/api"
import {
	Dialog,
	DialogContent,
	DialogDescription,
	DialogFooter,
	DialogHeader,
	DialogTitle,
} from "@/components/ui/dialog"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { ChevronDownIcon, ListIcon } from "lucide-react"
import { useToast } from "@/components/ui/use-toast"
import { $systems } from "@/lib/stores"
import type { NetworkProbeRecord } from "@/types"
import * as v from "valibot"

type ProbeProtocol = "icmp" | "tcp" | "http"

type ProbeValues = {
	system: string
	target: string
	protocol: ProbeProtocol
	port: number
	interval: string
	name?: string
}

type NormalizedProbeValues = Omit<ProbeValues, "system" | "interval"> & {
	interval: number
}

const ProbeProtocolSchema = v.picklist(["icmp", "tcp", "http"])

const ProbeIntervalSchema = v.pipe(v.string(), v.toNumber(), v.minValue(1), v.maxValue(3600))

// Both the single-probe form and the bulk importer flow through this schema so
// defaults and HTTP target normalization stay in one place.
const NormalizedProbeValuesSchema = v.pipe(
	v.object({
		target: v.pipe(v.string(), v.trim(), v.nonEmpty("target is required")),
		protocol: ProbeProtocolSchema,
		port: v.number(),
		interval: ProbeIntervalSchema,
		name: v.optional(v.pipe(v.string(), v.trim())),
	}),
	v.transform((input): NormalizedProbeValues => {
		let { protocol, port } = input
		if (protocol === "icmp") {
			port = 0
		} else if ((protocol === "tcp" || protocol === "http") && !port) {
			port = 443
		}
		return {
			// HTTP probes may be entered as bare hostnames, so normalize them to a
			// scheme-bearing URL before the payload is sent to PocketBase.
			target: protocol === "http" ? normalizeHttpTarget(input.target, port) : input.target,
			protocol,
			port,
			interval: input.interval,
			name: input.name || undefined,
		}
	}),
	v.forward(
		v.check((input) => {
			if (input.protocol === "icmp") {
				return input.port === 0
			}

			return Number.isInteger(input.port) && input.port >= 1 && input.port <= 65535
		}, "Port must be between 1 and 65535"),
		["port"]
	)
)

// Bulk parsing only trims raw CSV fields. Inference, defaults, and protocol-
// specific validation still go through the shared normalization schema above.
const BulkProbeSchema = v.object({
	target: v.pipe(v.string(), v.trim(), v.nonEmpty("target is required")),
	protocol: v.optional(v.pipe(v.string(), v.trim())),
	port: v.optional(v.pipe(v.string(), v.trim())),
	interval: v.optional(v.pipe(v.string(), v.trim())),
	name: v.optional(v.pipe(v.string(), v.trim())),
})

function normalizeHttpTarget(target: string, port: number) {
	if (/^https?:\/\//i.test(target)) {
		return target
	}

	return `${port === 443 ? "https" : "http"}://${target}`
}

function buildProbePayload(values: ProbeValues) {
	const normalizedValues = v.safeParse(NormalizedProbeValuesSchema, values)
	if (!normalizedValues.success) {
		throw new Error(normalizedValues.issues[0]?.message || "Invalid probe")
	}

	const payload = {
		system: values.system,
		enabled: true,
		...normalizedValues.output,
	}

	const trimmedName = normalizedValues.output.name?.trim()
	const targetName = normalizedValues.output.target.replace(/^https?:\/\//i, "")
	if (trimmedName) {
		payload.name = trimmedName
	} else if (targetName !== normalizedValues.output.target) {
		payload.name = targetName
	} else {
		payload.name = ""
	}
	return payload
}

function parseBulkProbeLine(line: string, lineNumber: number, system: string) {
	const [rawTarget = "", rawProtocol = "", rawPort = "", rawInterval = "", ...rawName] = line.split(",")
	const parsed = v.safeParse(BulkProbeSchema, {
		target: rawTarget,
		protocol: rawProtocol,
		port: rawPort,
		interval: rawInterval,
		name: rawName.join(","),
	})
	if (!parsed.success) {
		throw new Error(`Line ${lineNumber}: ${parsed.issues[0]?.message || "invalid probe entry"}`)
	}

	return buildProbePayload({
		system,
		target: parsed.output.target,
		protocol: (parsed.output.protocol?.toLowerCase() ||
			(/^https?:\/\//i.test(parsed.output.target) ? "http" : "icmp")) as ProbeProtocol,
		port: parsed.output.port ? Number(parsed.output.port) : 0,
		interval: parsed.output.interval || "30",
		name: parsed.output.name || undefined,
	})
}

export function AddProbeDialog({ systemId }: { systemId?: string }) {
	const [open, setOpen] = useState(false)
	const [bulkOpen, setBulkOpen] = useState(false)
	const [bulkInput, setBulkInput] = useState("")
	const [bulkLoading, setBulkLoading] = useState(false)
	const [bulkSelectedSystemId, setBulkSelectedSystemId] = useState("")
	const bulkFormRef = useRef<HTMLFormElement>(null)
	const { toast } = useToast()
	const { t } = useLingui()
	const systems = useStore($systems)

	const resetBulkForm = () => {
		setBulkInput("")
		// setBulkSelectedSystemId("")
	}

	const openBulkAdd = (selectedSystemId?: string) => {
		if (!systemId && selectedSystemId) {
			setBulkSelectedSystemId(selectedSystemId)
		}
		setOpen(false)
		setBulkOpen(true)
	}

	const openAdd = () => {
		setBulkOpen(false)
		setOpen(true)
	}

	async function handleBulkSubmit(e: React.FormEvent) {
		e.preventDefault()
		setBulkLoading(true)
		let closedForSubmit = false

		try {
			const system = systemId ?? bulkSelectedSystemId
			if (!system) {
				throw new Error("Select a system.")
			}
			const rawLines = bulkInput.split(/\r?\n/).filter((line) => line.trim())
			if (!rawLines.length) {
				throw new Error("Enter at least one probe.")
			}

			const payloads = rawLines.map((line, index) => parseBulkProbeLine(line, index + 1, system))
			closedForSubmit = true
			let batch = pb.createBatch()
			let inBatch = 0
			for (const payload of payloads) {
				batch.collection("network_probes").create(payload)
				inBatch++
				if (inBatch > 20) {
					await batch.send()
					batch = pb.createBatch()
					inBatch = 0
				}
			}
			if (inBatch) {
				await batch.send()
			}

			resetBulkForm()
			toast({ title: t`Probes created`, description: `${payloads.length} probe(s) added.` })
		} catch (err: unknown) {
			if (closedForSubmit) {
				setBulkOpen(true)
			}
			toast({ variant: "destructive", title: t`Error`, description: (err as Error)?.message })
		} finally {
			setBulkLoading(false)
		}
	}

	return (
		<>
			<div className="flex gap-0 rounded-lg">
				<Button variant="outline" onClick={openAdd} className="rounded-e-none grow">
					{/* <PlusIcon className="size-4 me-1" /> */}
					<Trans>Add {{ foo: t`Probe` }}</Trans>
				</Button>
				<div className="w-px h-full bg-muted"></div>
				<DropdownMenu>
					<DropdownMenuTrigger asChild>
						<Button variant="outline" className="px-2 rounded-s-none border-s-0" aria-label={t`More probe actions`}>
							<ChevronDownIcon className="size-4" />
						</Button>
					</DropdownMenuTrigger>
					<DropdownMenuContent align="end">
						<DropdownMenuItem onClick={() => openBulkAdd(systemId)}>
							<ListIcon className="size-4 me-2" />
							<Trans>Bulk Add</Trans>
						</DropdownMenuItem>
					</DropdownMenuContent>
				</DropdownMenu>
			</div>
			<Dialog
				open={open}
				onOpenChange={(nextOpen) => {
					setOpen(nextOpen)
				}}
			>
				<ProbeDialogContent open={open} setOpen={setOpen} systemId={systemId} onOpenBulkAdd={openBulkAdd} />
			</Dialog>

			<Sheet
				open={bulkOpen}
				onOpenChange={(nextOpen) => {
					setBulkOpen(nextOpen)
					if (!nextOpen) {
						resetBulkForm()
					}
				}}
			>
				<SheetContent className="w-full sm:max-w-xl gap-0">
					<SheetHeader className="border-b">
						<SheetTitle>
							<Trans>Bulk Add {{ foo: t`Network Probes` }}</Trans>
						</SheetTitle>
						<SheetDescription>
							target[,protocol[,port[,interval[,name]]]] - TCP/HTTP default to port 443.
						</SheetDescription>
					</SheetHeader>
					<form ref={bulkFormRef} onSubmit={handleBulkSubmit} className="flex h-full flex-col overflow-hidden">
						<div className="flex-1 space-y-4 overflow-auto p-4">
							{!systemId && (
								<div className="grid gap-2">
									<Label>
										<Trans>System</Trans>
									</Label>
									<Select value={bulkSelectedSystemId} onValueChange={setBulkSelectedSystemId} required>
										<SelectTrigger>
											<SelectValue placeholder={t`Select a system`} />
										</SelectTrigger>
										<SelectContent>
											{systems.map((sys) => (
												<SelectItem key={sys.id} value={sys.id}>
													{sys.name}
												</SelectItem>
											))}
										</SelectContent>
									</Select>
								</div>
							)}
							<div className="grid gap-2">
								<Label htmlFor="bulk-probes" className="sr-only">
									Entries
								</Label>
								<Textarea
									id="bulk-probes"
									value={bulkInput}
									onChange={(e) => setBulkInput(e.target.value)}
									onKeyDown={(e) => {
										if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
											e.preventDefault()
											bulkFormRef.current?.requestSubmit()
										}
									}}
									className="h-200 font-mono text-sm bg-muted/40"
									style={{ maxHeight: `calc(100vh - 20rem)` }}
									placeholder={["1.1.1.1", "example.com,tcp", "https://example.com,http,,60,Homepage"].join("\n")}
									required
								/>
								<p className="text-xs text-muted-foreground">
									target[,protocol[,port[,interval[,name]]]] • TCP and HTTP default to port 443.
								</p>
							</div>
						</div>
						<SheetFooter className="border-t">
							<Button type="submit" disabled={bulkLoading || (!systemId && !bulkSelectedSystemId)}>
								<Trans>Add {{ foo: t`Network Probes` }}</Trans>
							</Button>
						</SheetFooter>
					</form>
				</SheetContent>
			</Sheet>
		</>
	)
}

export function EditProbeDialog({
	open,
	setOpen,
	systemId,
	probe,
}: {
	open: boolean
	setOpen: (open: boolean) => void
	systemId?: string
	probe?: NetworkProbeRecord
}) {
	if (!probe) {
		return null
	}

	return (
		<Dialog open={open} onOpenChange={setOpen}>
			<ProbeDialogContent open={open} setOpen={setOpen} systemId={systemId} probe={probe} />
		</Dialog>
	)
}

function ProbeDialogContent({
	open,
	setOpen,
	systemId,
	probe,
	onOpenBulkAdd,
}: {
	open: boolean
	setOpen: (open: boolean) => void
	systemId?: string
	probe?: NetworkProbeRecord
	onOpenBulkAdd?: (selectedSystemId?: string) => void
}) {
	const [protocol, setProtocol] = useState<ProbeProtocol>(probe?.protocol ?? "icmp")
	const [target, setTarget] = useState(probe?.target ?? "")
	const [port, setPort] = useState(
		(probe?.protocol === "tcp" || probe?.protocol === "http") && probe.port ? String(probe.port) : ""
	)
	const [probeInterval, setProbeInterval] = useState(String(probe?.interval ?? 30))
	const [name, setName] = useState(probe?.name ?? "")
	const [loading, setLoading] = useState(false)
	const [selectedSystemId, setSelectedSystemId] = useState(probe?.system ?? "")
	const systems = useStore($systems)
	const { toast } = useToast()
	const { t } = useLingui()
	const isEditing = !!probe
	const targetName = target.replace(/^https?:\/\//, "")

	// When the dialog is opened, initialize form fields with probe values (if editing) or defaults (if adding).
	useEffect(() => {
		if (!open) {
			return
		}

		setProtocol(probe?.protocol ?? "icmp")
		setTarget(probe?.target ?? "")
		setPort((probe?.protocol === "tcp" || probe?.protocol === "http") && probe.port ? String(probe.port) : "")
		setProbeInterval(String(probe?.interval ?? 30))
		setName(probe?.name ?? "")
		setSelectedSystemId(probe?.system ?? "")
		setLoading(false)
	}, [open, probe])

	async function handleSubmit(e: React.FormEvent) {
		e.preventDefault()
		setLoading(true)

		try {
			const selectedSystem = systemId ?? selectedSystemId
			if (!selectedSystem) {
				throw new Error("Select a system.")
			}
			const payload = buildProbePayload({
				system: selectedSystem,
				target,
				protocol,
				port: protocol === "tcp" || protocol === "http" ? Number(port) : 0,
				interval: probeInterval,
				name,
			})
			if (probe) {
				await pb.collection("network_probes").update(probe.id, payload)
			} else {
				await pb.collection("network_probes").create(payload)
			}
			setOpen(false)
		} catch (err: unknown) {
			toast({ variant: "destructive", title: t`Error`, description: (err as Error)?.message })
		} finally {
			setLoading(false)
		}
	}

	return (
		<DialogContent className="max-w-md">
			<DialogHeader>
				<DialogTitle>
					{isEditing ? <Trans>Edit {{ foo: t`Network Probe` }}</Trans> : <Trans>Add {{ foo: t`Network Probe` }}</Trans>}
				</DialogTitle>
				<DialogDescription>
					<Trans>Configure response monitoring from this agent.</Trans>
				</DialogDescription>
			</DialogHeader>
			<form onSubmit={handleSubmit} className="grid gap-4 tabular-nums">
				{!systemId && (
					<div className="grid gap-2">
						<Label>
							<Trans>System</Trans>
						</Label>
						<Select value={selectedSystemId} onValueChange={setSelectedSystemId} required>
							<SelectTrigger>
								<SelectValue placeholder={t`Select a system`} />
							</SelectTrigger>
							<SelectContent>
								{systems.map((sys) => (
									<SelectItem key={sys.id} value={sys.id}>
										{sys.name}
									</SelectItem>
								))}
							</SelectContent>
						</Select>
					</div>
				)}
				<div className="grid gap-2">
					<Label>
						<Trans>Target</Trans>
					</Label>
					<Input
						value={target}
						onChange={(e) => setTarget(e.target.value)}
						placeholder={protocol === "http" ? "https://example.com" : "1.1.1.1"}
						required
					/>
				</div>
				<div className="grid gap-2">
					<Label>
						<Trans>Protocol</Trans>
					</Label>

					<Select value={protocol} onValueChange={(value) => setProtocol(value as ProbeProtocol)}>
						<SelectTrigger>
							<SelectValue />
						</SelectTrigger>
						<SelectContent>
							<SelectItem value="icmp">ICMP</SelectItem>
							<SelectItem value="tcp">TCP</SelectItem>
							<SelectItem value="http">HTTP</SelectItem>
						</SelectContent>
					</Select>
				</div>
				{(protocol === "tcp" || protocol === "http") && (
					<div className="grid gap-2">
						<Label>
							<Trans>Port</Trans>
						</Label>
						<Input
							type="number"
							value={port}
							onChange={(e) => setPort(e.target.value)}
							placeholder="443"
							min={1}
							max={65535}
							required={protocol === "tcp"}
						/>
					</div>
				)}
				<div className="grid gap-2">
					<Label>
						<Trans>Interval (seconds)</Trans>
					</Label>
					<Input
						type="number"
						value={probeInterval}
						onChange={(e) => setProbeInterval(e.target.value)}
						min={1}
						max={3600}
						required
					/>
				</div>
				<div className="grid gap-2">
					<Label>
						<Trans>Name (optional)</Trans>
					</Label>
					<Input
						value={name}
						onChange={(e) => setName(e.target.value)}
						placeholder={targetName || t`e.g. Cloudflare DNS`}
					/>
				</div>
				<DialogFooter>
					{!isEditing && onOpenBulkAdd && (
						<Button
							type="button"
							variant="outline"
							onClick={() => onOpenBulkAdd(selectedSystemId)}
							disabled={loading}
							className="me-auto"
						>
							<ListIcon className="size-4 me-2" />
							<Trans>Bulk Add</Trans>
						</Button>
					)}
					<Button type="submit" disabled={loading || (!systemId && !selectedSystemId)}>
						{loading ? (
							isEditing ? (
								<Trans>Saving...</Trans>
							) : (
								<Trans>Creating...</Trans>
							)
						) : isEditing ? (
							<Trans>Save {{ foo: t`Probe` }}</Trans>
						) : (
							<Trans>Add {{ foo: t`Probe` }}</Trans>
						)}
					</Button>
				</DialogFooter>
			</form>
		</DialogContent>
	)
}
