import { AppNode, nodeLabel, useEnv, useMessages, useProjectId, useStateStore } from "@/lib/state";
import { Button } from "./ui/button";
import { useCallback, useEffect, useState } from "react";
import { generateDodoConfig } from "@/lib/config";
import { useNodes, useReactFlow } from "@xyflow/react";
import { useToast } from "@/hooks/use-toast";
import {
	DropdownMenuGroup,
	DropdownMenuItem,
	DropdownMenu,
	DropdownMenuContent,
	DropdownMenuTrigger,
} from "./ui/dropdown-menu";
import { LoaderCircle, Menu } from "lucide-react";

function toNodeType(t: string): string {
	if (t === "ingress") {
		return "gateway-https";
	} else if (t === "service") {
		return "app";
	} else {
		return t;
	}
}

export function Actions() {
	const { toast } = useToast();
	const store = useStateStore();
	const projectId = useProjectId();
	const nodes = useNodes<AppNode>();
	const env = useEnv();
	const messages = useMessages();
	const instance = useReactFlow();
	const [ok, setOk] = useState(false);
	const [loading, setLoading] = useState(false);
	const [reloading, setReloading] = useState(false);
	const info = useCallback(
		(title: string, description?: string, duration?: number) => {
			return toast({
				title,
				description,
				duration: duration ?? 2000,
			});
		},
		[toast],
	);
	const error = useCallback(
		(title: string, description?: string, duration?: number) => {
			return toast({
				variant: "destructive",
				title,
				description,
				duration: duration ?? 5000,
			});
		},
		[toast],
	);
	useEffect(() => {
		setOk(!messages.some((m) => m.type === "FATAL"));
	}, [messages, setOk]);
	const monitor = useCallback(async () => {
		const m = async function () {
			const resp = await fetch(`/api/project/${projectId}/status`, {
				method: "GET",
				headers: {
					"Content-Type": "application/json",
				},
			});
			if (resp.status !== 200) {
				return;
			}
			const data: { type: string; name: string; status: string }[] = await resp.json();
			console.log(data);
			for (const n of nodes) {
				if (n.type === "network") {
					continue;
				}
				const d = data.find((d) => n.type === toNodeType(d.type) && nodeLabel(n) === d.name);
				if (d !== undefined) {
					store.updateNodeData(n.id, {
						state: d?.status,
					});
				}
			}
			if (data.find((d) => d.status !== "success" && d.status != "failure") !== undefined) {
				setTimeout(m, 1000);
			}
		};
		setTimeout(m, 100);
	}, [projectId, nodes, store]);
	const deploy = useCallback(async () => {
		if (projectId == null) {
			return;
		}
		setLoading(true);
		try {
			const config = generateDodoConfig(projectId, nodes, env);
			if (config == null) {
				throw new Error("MUST NOT REACH!");
			}
			const resp = await fetch(`/api/project/${projectId}/deploy`, {
				method: "POST",
				headers: {
					"Content-Type": "application/json",
				},
				body: JSON.stringify({
					state: instance.toObject(),
					config,
				}),
			});
			if (resp.ok) {
				store.setMode("deploy");
				info("Deployment succeeded");
				monitor();
			} else {
				error("Deployment failed", await resp.text());
			}
			store.refreshEnv();
		} catch (e) {
			error("Deployment failed", e instanceof Error ? e.message : undefined);
		} finally {
			setLoading(false);
		}
	}, [projectId, instance, nodes, env, setLoading, info, error, monitor, store]);
	const save = useCallback(async () => {
		if (projectId == null) {
			return;
		}
		const resp = await fetch(`/api/project/${projectId}/saved`, {
			method: "POST",
			headers: {
				"Content-Type": "application/json",
			},
			body: JSON.stringify(instance.toObject()),
		});
		if (resp.ok) {
			info("Save succeeded");
		} else {
			error("Save failed", await resp.text());
		}
	}, [projectId, instance, info, error]);
	const restoreSaved = useCallback(async () => {
		if (projectId == null) {
			return;
		}
		const resp = await fetch(`/api/project/${projectId}/saved/${store.mode === "deploy" ? "deploy" : "draft"}`, {
			method: "GET",
		});
		const inst = await resp.json();
		const { x = 0, y = 0, zoom = 1 } = inst.viewport;
		store.setNodes(inst.nodes || []);
		store.setEdges(inst.edges || []);
		instance.setViewport({ x, y, zoom });
	}, [projectId, instance, store]);
	const clear = useCallback(() => {
		store.setEdges([]);
		store.setNodes(store.nodes.filter((n) => n.type === "network"));
	}, [store]);
	const edit = useCallback(async () => {
		store.setMode("edit");
	}, [store]);
	// TODO(gio): refresh projects
	const deleteProject = useCallback(async () => {
		if (projectId == null) {
			return;
		}
		if (!confirm("Are you sure you want to delete this project? This action cannot be undone.")) {
			return;
		}
		const resp = await fetch(`/api/project/${projectId}`, {
			method: "DELETE",
		});
		if (resp.ok) {
			clear();
			store.setProject(undefined);
			info("Project deleted");
		} else {
			error("Failed to delete project", await resp.text());
		}
	}, [store, clear, projectId, info, error]);
	const reload = useCallback(async () => {
		if (projectId == null) {
			return;
		}
		setReloading(true);
		const { dismiss } = info("Reloading services", "This may take a while...", Infinity);
		try {
			const resp = await fetch(`/api/project/${projectId}/reload`, {
				method: "POST",
				headers: {
					"Content-Type": "application/json",
				},
			});
			if (resp.ok) {
				dismiss();
				info("Reloaded services successfully");
			} else {
				dismiss();
				error("Reload failed", await resp.text());
			}
		} catch (e) {
			dismiss();
			error("Reload failed", e instanceof Error ? e.message : undefined);
		} finally {
			setReloading(false);
		}
	}, [projectId, info, error]);
	const removeDeployment = useCallback(async () => {
		if (projectId == null) {
			return;
		}
		if (!confirm("Are you sure you want to remove this deployment? This action cannot be undone.")) {
			return;
		}
		setReloading(true);
		try {
			const resp = await fetch(`/api/project/${projectId}/remove-deployment`, {
				method: "POST",
				headers: {
					"Content-Type": "application/json",
				},
			});
			if (resp.ok) {
				info("Deployment removed successfully");
				store.setMode("edit");
			} else {
				const errorData = await resp.json();
				error("Failed to remove deployment", errorData.error || "Unknown error");
			}
		} catch (e) {
			error("Failed to remove deployment", e instanceof Error ? e.message : undefined);
		} finally {
			setReloading(false);
		}
	}, [projectId, info, error, store]);
	const [deployProps, setDeployProps] = useState<{ loading?: boolean; disabled?: boolean }>({
		loading: false,
		disabled: false,
	});
	const [reloadProps, setReloadProps] = useState<{ loading?: boolean; disabled?: boolean }>({
		loading: false,
		disabled: false,
	});
	useEffect(() => {
		if (loading) {
			setDeployProps({ loading: true, disabled: true });
		} else if (ok) {
			setDeployProps({ disabled: false });
		} else {
			setDeployProps({ disabled: true });
		}

		if (reloading) {
			setReloadProps({ loading: true, disabled: true });
		} else {
			setReloadProps({ disabled: projectId === undefined });
		}
	}, [ok, loading, reloading, projectId]);
	if (store.mode === "deploy") {
		return (
			<div className="flex flex-row gap-1 items-center">
				<Button onClick={edit} {...reloadProps}>
					Edit
				</Button>
				<DropdownMenu>
					<DropdownMenuTrigger>
						<Menu className="rounded-md bg-gray-200 opacity-50" />
					</DropdownMenuTrigger>
					<DropdownMenuContent className="w-56">
						<DropdownMenuGroup>
							<DropdownMenuItem
								onClick={reload}
								className="cursor-pointer hover:bg-gray-200"
								{...reloadProps}
							>
								{reloadProps.loading ? (
									<>
										<LoaderCircle className="animate-spin" />
										Reloading...
									</>
								) : (
									"Reload services"
								)}
							</DropdownMenuItem>
							<DropdownMenuItem
								onClick={removeDeployment}
								disabled={projectId === undefined}
								className="cursor-pointer hover:bg-gray-200"
							>
								Remove deployment
							</DropdownMenuItem>
							<DropdownMenuItem
								onClick={deleteProject}
								disabled={projectId === undefined}
								className="cursor-pointer hover:bg-gray-200"
							>
								Delete project
							</DropdownMenuItem>
						</DropdownMenuGroup>
					</DropdownMenuContent>
				</DropdownMenu>
			</div>
		);
	} else {
		return (
			<div className="flex flex-row gap-1 items-center">
				<Button onClick={deploy} {...deployProps}>
					{deployProps.loading ? (
						<>
							<LoaderCircle className="animate-spin" />
							Deploying...
						</>
					) : (
						"Deploy"
					)}
				</Button>
				<Button onClick={save}>Save</Button>
				<DropdownMenu>
					<DropdownMenuTrigger>
						<Menu />
					</DropdownMenuTrigger>
					<DropdownMenuContent className="w-56">
						<DropdownMenuGroup>
							<DropdownMenuItem
								onClick={restoreSaved}
								disabled={projectId === undefined}
								className="cursor-pointer hover:bg-gray-200"
							>
								Restore
							</DropdownMenuItem>
							<DropdownMenuItem onClick={clear} className="cursor-pointer hover:bg-gray-200">
								Clear
							</DropdownMenuItem>
							<DropdownMenuItem
								onClick={deleteProject}
								disabled={projectId === undefined}
								className="cursor-pointer hover:bg-gray-200"
							>
								Delete project
							</DropdownMenuItem>
						</DropdownMenuGroup>
					</DropdownMenuContent>
				</DropdownMenu>
			</div>
		);
	}
}
