import { useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import {
	Dialog,
	DialogContent,
	DialogHeader,
	DialogTitle,
} from "@/components/ui/dialog";
import {
	Select,
	SelectContent,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";
import {
	ChevronDown,
	ChevronUp,
	ExternalLink,
	Plus,
	Trash2,
} from "lucide-react";
import {
	useDeleteServiceInstance,
	useSaveServiceInstance,
	useServiceInstances,
	useTestServiceInstance,
} from "../hooks/useServices";
import { useServiceTypes } from "../hooks/useServices";
import {
	useDashboards,
	useDeleteDashboard,
	useSaveDashboard,
} from "../hooks/useDashboards";
import type {
	SecretFieldInfo,
	ServiceInstance,
	ServiceInstanceInput,
	ServiceTestResult,
	ServiceTypeInfo,
} from "../types";
import { SectionCard } from "../components/SectionCard";
import { ConfirmDialog } from "../components/ConfirmDialog";
import { DialogFooter } from "../components/DialogFooter";
import { getServiceBinding } from "../integrations/registry";
import { ServiceTestPanel } from "../components/ServiceTestPanel";
import { serviceLinkTarget } from "../components/PinnedServiceLink";
import type { NamedDashboardInput } from "../api/dashboards";

interface CreateDraft {
	serviceType: string;
	name: string;
	config: Record<string, unknown>;
	secrets: Record<string, string>;
	enabled: boolean;
}

function emptyDraft(serviceType: string): CreateDraft {
	return { serviceType, name: "", config: {}, secrets: {}, enabled: true };
}

function Field({
	label,
	htmlFor,
	helper,
	children,
}: {
	label: string;
	htmlFor: string;
	helper?: string;
	children: React.ReactNode;
}) {
	return (
		<div className="flex flex-col gap-1.5">
			<Label htmlFor={htmlFor}>{label}</Label>
			{children}
			{helper ? (
				<p className="text-xs text-muted-foreground">{helper}</p>
			) : null}
		</div>
	);
}

function ServiceConfigFields({
	type,
	config,
	onChange,
}: {
	type: ServiceTypeInfo;
	config: Record<string, unknown>;
	onChange: (config: Record<string, unknown>) => void;
}) {
	const properties =
		(
			type.config_schema as {
				properties?: Record<string, { type?: string; description?: string }>;
			}
		).properties ?? {};
	return (
		<div className="flex flex-col gap-3">
			{Object.entries(properties).map(([key, schema]) => {
				const isNumber = schema.type === "integer" || schema.type === "number";
				return (
					<Field
						key={key}
						label={key}
						htmlFor={`cfg-${key}`}
						helper={schema.description}
					>
						<Input
							id={`cfg-${key}`}
							type={isNumber ? "number" : "text"}
							value={String(config[key] ?? "")}
							onChange={(e) =>
								onChange({
									...config,
									[key]: isNumber
										? e.target.value === ""
											? undefined
											: Number(e.target.value)
										: e.target.value,
								})
							}
						/>
					</Field>
				);
			})}
		</div>
	);
}

function ServiceSecretFields({
	fields,
	secrets,
	onChange,
}: {
	fields: SecretFieldInfo[];
	secrets: Record<string, string>;
	onChange: (secrets: Record<string, string>) => void;
}) {
	if (fields.length === 0) return null;
	return (
		<div className="flex flex-col gap-3">
			{fields.map((field) => (
				<Field
					key={field.key}
					label={field.label}
					htmlFor={`secret-${field.key}`}
					helper={field.helper ?? (field.required ? "Required" : undefined)}
				>
					<Input
						id={`secret-${field.key}`}
						type="password"
						value={secrets[field.key] ?? ""}
						onChange={(e) =>
							onChange({ ...secrets, [field.key]: e.target.value })
						}
					/>
				</Field>
			))}
		</div>
	);
}

function CreateServiceDialog({
	open,
	onClose,
}: {
	open: boolean;
	onClose: () => void;
}) {
	const { data: types = [] } = useServiceTypes();
	const saveService = useSaveServiceInstance();
	const testService = useTestServiceInstance();
	const [draft, setDraft] = useState<CreateDraft | null>(null);
	const [submitError, setSubmitError] = useState<string | null>(null);
	const [testResult, setTestResult] = useState<ServiceTestResult | null>(null);
	const [saveAnyway, setSaveAnyway] = useState(false);

	// Reset test state when draft changes (React "store previous" pattern —
	// avoids setState-in-effect). Any field edit creates a new draft object.
	const [prevDraft, setPrevDraft] = useState(draft);
	if (draft !== prevDraft) {
		setPrevDraft(draft);
		setTestResult(null);
		setSaveAnyway(false);
	}

	const testPassed = (testResult?.ok ?? false) || saveAnyway;

	function reset() {
		setDraft(null);
		setSubmitError(null);
		setTestResult(null);
		setSaveAnyway(false);
	}

	async function handleTest() {
		if (!draft) return;
		try {
			const res = await testService.mutateAsync({
				service_type: draft.serviceType,
				name: draft.name.trim(),
				config: draft.config,
				secrets: draft.secrets,
				enabled: draft.enabled,
			});
			setTestResult(res);
		} catch (err) {
			setTestResult({
				ok: false,
				detail: err instanceof Error ? err.message : String(err),
				evidence: null,
			});
		}
	}

	async function save() {
		if (!draft) return;
		if (!draft.name.trim()) return;
		const input: ServiceInstanceInput = {
			service_type: draft.serviceType,
			name: draft.name.trim(),
			config: draft.config,
			secrets: draft.secrets,
			enabled: draft.enabled,
		};
		setSubmitError(null);
		try {
			await saveService.mutateAsync(input);
			reset();
			onClose();
		} catch (err) {
			setSubmitError(err instanceof Error ? err.message : String(err));
		}
	}

	const selectedType = types.find((t) => t.service_type === draft?.serviceType);

	return (
		<Dialog
			open={open}
			onOpenChange={(next) => {
				if (!next) {
					reset();
					onClose();
				}
			}}
		>
			<DialogContent className="sm:max-w-lg">
				<DialogHeader>
					<DialogTitle>New service</DialogTitle>
				</DialogHeader>
				<div className="flex flex-col gap-4">
					{!draft ? (
						<div className="flex flex-col gap-2">
							{types.map((t) => (
								<Button
									key={t.service_type}
									variant="outline"
									onClick={() => setDraft(emptyDraft(t.service_type))}
								>
									<Plus className="mr-1 h-3 w-3" />
									{t.name}
								</Button>
							))}
						</div>
					) : (
						<>
							<p className="text-sm text-muted-foreground">
								{selectedType?.description}
							</p>
							<Field label="Name" htmlFor="service-name">
								<Input
									id="service-name"
									value={draft.name}
									onChange={(e) => setDraft({ ...draft, name: e.target.value })}
								/>
							</Field>
							{selectedType ? (
								<ServiceConfigFields
									type={selectedType}
									config={draft.config}
									onChange={(config) => setDraft({ ...draft, config })}
								/>
							) : null}
							{selectedType ? (
								<ServiceSecretFields
									fields={selectedType.secret_fields}
									secrets={draft.secrets}
									onChange={(secrets) => setDraft({ ...draft, secrets })}
								/>
							) : null}
							<div className="flex items-center gap-2">
								<Switch
									id="service-enabled"
									checked={draft.enabled}
									onCheckedChange={(checked) =>
										setDraft({ ...draft, enabled: checked })
									}
								/>
								<Label htmlFor="service-enabled">Enabled</Label>
							</div>
							<ServiceTestPanel
								result={testResult}
								isPending={testService.isPending}
								saveAnyway={saveAnyway}
								onTest={handleTest}
								onSaveAnywayChange={setSaveAnyway}
							/>
						</>
					)}
				</div>
				{submitError ? (
					<Alert variant="destructive">
						<AlertDescription>{submitError}</AlertDescription>
					</Alert>
				) : null}
				{draft ? (
					<DialogFooter
						onCancel={reset}
						onConfirm={save}
						confirmLabel="Create service"
						confirmDisabled={
							!draft.name.trim() || saveService.isPending || !testPassed
						}
					/>
				) : null}
			</DialogContent>
		</Dialog>
	);
}

// --- Named dashboards management (Slice 10.3) ---

function DashboardManagementCard() {
	const { data: dashboards = [] } = useDashboards();
	const saveDashboard = useSaveDashboard();
	const deleteDashboard = useDeleteDashboard();
	const { data: services = [] } = useServiceInstances();
	const [createOpen, setCreateOpen] = useState(false);
	const [newLabel, setNewLabel] = useState("");
	const [deleteId, setDeleteId] = useState<string | null>(null);
	const [linkDashId, setLinkDashId] = useState<string | null>(null);
	const [linkLabel, setLinkLabel] = useState("");
	const [linkTarget, setLinkTarget] = useState("");

	const enabledServices = useMemo(
		() => services.filter((s) => s.enabled),
		[services],
	);

	function createDashboard() {
		if (!newLabel.trim()) return;
		const input: NamedDashboardInput = {
			label: newLabel.trim(),
			sort_order: dashboards.length,
			payload: { items: [] },
		};
		saveDashboard.mutate(input);
		setNewLabel("");
		setCreateOpen(false);
	}

	function reorder(dashId: string, direction: -1 | 1) {
		const sorted = [...dashboards].sort((a, b) => a.sort_order - b.sort_order);
		const idx = sorted.findIndex((d) => d.id === dashId);
		const swapIdx = idx + direction;
		if (swapIdx < 0 || swapIdx >= sorted.length) return;
		const a = sorted[idx];
		const b = sorted[swapIdx];
		saveDashboard.mutate({
			...a,
			sort_order: b.sort_order,
			payload: a.payload,
		});
		saveDashboard.mutate({
			...b,
			sort_order: a.sort_order,
			payload: b.payload,
		});
	}

	function addPinnedLink() {
		if (!linkDashId || !linkLabel.trim() || !linkTarget.trim()) return;
		const dash = dashboards.find((d) => d.id === linkDashId);
		if (!dash) return;
		const items = Array.isArray(dash.payload.items)
			? (dash.payload.items as unknown[])
			: [];
		items.push({ type: "link", label: linkLabel.trim(), target: linkTarget });
		saveDashboard.mutate({
			id: dash.id,
			label: dash.label,
			sort_order: dash.sort_order,
			payload: { items },
		});
		setLinkLabel("");
		setLinkTarget("");
	}

	return (
		<SectionCard
			title="Dashboards"
			description="Named dashboards appear in the top nav. Compose them from pinned service links."
			action={
				<Button variant="outline" onClick={() => setCreateOpen(true)}>
					<Plus className="mr-1 h-3 w-3" />
					New dashboard
				</Button>
			}
		>
			{dashboards.length === 0 ? (
				<p className="text-sm text-muted-foreground">
					No named dashboards yet. Create one to add pinned service links.
				</p>
			) : (
				<div className="flex flex-col gap-3">
					{[...dashboards]
						.sort((a, b) => a.sort_order - b.sort_order)
						.map((d, idx, arr) => (
							<div key={d.id} className="rounded border p-3">
								<div className="flex items-center justify-between gap-2">
									<div className="flex items-center gap-2">
										<span className="font-medium">{d.label}</span>
										<Badge variant="outline">/{d.slug}</Badge>
									</div>
									<div className="flex items-center gap-1">
										<Button
											variant="ghost"
											size="icon"
											className="h-7 w-7"
											disabled={idx === 0}
											onClick={() => reorder(d.id, -1)}
										>
											<ChevronUp className="h-4 w-4" />
										</Button>
										<Button
											variant="ghost"
											size="icon"
											className="h-7 w-7"
											disabled={idx === arr.length - 1}
											onClick={() => reorder(d.id, 1)}
										>
											<ChevronDown className="h-4 w-4" />
										</Button>
										<Button
											variant="ghost"
											size="icon"
											className="h-7 w-7 text-destructive"
											onClick={() => setDeleteId(d.id)}
										>
											<Trash2 className="h-4 w-4" />
										</Button>
									</div>
								</div>
								<div className="mt-2 flex flex-wrap items-center gap-2">
									{Array.isArray(d.payload.items) &&
									(d.payload.items as unknown[]).length > 0 ? (
										<span className="text-xs text-muted-foreground">
											{(d.payload.items as unknown[]).length} pinned link(s)
										</span>
									) : (
										<span className="text-xs text-muted-foreground">
											No links yet
										</span>
									)}
								</div>
								<div className="mt-2 flex flex-wrap items-end gap-2">
									<Field label="Link label" htmlFor={`link-label-${d.id}`}>
										<Input
											id={`link-label-${d.id}`}
											className="w-40"
											placeholder="My Jellyfin"
											value={linkDashId === d.id ? linkLabel : ""}
											onChange={(e) => {
												setLinkDashId(d.id);
												setLinkLabel(e.target.value);
											}}
										/>
									</Field>
									<div className="flex flex-col gap-1.5">
										<Label htmlFor={`link-target-${d.id}`}>Service</Label>
										<Select
											value={linkDashId === d.id ? linkTarget : ""}
											onValueChange={(v) => {
												setLinkDashId(d.id);
												setLinkTarget(v);
											}}
										>
											<SelectTrigger
												id={`link-target-${d.id}`}
												className="w-56"
											>
												<SelectValue placeholder="Pick a service" />
											</SelectTrigger>
											<SelectContent>
												{enabledServices.map((s) => (
													<SelectItem
														key={s.id}
														value={serviceLinkTarget(s.service_type, s.id)}
													>
														{s.name} ({s.service_type})
													</SelectItem>
												))}
											</SelectContent>
										</Select>
									</div>
									<Button
										variant="outline"
										size="sm"
										disabled={
											linkDashId !== d.id ||
											!linkLabel.trim() ||
											!linkTarget.trim()
										}
										onClick={addPinnedLink}
									>
										Add link
									</Button>
								</div>
							</div>
						))}
				</div>
			)}

			<Dialog open={createOpen} onOpenChange={setCreateOpen}>
				<DialogContent className="sm:max-w-sm">
					<DialogHeader>
						<DialogTitle>New dashboard</DialogTitle>
					</DialogHeader>
					<Field label="Label" htmlFor="dash-label">
						<Input
							id="dash-label"
							placeholder="Storage overview"
							value={newLabel}
							onChange={(e) => setNewLabel(e.target.value)}
							onKeyDown={(e) => {
								if (e.key === "Enter") createDashboard();
							}}
						/>
					</Field>
					<DialogFooter
						onCancel={() => setCreateOpen(false)}
						onConfirm={createDashboard}
						confirmLabel="Create"
						confirmDisabled={!newLabel.trim() || saveDashboard.isPending}
					/>
				</DialogContent>
			</Dialog>

			<ConfirmDialog
				open={Boolean(deleteId)}
				title="Delete dashboard?"
				message="This removes the named dashboard and its pinned links."
				confirmLabel="Delete"
				onCancel={() => setDeleteId(null)}
				onConfirm={() => {
					if (deleteId) deleteDashboard.mutate(deleteId);
					setDeleteId(null);
				}}
			/>
		</SectionCard>
	);
}

export function ServicesPage() {
	const navigate = useNavigate();
	const { data: services = [] } = useServiceInstances();
	const { data: types = [] } = useServiceTypes();
	const deleteService = useDeleteServiceInstance();
	const [createOpen, setCreateOpen] = useState(false);
	const [deleteId, setDeleteId] = useState<string | null>(null);

	const grouped = useMemo(() => {
		const map = new Map<string, ServiceInstance[]>();
		for (const s of services) {
			const list = map.get(s.service_type) ?? [];
			list.push(s);
			map.set(s.service_type, list);
		}
		return [...map.entries()].sort((a, b) => a[0].localeCompare(b[0]));
	}, [services]);

	const typeName = (t: string) =>
		types.find((x) => x.service_type === t)?.name ??
		getServiceBinding(t)?.name ??
		t;

	return (
		<div className="flex flex-col gap-4">
			<SectionCard
				title="Services"
				description="External services the app talks to. Configure URLs and API keys here; they are encrypted at rest."
				action={
					<Button variant="outline" onClick={() => setCreateOpen(true)}>
						<Plus className="mr-1 h-3 w-3" />
						Add service
					</Button>
				}
			>
				{services.length === 0 ? (
					<Alert>
						<AlertDescription>
							No services yet. Add a Prometheus, Jellyfin, Nextcloud, or SSH
							task runner.
						</AlertDescription>
					</Alert>
				) : (
					<div className="flex flex-col gap-4">
						{grouped.map(([serviceType, instances]) => (
							<div key={serviceType} className="flex flex-col gap-2">
								<div className="text-sm font-medium">
									{typeName(serviceType)}
								</div>
								<div className="flex flex-col gap-2">
									{instances.map((s) => (
										<div
											key={s.id}
											className="flex items-center gap-2 rounded border p-2"
										>
											<div className="flex flex-1 flex-col gap-1">
												<div className="flex items-center gap-2">
													<span className="font-medium">{s.name}</span>
													<Badge variant="outline">{s.service_type}</Badge>
													{!s.enabled ? (
														<Badge variant="secondary">disabled</Badge>
													) : null}
													{Object.entries(s.secrets_set).some(([, v]) => v) ? (
														<Badge variant="outline">secrets set</Badge>
													) : null}
												</div>
											</div>
											<div className="flex items-center gap-1">
												<Button
													variant="ghost"
													size="sm"
													onClick={() =>
														navigate(`/services/${s.service_type}/${s.id}`)
													}
												>
													Open <ExternalLink className="ml-1 h-3 w-3" />
												</Button>
												<Button
													variant="ghost"
													size="icon"
													className="h-8 w-8 text-destructive"
													onClick={() => setDeleteId(s.id)}
												>
													<Trash2 className="h-4 w-4" />
												</Button>
											</div>
										</div>
									))}
								</div>
							</div>
						))}
					</div>
				)}
			</SectionCard>

			<DashboardManagementCard />

			<CreateServiceDialog
				open={createOpen}
				onClose={() => setCreateOpen(false)}
			/>
			<ConfirmDialog
				open={Boolean(deleteId)}
				title="Delete service?"
				message="This removes the service and any widgets that reference it. This cannot be undone."
				confirmLabel="Delete"
				onCancel={() => setDeleteId(null)}
				onConfirm={() => {
					if (deleteId) deleteService.mutate(deleteId);
					setDeleteId(null);
				}}
			/>
		</div>
	);
}
