import { v4 as uuidv4 } from "uuid";
import { useStateStore, AppNode, nodeLabel, useEnv, GatewayTCPNode, nodeIsConnectable } from "@/lib/state";
import { Edge, Handle, Position, useNodes } from "@xyflow/react";
import { NodeRect } from "./node-rect";
import { useCallback, useEffect, useMemo, useState } 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 { Button } from "./ui/button";

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

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

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

export function NodeGatewayTCPDetails({ node, disabled }: { node: GatewayTCPNode; disabled?: boolean }) {
	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) => {
							console.log(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-tcp">(id, { network: value.network });
				} else if (name === "subdomain") {
					store.updateNodeData<"gateway-tcp">(id, { subdomain: value.subdomain });
				}
			},
		);
		return () => sub.unsubscribe();
	}, [id, data, form, store]);
	const connectedToForm = useForm<z.infer<typeof connectedToSchema>>({
		resolver: zodResolver(connectedToSchema),
		mode: "onSubmit",
		defaultValues: {
			serviceId: data.selected?.serviceId,
			portId: data.selected?.portId,
		},
	});
	useEffect(() => {
		connectedToForm.reset({
			serviceId: data.selected?.serviceId,
			portId: data.selected?.portId,
		});
		console.log(connectedToForm.getValues());
	}, [id, connectedToForm, data]);
	const nodes = useNodes<AppNode>();
	const [selected, setSelected] = useState<AppNode | undefined>(undefined);
	useEffect(() => {
		if (data.selected?.serviceId == null) {
			setSelected(undefined);
		} else {
			const serviceId = data.selected.serviceId;
			setSelected(nodes.find((n) => n.id === serviceId));
		}
	}, [id, data, setSelected, nodes]);
	const selectable = useMemo(() => {
		console.log(selected);
		return nodes.filter((n) => {
			if (n.id === id) {
				return false;
			}
			if (selected != null && selected.id === id) {
				return true;
			}
			if ("ports" in n.data && (n.data.ports || []).length > 0) {
				return true;
			}
			return false;
		});
	}, [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 "serviceId":
						if (!value.serviceId) {
							break;
						}
						store.updateNodeData<"gateway-tcp">(id, {
							selected: {
								serviceId: value.serviceId,
							},
						});
						break;
					case "portId":
						if (!value.portId) {
							break;
						}
						store.updateNodeData<"gateway-tcp">(id, {
							selected: {
								serviceId: value.serviceId,
								portId: value.portId,
							},
						});
						break;
				}
			},
		);
		return () => sub.unsubscribe();
	}, [id, connectedToForm, store]);
	const [nodeLabels, setNodeLabels] = useState(new Map<string, string>());
	const [portLabels, setPortLabels] = useState(new Map<string, string>());
	useEffect(() => {
		setNodeLabels(
			new Map(
				(data.exposed || []).map((e) => [e.serviceId, nodeLabel(nodes.find((n) => n.id === e.serviceId)!)]),
			),
		);
		setPortLabels(
			new Map(
				(data.exposed || []).map((e) => [
					`${e.serviceId} - ${e.portId}`,
					(nodes.find((n) => n.id === e.serviceId)!.data.ports || []).find((p) => p.id === e.portId)!.name,
				]),
			),
		);
	}, [nodes, data, setNodeLabels, setPortLabels]);
	const onSubmit = useCallback(
		(values: z.infer<typeof connectedToSchema>) => {
			const edges = store.edges.filter((e) => e.target !== id);
			const exp = (data.exposed || []).concat({
				serviceId: values.serviceId,
				portId: values.portId,
			});
			store.updateNodeData<"gateway-tcp">(id, {
				exposed: exp,
				selected: undefined,
			});
			store.setEdges(
				edges.concat(
					exp.map((e): Edge => {
						const sn = nodes.find((n) => n.id === e.serviceId);
						if (sn == null) {
							throw new Error(`Service ${e.serviceId} not found`);
						}
						if (sn.type === "app") {
							return {
								id: uuidv4(),
								source: e.serviceId,
								sourceHandle: "ports",
								target: id,
								targetHandle: "tcp",
							};
						} else {
							return {
								id: uuidv4(),
								source: e.serviceId,
								sourceHandle: "env_var",
								target: id,
								targetHandle: "tcp",
							};
						}
					}),
				),
			);
		},
		[id, data, store, nodes],
	);
	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>
			Exposed Services
			<ul>
				{(data.exposed || []).map((e, i) => (
					<li key={i}>
						{nodeLabels.get(e.serviceId)} - {portLabels.get(`${e.serviceId} - ${e.portId}`)}
					</li>
				))}
			</ul>
			<Form {...connectedToForm}>
				<form className="space-y-2" onSubmit={connectedToForm.handleSubmit(onSubmit)}>
					<FormField
						control={connectedToForm.control}
						name="serviceId"
						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 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>
						)}
					/>
					<Button type="submit" disabled={disabled}>
						Expose
					</Button>
				</form>
			</Form>
		</>
	);
}
