import { useMutation, useQuery } from "@tanstack/react-query";
import { Copy, Plus, 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 { Tooltip, TooltipContent, TooltipTrigger } from "~/client/components/ui/tooltip";
import {
	getScheduleMirrorsOptions,
	getMirrorCompatibilityOptions,
	updateScheduleMirrorsMutation,
} 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 { formatDistanceToNow } from "date-fns";
import { Link } from "react-router";
import { cn } from "~/client/lib/utils";

type Props = {
	scheduleId: number;
	primaryRepositoryId: string;
	repositories: Repository[];
};

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

export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, repositories }: Props) => {
	const [assignments, setAssignments] = useState<Map<string, MirrorAssignment>>(new Map());
	const [hasChanges, setHasChanges] = useState(false);
	const [isAddingNew, setIsAddingNew] = useState(false);

	const { data: currentMirrors } = useQuery({
		...getScheduleMirrorsOptions({ path: { scheduleId: scheduleId.toString() } }),
	});

	const { data: compatibility } = useQuery({
		...getMirrorCompatibilityOptions({ path: { scheduleId: scheduleId.toString() } }),
	});

	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 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(() => {
		if (currentMirrors && !hasChanges) {
			const map = new Map<string, MirrorAssignment>();
			for (const mirror of currentMirrors) {
				map.set(mirror.repositoryId, {
					repositoryId: mirror.repositoryId,
					enabled: mirror.enabled,
					lastCopyAt: mirror.lastCopyAt,
					lastCopyStatus: mirror.lastCopyStatus,
					lastCopyError: mirror.lastCopyError,
				});
			}

			setAssignments(map);
		}
	}, [currentMirrors, hasChanges]);

	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: { scheduleId: scheduleId.toString() },
			body: {
				mirrors: mirrorsList,
			},
		});
	};

	const handleReset = () => {
		if (currentMirrors) {
			const map = new Map<string, MirrorAssignment>();
			for (const mirror of currentMirrors) {
				map.set(mirror.repositoryId, {
					repositoryId: mirror.repositoryId,
					enabled: mirror.enabled,
					lastCopyAt: mirror.lastCopyAt,
					lastCopyStatus: mirror.lastCopyStatus,
					lastCopyError: mirror.lastCopyError,
				});
			}
			setAssignments(map);
			setHasChanges(false);
		}
	};

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

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

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

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

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

	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>
							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.id);

									return (
										<Tooltip key={repository.id}>
											<TooltipTrigger asChild>
												<div>
													<SelectItem value={repository.id} 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-[100px]">Enabled</TableHead>
									<TableHead className="w-[180px]">Last Copy</TableHead>
									<TableHead className="w-[50px]"></TableHead>
								</TableRow>
							</TableHeader>
							<TableBody>
								{assignedRepositories.map((repository) => {
									const assignment = assignments.get(repository.id);
									if (!assignment) return null;

									return (
										<TableRow key={repository.id}>
											<TableCell>
												<div className="flex items-center gap-2">
													<Link
														to={`/repositories/${repository.name}`}
														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.id)}
												/>
											</TableCell>
											<TableCell>
												{assignment.lastCopyAt ? (
													<div className="flex items-center gap-2">
														<StatusDot
															variant={getStatusVariant(assignment.lastCopyStatus)}
															label={getStatusLabel(assignment)}
														/>
														<span className="text-sm text-muted-foreground">
															{formatDistanceToNow(new Date(assignment.lastCopyAt), { addSuffix: true })}
														</span>
													</div>
												) : (
													<span className="text-sm text-muted-foreground">Never</span>
												)}
											</TableCell>
											<TableCell>
												<Button
													variant="ghost"
													size="icon"
													onClick={() => removeRepository(repository.id)}
													className="h-8 w-8 text-muted-foreground hover:text-destructive align-baseline"
												>
													<Trash2 className="h-4 w-4" />
												</Button>
											</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>
				)}
			</CardContent>
		</Card>
	);
};
