import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query";
import { Copy, Plus, RefreshCw, Trash2 } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import { Button } from "~/client/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
import { Switch } from "~/client/components/ui/switch";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table";
import { Badge } from "~/client/components/ui/badge";
import { Checkbox } from "~/client/components/ui/checkbox";
import { Tooltip, TooltipContent, TooltipTrigger } from "~/client/components/ui/tooltip";
import {
	Dialog,
	DialogContent,
	DialogDescription,
	DialogFooter,
	DialogHeader,
	DialogTitle,
} from "~/client/components/ui/dialog";
import {
	getScheduleMirrorsOptions,
	getMirrorCompatibilityOptions,
	getMirrorSyncStatusOptions,
	updateScheduleMirrorsMutation,
	syncMirrorMutation,
} from "~/client/api-client/@tanstack/react-query.gen";
import { parseError } from "~/client/lib/errors";
import type { Repository } from "~/client/lib/types";
import { RepositoryIcon } from "~/client/components/repository-icon";
import { StatusDot } from "~/client/components/status-dot";
import { ByteSize } from "~/client/components/bytes-size";
import { useServerEvents } from "~/client/hooks/use-server-events";
import { cn } from "~/client/lib/utils";
import type { GetScheduleMirrorsResponse } from "~/client/api-client";
import { Link } from "@tanstack/react-router";
import { useTimeFormat } from "~/client/lib/datetime";

type Props = {
	scheduleShortId: string;
	primaryRepositoryId: string;
	repositories: Repository[];
	initialData: GetScheduleMirrorsResponse;
};

type MirrorAssignment = {
	repositoryId: string;
	enabled: boolean;
	lastCopyAt: number | null;
	lastCopyStatus: "success" | "error" | "in_progress" | null;
	lastCopyError: string | null;
};

const isSyncing = (assignment: MirrorAssignment) => assignment.lastCopyStatus === "in_progress";

const buildAssignments = (mirrors: GetScheduleMirrorsResponse) =>
	new Map<string, MirrorAssignment>(
		mirrors.map((mirror) => [
			mirror.repositoryId,
			{
				repositoryId: mirror.repositoryId,
				enabled: mirror.enabled,
				lastCopyAt: mirror.lastCopyAt,
				lastCopyStatus: mirror.lastCopyStatus,
				lastCopyError: mirror.lastCopyError,
			},
		]),
	);

export const ScheduleMirrorsConfig = ({ scheduleShortId, primaryRepositoryId, repositories, initialData }: Props) => {
	const { formatDateTime, formatTimeAgo } = useTimeFormat();
	const [assignments, setAssignments] = useState<Map<string, MirrorAssignment>>(() => buildAssignments(initialData));
	const [hasChanges, setHasChanges] = useState(false);
	const [isAddingNew, setIsAddingNew] = useState(false);
	const [syncDialogMirrorId, setSyncDialogMirrorId] = useState<string | null>(null);
	const [selectedSnapshotIds, setSelectedSnapshotIds] = useState<Set<string>>(new Set());
	const [syncDialogOpen, setSyncDialogOpen] = useState(false);
	const { addEventListener } = useServerEvents();

	const closeSyncDialog = () => {
		setSyncDialogOpen(false);
		setTimeout(() => {
			setSyncDialogMirrorId(null);
			setSelectedSnapshotIds(new Set());
		}, 300);
	};

	const openSyncDialog = (mirrorShortId: string) => {
		setSyncDialogOpen(true);
		setSelectedSnapshotIds(new Set());
		setSyncDialogMirrorId(mirrorShortId);
	};

	const { data: currentMirrors } = useSuspenseQuery({
		...getScheduleMirrorsOptions({ path: { shortId: scheduleShortId } }),
	});

	useEffect(() => {
		if (!hasChanges) {
			setAssignments(buildAssignments(currentMirrors));
		}
	}, [currentMirrors, hasChanges]);

	const { data: compatibility } = useQuery({
		...getMirrorCompatibilityOptions({ path: { shortId: scheduleShortId } }),
	});

	const updateMirrors = useMutation({
		...updateScheduleMirrorsMutation(),
		onSuccess: () => {
			toast.success("Mirror settings saved successfully");
			setHasChanges(false);
		},
		onError: (error) => {
			toast.error("Failed to save mirror settings", {
				description: parseError(error)?.message,
			});
		},
	});

	const { data: syncStatus, isLoading: isSyncStatusLoading } = useQuery({
		...getMirrorSyncStatusOptions({
			path: { shortId: scheduleShortId, mirrorShortId: syncDialogMirrorId ?? "" },
		}),
		enabled: syncDialogMirrorId !== null,
	});

	const toggleSnapshotSelection = (shortId: string) => {
		setSelectedSnapshotIds((prev) => {
			const next = new Set(prev);
			if (next.has(shortId)) {
				next.delete(shortId);
			} else {
				next.add(shortId);
			}
			return next;
		});
	};

	const toggleAllSnapshots = () => {
		if (!syncStatus) return;
		if (selectedSnapshotIds.size === syncStatus.missingSnapshots.length) {
			setSelectedSnapshotIds(new Set());
		} else {
			setSelectedSnapshotIds(new Set(syncStatus.missingSnapshots.map((s) => s.short_id)));
		}
	};

	const triggerSync = useMutation({
		...syncMirrorMutation(),
		onSuccess: () => {
			toast.success("Full sync started");
			closeSyncDialog();
		},
		onError: (error) => {
			toast.error("Failed to start sync", {
				description: parseError(error)?.message,
			});
		},
	});

	const compatibilityMap = useMemo(() => {
		const map = new Map<string, { compatible: boolean; reason: string | null }>();
		if (compatibility) {
			for (const item of compatibility) {
				map.set(item.repositoryId, { compatible: item.compatible, reason: item.reason });
			}
		}
		return map;
	}, [compatibility]);

	useEffect(() => {
		const abortController = new AbortController();

		addEventListener(
			"mirror:started",
			(event) => {
				if (event.scheduleId !== scheduleShortId) return;
				setAssignments((prev) => {
					const next = new Map(prev);
					const existing = next.get(event.repositoryId);
					if (!existing) return prev;
					next.set(event.repositoryId, { ...existing, lastCopyStatus: "in_progress", lastCopyError: null });
					return next;
				});
			},
			{ signal: abortController.signal },
		);

		addEventListener(
			"mirror:completed",
			(event) => {
				if (event.scheduleId !== scheduleShortId) return;
				setAssignments((prev) => {
					const next = new Map(prev);
					const existing = next.get(event.repositoryId);
					if (!existing) return prev;
					next.set(event.repositoryId, {
						...existing,
						lastCopyStatus: event.status ?? existing.lastCopyStatus,
						lastCopyError: event.error ?? null,
						lastCopyAt: Date.now(),
					});
					return next;
				});
			},
			{ signal: abortController.signal },
		);

		return () => abortController.abort();
	}, [addEventListener, scheduleShortId]);

	const addRepository = (repositoryId: string) => {
		const newAssignments = new Map(assignments);
		newAssignments.set(repositoryId, {
			repositoryId,
			enabled: true,
			lastCopyAt: null,
			lastCopyStatus: null,
			lastCopyError: null,
		});

		setAssignments(newAssignments);
		setHasChanges(true);
		setIsAddingNew(false);
	};

	const removeRepository = (repositoryId: string) => {
		const newAssignments = new Map(assignments);
		newAssignments.delete(repositoryId);
		setAssignments(newAssignments);
		setHasChanges(true);
	};

	const toggleEnabled = (repositoryId: string) => {
		const assignment = assignments.get(repositoryId);
		if (!assignment) return;

		const newAssignments = new Map(assignments);
		newAssignments.set(repositoryId, {
			...assignment,
			enabled: !assignment.enabled,
		});

		setAssignments(newAssignments);
		setHasChanges(true);
	};

	const handleSave = () => {
		const mirrorsList = Array.from(assignments.values()).map((a) => ({
			repositoryId: a.repositoryId,
			enabled: a.enabled,
		}));
		updateMirrors.mutate({
			path: { shortId: scheduleShortId },
			body: {
				mirrors: mirrorsList,
			},
		});
	};

	const handleReset = () => {
		setAssignments(buildAssignments(currentMirrors));
		setHasChanges(false);
	};

	const selectableRepositories =
		repositories?.filter((r) => {
			if (r.shortId === primaryRepositoryId) return false;
			if (assignments.has(r.shortId)) return false;
			return true;
		}) || [];

	const hasAvailableRepositories = selectableRepositories.some((r) => {
		const compat = compatibilityMap.get(r.shortId);
		return compat?.compatible !== false;
	});

	const assignedRepositories = Array.from(assignments.keys())
		.map((id) => repositories?.find((r) => r.shortId === id))
		.filter((r) => r !== undefined);

	const getStatusVariant = (status: string | null) => {
		if (status === "success") return "success";
		if (status === "error") return "error";
		if (status === "in_progress") return "info";
		return "neutral";
	};

	const getLabel = (assignment: MirrorAssignment) => {
		if (isSyncing(assignment)) {
			return "Syncing...";
		}
		if (assignment.lastCopyAt) {
			return formatTimeAgo(assignment.lastCopyAt);
		}
		return "Never";
	};

	const getStatusLabel = (assignment: MirrorAssignment) => {
		if (isSyncing(assignment)) {
			return "Mirror sync in progress";
		}
		if (assignment.lastCopyStatus === "error" && assignment.lastCopyError) {
			return assignment.lastCopyError;
		}
		if (assignment.lastCopyStatus === "success") {
			return "Last copy successful";
		}
		return "Never copied";
	};

	const selectedMirrorRepo = repositories.find((r) => r.shortId === syncDialogMirrorId);

	return (
		<Card>
			<CardHeader>
				<div className="flex items-center justify-between">
					<div>
						<CardTitle className="flex items-center gap-2">
							<Copy className="h-5 w-5" />
							Mirror Repositories
						</CardTitle>
						<CardDescription className="hidden @md:block mt-1">
							Configure secondary repositories where snapshots will be automatically copied after each backup
						</CardDescription>
					</div>
					{!isAddingNew && selectableRepositories.length > 0 && (
						<Button variant="outline" size="sm" onClick={() => setIsAddingNew(true)}>
							<Plus className="h-4 w-4 mr-2" />
							Add mirror
						</Button>
					)}
				</div>
			</CardHeader>
			<CardContent>
				{isAddingNew && (
					<div className="mb-6 flex items-center gap-2 max-w-md">
						<Select onValueChange={addRepository}>
							<SelectTrigger className="w-full">
								<SelectValue placeholder="Select a repository to mirror to..." />
							</SelectTrigger>
							<SelectContent>
								{selectableRepositories.map((repository) => {
									const compat = compatibilityMap.get(repository.shortId);

									return (
										<Tooltip key={repository.shortId}>
											<TooltipTrigger asChild>
												<div>
													<SelectItem value={repository.shortId} disabled={!compat?.compatible}>
														<div className="flex items-center gap-2">
															<RepositoryIcon backend={repository.type} className="h-4 w-4" />
															<span>{repository.name}</span>
															<span className="text-xs uppercase text-muted-foreground">({repository.type})</span>
														</div>
													</SelectItem>
												</div>
											</TooltipTrigger>
											<TooltipContent side="right" className={cn("max-w-xs", { hidden: compat?.compatible })}>
												<p>{compat?.reason || "This repository is not compatible for mirroring."}</p>
												<p className="mt-1 text-xs text-muted-foreground">
													Consider creating a new backup scheduler with the desired destination instead.
												</p>
											</TooltipContent>
										</Tooltip>
									);
								})}
								{!hasAvailableRepositories && selectableRepositories.length > 0 && (
									<div className="px-2 py-3 text-sm text-muted-foreground text-center">
										All available repositories have conflicting backends.
										<br />
										<span className="text-xs">
											Consider creating a new backup scheduler with the desired destination instead.
										</span>
									</div>
								)}
							</SelectContent>
						</Select>
						<Button variant="ghost" size="sm" onClick={() => setIsAddingNew(false)}>
							Cancel
						</Button>
					</div>
				)}

				{assignedRepositories.length === 0 ? (
					<div className="flex flex-col items-center justify-center py-8 text-center text-muted-foreground">
						<Copy className="h-8 w-8 mb-2 opacity-20" />
						<p className="text-sm">No mirror repositories configured for this schedule.</p>
						<p className="text-xs mt-1">Click "Add mirror" to replicate backups to additional repositories.</p>
					</div>
				) : (
					<div className="rounded-md border">
						<Table>
							<TableHeader>
								<TableRow>
									<TableHead>Repository</TableHead>
									<TableHead className="text-center w-25">Enabled</TableHead>
									<TableHead className="w-45">Last Copy</TableHead>
									<TableHead className="w-12.5"></TableHead>
								</TableRow>
							</TableHeader>
							<TableBody>
								{assignedRepositories.map((repository) => {
									const assignment = assignments.get(repository.shortId);
									if (!assignment) return null;

									return (
										<TableRow key={repository.shortId}>
											<TableCell>
												<div className="flex items-center gap-2">
													<Link
														to="/repositories/$repositoryId"
														params={{ repositoryId: repository.shortId }}
														className="hover:underline flex items-center gap-2"
													>
														<RepositoryIcon backend={repository.type} className="h-4 w-4" />
														<span className="font-medium">{repository.name}</span>
													</Link>
													<Badge variant="outline" className="text-[10px] align-middle">
														{repository.type}
													</Badge>
												</div>
											</TableCell>
											<TableCell className="text-center">
												<Switch
													className="align-middle"
													checked={assignment.enabled}
													onCheckedChange={() => toggleEnabled(repository.shortId)}
												/>
											</TableCell>
											<TableCell>
												<div className="flex items-center gap-2">
													<div className="w-3 shrink-0 mr-1">
														<StatusDot
															variant={getStatusVariant(assignment.lastCopyStatus)}
															label={getStatusLabel(assignment)}
															animated={isSyncing(assignment)}
														/>
													</div>
													<span className="text-sm text-muted-foreground">{getLabel(assignment)}</span>
												</div>
											</TableCell>
											<TableCell>
												<div className="flex items-center gap-1">
													<Tooltip>
														<TooltipTrigger asChild>
															<Button
																variant="ghost"
																size="icon"
																onClick={() => openSyncDialog(repository.shortId)}
																disabled={isSyncing(assignment) || hasChanges}
																className="h-8 w-8 text-muted-foreground hover:text-foreground"
															>
																<RefreshCw className={cn("h-4 w-4", isSyncing(assignment) && "animate-spin")} />
															</Button>
														</TooltipTrigger>
														<TooltipContent>Sync more snapshots</TooltipContent>
													</Tooltip>
													<Button
														variant="ghost"
														size="icon"
														onClick={() => removeRepository(repository.shortId)}
														className="h-8 w-8 text-muted-foreground hover:text-destructive align-baseline"
													>
														<Trash2 className="h-4 w-4" />
													</Button>
												</div>
											</TableCell>
										</TableRow>
									);
								})}
							</TableBody>
						</Table>
					</div>
				)}

				{hasChanges && (
					<div className="flex gap-2 justify-end mt-4 pt-4">
						<Button variant="outline" size="sm" onClick={handleReset}>
							Cancel
						</Button>
						<Button variant="default" size="sm" onClick={handleSave} loading={updateMirrors.isPending}>
							Save changes
						</Button>
					</div>
				)}

				<Dialog open={syncDialogOpen} onOpenChange={(open) => !open && closeSyncDialog()}>
					<DialogContent>
						<DialogHeader>
							<DialogTitle>Sync snapshots</DialogTitle>
							<DialogDescription>
								{`Sync missing snapshots to ${selectedMirrorRepo?.name || "mirror repository"}.`}
							</DialogDescription>
						</DialogHeader>

						{isSyncStatusLoading && !syncStatus ? (
							<div className="py-6 text-center text-muted-foreground text-sm">Loading snapshot status...</div>
						) : syncStatus && syncStatus.missingSnapshots.length === 0 ? (
							<div className="py-6 text-center text-muted-foreground text-sm">
								All {syncStatus.sourceCount} snapshots are already synced to this mirror.
							</div>
						) : syncStatus ? (
							<div className="space-y-3">
								<p className="text-sm text-muted-foreground">
									{syncStatus.missingSnapshots.length} of {syncStatus.sourceCount} snapshots are missing in this mirror.
								</p>
								<div className="rounded-md border max-h-64 overflow-y-auto">
									<Table>
										<TableHeader>
											<TableRow>
												<TableHead className="w-10">
													<Checkbox
														checked={selectedSnapshotIds.size === syncStatus.missingSnapshots.length}
														onCheckedChange={toggleAllSnapshots}
													/>
												</TableHead>
												<TableHead>ID</TableHead>
												<TableHead>Date</TableHead>
												<TableHead className="text-right">Size</TableHead>
											</TableRow>
										</TableHeader>
										<TableBody>
											{syncStatus.missingSnapshots.map((snapshot) => (
												<TableRow
													key={snapshot.short_id}
													className="cursor-pointer"
													onClick={() => toggleSnapshotSelection(snapshot.short_id)}
												>
													<TableCell onClick={(e) => e.stopPropagation()}>
														<Checkbox
															checked={selectedSnapshotIds.has(snapshot.short_id)}
															onCheckedChange={() => toggleSnapshotSelection(snapshot.short_id)}
														/>
													</TableCell>
													<TableCell className="font-mono text-xs">{snapshot.short_id}</TableCell>
													<TableCell className="text-sm">{formatDateTime(new Date(snapshot.time))}</TableCell>
													<TableCell className="text-right text-sm">
														<ByteSize bytes={snapshot.size} base={1024} />
													</TableCell>
												</TableRow>
											))}
										</TableBody>
									</Table>
								</div>
							</div>
						) : null}

						<DialogFooter>
							<Button variant="outline" onClick={closeSyncDialog}>
								Cancel
							</Button>
							<Button
								onClick={() => {
									if (syncDialogMirrorId) {
										triggerSync.mutate({
											path: { shortId: scheduleShortId, mirrorShortId: syncDialogMirrorId },
											body: { snapshotIds: Array.from(selectedSnapshotIds) },
										});
									}
								}}
								loading={triggerSync.isPending}
								disabled={selectedSnapshotIds.size === 0}
							>
								Sync {selectedSnapshotIds.size} snapshots
							</Button>
						</DialogFooter>
					</DialogContent>
				</Dialog>
			</CardContent>
		</Card>
	);
};
