import { v4 as uuidv4 } from "uuid";
import { useStateStore, nodeLabel, useEnv, nodeIsConnectable } from "@/lib/state";
import { Handle, Position, useNodes } from "@xyflow/react";
import { NodeRect } from "./node-rect";
import { useCallback, useEffect, useMemo } from "react";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm, EventType, DeepPartial } from "react-hook-form";
import { Form, FormControl, FormField, FormItem, FormMessage } from "./ui/form";
import { Input } from "./ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
import { Label } from "./ui/label";
import { Button } from "./ui/button";
import { XIcon } from "lucide-react";
import { Switch } from "./ui/switch";
import { NodeDetailsProps } from "@/lib/types";
import { AppNode, GatewayHttpsNode, ServiceNode } from "config";

const schema = z.object({
	network: z.string().min(1, "reqired"),
	subdomain: z.string().min(1, "required"),
});

const connectedToSchema = z.object({
	id: z.string(),
	portId: z.string(),
});

const authEnabledSchema = z.object({
	enabled: z.boolean(),
});

const authGroupSchema = z.object({
	group: z.string(),
});

const authNoAuthPatternSchema = z.object({
	noAuthPathPattern: z.string(),
});

export function NodeGatewayHttps(node: GatewayHttpsNode) {
	const { id, selected } = node;
	const isConnectableNetwork = useMemo(() => nodeIsConnectable(node, "subdomain"), [node]);
	const isConnectable = useMemo(() => nodeIsConnectable(node, "https"), [node]);
	return (
		<NodeRect id={id} selected={selected} node={node} state={node.data.state}>
			{nodeLabel(node)}
			<Handle
				type={"source"}
				id="subdomain"
				position={Position.Top}
				isConnectable={isConnectableNetwork}
				isConnectableStart={isConnectableNetwork}
				isConnectableEnd={isConnectableNetwork}
			/>
			<Handle
				type={"target"}
				id="https"
				position={Position.Bottom}
				isConnectable={isConnectable}
				isConnectableStart={isConnectable}
				isConnectableEnd={isConnectable}
			/>
		</NodeRect>
	);
}

export function NodeGatewayHttpsDetails({ node, disabled }: NodeDetailsProps<GatewayHttpsNode>) {
	const { id, data } = node;
	const store = useStateStore();
	const env = useEnv();
	const form = useForm<z.infer<typeof schema>>({
		resolver: zodResolver(schema),
		mode: "onChange",
		defaultValues: {
			network: data.network,
			subdomain: data.subdomain,
		},
	});
	useEffect(() => {
		const sub = form.watch(
			(
				value: DeepPartial<z.infer<typeof schema>>,
				{ name }: { name?: keyof z.infer<typeof schema> | undefined; type?: EventType | undefined },
			) => {
				if (name === "network") {
					let edges = store.edges;
					if (data.network !== undefined) {
						edges = edges.filter((e) => {
							if (
								e.source === id &&
								e.sourceHandle === "subdomain" &&
								e.target === data.network &&
								e.targetHandle === "subdomain"
							) {
								return false;
							} else {
								return true;
							}
						});
					}
					if (value.network !== undefined) {
						edges = edges.concat({
							id: uuidv4(),
							source: id,
							sourceHandle: "subdomain",
							target: value.network,
							targetHandle: "subdomain",
						});
					}
					store.setEdges(edges);
					store.updateNodeData<"gateway-https">(id, { network: value.network });
				} else if (name === "subdomain") {
					store.updateNodeData<"gateway-https">(id, { subdomain: value.subdomain });
				}
			},
		);
		return () => sub.unsubscribe();
	}, [id, data, form, store]);
	const network = useMemo(() => {
		if (data.network === undefined) {
			return null;
		}
		return env.networks.find((n) => n.domain === data.network)!;
	}, [data, env]);
	const connectedToForm = useForm<z.infer<typeof connectedToSchema>>({
		resolver: zodResolver(connectedToSchema),
		mode: "onChange",
		defaultValues: {
			id: data.https?.serviceId,
			portId: data.https?.portId,
		},
	});
	useEffect(() => {
		connectedToForm.reset({
			id: data.https?.serviceId,
			portId: data.https?.portId,
		});
	}, [connectedToForm, data]);
	const nodes = useNodes<AppNode>();
	const selected = useMemo(() => {
		if (data !== undefined && data.https !== undefined) {
			const https = data.https;
			return nodes.find((n) => n.id === https.serviceId)! as ServiceNode;
		}
		return null;
	}, [data, nodes]);
	const selectable = useMemo(() => {
		return nodes.filter((n) => {
			if (n.id === id) {
				return false;
			}
			if (selected !== null && selected.id === id) {
				return true;
			}
			if (n.type !== "app") {
				return false;
			}
			return n.data && n.data.ports && n.data.ports.length > 0;
		});
	}, [id, nodes, selected]);
	useEffect(() => {
		const sub = connectedToForm.watch(
			(
				value: DeepPartial<z.infer<typeof connectedToSchema>>,
				{
					name,
					type,
				}: { name?: keyof z.infer<typeof connectedToSchema> | undefined; type?: EventType | undefined },
			) => {
				if (type !== "change") {
					return;
				}
				switch (name) {
					case "id": {
						if (!value.id) {
							break;
						}
						const current = store.edges.filter((e) => e.target === id);
						const cid = current[0] ? current[0].id : undefined;
						store.replaceEdge(
							{
								source: value.id,
								sourceHandle: "ports",
								target: id,
								targetHandle: "https",
							},
							cid,
						);
						break;
					}
					case "portId":
						store.updateNodeData<"gateway-https">(id, {
							https: {
								serviceId: value.id,
								portId: value.portId,
							},
						});
						break;
				}
			},
		);
		return () => sub.unsubscribe();
	}, [id, connectedToForm, store, selectable]);
	const authEnabledForm = useForm<z.infer<typeof authEnabledSchema>>({
		resolver: zodResolver(authEnabledSchema),
		mode: "onChange",
		defaultValues: {
			enabled: data.auth ? data.auth.enabled : false,
		},
	});
	const authGroupForm = useForm<z.infer<typeof authGroupSchema>>({
		resolver: zodResolver(authGroupSchema),
		mode: "onSubmit",
		defaultValues: {
			group: "",
		},
	});
	const authNoAuthPatternFrom = useForm<z.infer<typeof authNoAuthPatternSchema>>({
		resolver: zodResolver(authNoAuthPatternSchema),
		mode: "onChange",
		defaultValues: {
			noAuthPathPattern: "",
		},
	});
	useEffect(() => {
		const sub = authEnabledForm.watch((value, { name }) => {
			if (name === "enabled") {
				store.updateNodeData<"gateway-https">(id, {
					auth: {
						...data.auth,
						enabled: value.enabled,
					},
				});
			}
		});
		return () => sub.unsubscribe();
	}, [id, data, authEnabledForm, store]);
	const removeGroup = useCallback(
		(group: string) => {
			const groups = data?.auth?.groups || [];
			store.updateNodeData<"gateway-https">(id, {
				auth: {
					...data.auth,
					groups: groups.filter((g) => g !== group),
				},
			});
			return true;
		},
		[id, data, store],
	);
	const onGroupSubmit = useCallback(
		(values: z.infer<typeof authGroupSchema>) => {
			const groups = data.auth?.groups || [];
			groups.push(values.group);
			store.updateNodeData<"gateway-https">(id, {
				auth: {
					...data.auth,
					groups,
				},
			});
			authGroupForm.reset();
		},
		[id, data, store, authGroupForm],
	);
	const removeNoAuthPathPattern = useCallback(
		(path: string) => {
			const noAuthPathPatterns = data?.auth?.noAuthPathPatterns || [];
			store.updateNodeData<"gateway-https">(id, {
				auth: {
					...data.auth,
					noAuthPathPatterns: noAuthPathPatterns.filter((p) => p !== path),
				},
			});
			return true;
		},
		[id, data, store],
	);
	const onNoAuthPathPatternSubmit = useCallback(
		(values: z.infer<typeof authNoAuthPatternSchema>) => {
			const noAuthPathPatterns = data.auth?.noAuthPathPatterns || [];
			noAuthPathPatterns.push(values.noAuthPathPattern);
			store.updateNodeData<"gateway-https">(id, {
				auth: {
					...data.auth,
					noAuthPathPatterns,
				},
			});
			authNoAuthPatternFrom.reset();
		},
		[id, data, store, authNoAuthPatternFrom],
	);
	return (
		<>
			<Form {...form}>
				<form className="space-y-2">
					<FormField
						control={form.control}
						name="network"
						render={({ field }) => (
							<FormItem>
								<Select
									onValueChange={field.onChange}
									defaultValue={field.value}
									disabled={data.readonly || disabled}
								>
									<FormControl>
										<SelectTrigger>
											<SelectValue placeholder="Network" />
										</SelectTrigger>
									</FormControl>
									<SelectContent>
										{env.networks.map((n) => (
											<SelectItem
												key={n.name}
												value={n.domain}
											>{`${n.name} - ${n.domain}`}</SelectItem>
										))}
									</SelectContent>
								</Select>
								<FormMessage />
							</FormItem>
						)}
					/>
					<FormField
						control={form.control}
						name="subdomain"
						render={({ field }) => (
							<FormItem>
								<FormControl>
									<Input placeholder="subdomain" {...field} disabled={data.readonly || disabled} />
								</FormControl>
								<FormMessage />
							</FormItem>
						)}
					/>
				</form>
			</Form>
			<Form {...connectedToForm}>
				<form className="space-y-2">
					<FormField
						control={connectedToForm.control}
						name="id"
						render={({ field }) => (
							<FormItem>
								<Select
									onValueChange={field.onChange}
									defaultValue={field.value}
									disabled={data.readonly || disabled}
								>
									<FormControl>
										<SelectTrigger>
											<SelectValue placeholder="Service" />
										</SelectTrigger>
									</FormControl>
									<SelectContent>
										{selectable.map((n) => (
											<SelectItem key={n.id} value={n.id}>
												{nodeLabel(n)}
											</SelectItem>
										))}
									</SelectContent>
								</Select>
								<FormMessage />
							</FormItem>
						)}
					/>
					<FormField
						control={connectedToForm.control}
						name="portId"
						render={({ field }) => (
							<FormItem>
								<Select
									onValueChange={field.onChange}
									defaultValue={field.value}
									disabled={data.readonly || disabled}
								>
									<FormControl>
										<SelectTrigger>
											<SelectValue placeholder="Port" />
										</SelectTrigger>
									</FormControl>
									<SelectContent>
										{selected &&
											selected.data.ports.map((p) => (
												<SelectItem key={p.id} value={p.id}>
													{p.name} - {p.value}
												</SelectItem>
											))}
									</SelectContent>
								</Select>
								<FormMessage />
							</FormItem>
						)}
					/>
				</form>
			</Form>
			{network?.hasAuth && (
				<>
					Auth
					<Form {...authEnabledForm}>
						<form className="space-y-2">
							<FormField
								control={authEnabledForm.control}
								name="enabled"
								render={({ field }) => (
									<FormItem>
										<div className="flex flex-row gap-1 items-center">
											<Switch
												id="authEnabled"
												onCheckedChange={field.onChange}
												checked={field.value}
												disabled={disabled}
											/>
											<Label htmlFor="authEnabled">Enabled</Label>
										</div>
										<FormMessage />
									</FormItem>
								)}
							/>
						</form>
					</Form>
					{data && data.auth && data.auth.enabled ? (
						<>
							Authorized Groups
							<ul>
								{(data.auth.groups || []).map((p) => (
									<li key={p} className="flex flex-row gap-1 items-center">
										<Button
											size={"icon"}
											variant={"ghost"}
											onClick={() => removeGroup(p)}
											disabled={disabled}
										>
											<XIcon />
										</Button>
										<div>{p}</div>
									</li>
								))}
							</ul>
							<Form {...authGroupForm}>
								<form
									className="flex flex-row space-x-1"
									onSubmit={authGroupForm.handleSubmit(onGroupSubmit)}
								>
									<FormField
										control={authGroupForm.control}
										name="group"
										render={({ field }) => (
											<FormItem>
												<FormControl>
													<Input placeholder="group" {...field} disabled={disabled} />
												</FormControl>
												<FormMessage />
											</FormItem>
										)}
									/>
									<Button type="submit" disabled={disabled}>
										Add Group
									</Button>
								</form>
							</Form>
							Auth optional path patterns
							<ul>
								{(data.auth.noAuthPathPatterns || []).map((p) => (
									<li key={p} className="flex flex-row gap-1 items-center">
										<Button
											size={"icon"}
											variant={"ghost"}
											onClick={() => removeNoAuthPathPattern(p)}
											disabled={disabled}
										>
											<XIcon />
										</Button>
										<div>{p}</div>
									</li>
								))}
							</ul>
							<Form {...authNoAuthPatternFrom}>
								<form
									className="flex flex-row space-x-1"
									onSubmit={authNoAuthPatternFrom.handleSubmit(onNoAuthPathPatternSubmit)}
								>
									<FormField
										control={authNoAuthPatternFrom.control}
										name="noAuthPathPattern"
										render={({ field }) => (
											<FormItem>
												<FormControl>
													<Input placeholder="group" {...field} disabled={disabled} />
												</FormControl>
												<FormMessage />
											</FormItem>
										)}
									/>
									<Button type="submit" disabled={disabled}>
										Add
									</Button>
								</form>
							</Form>
						</>
					) : (
						<></>
					)}
				</>
			)}
		</>
	);
}
