"use client";

import {
  ArrowPathIcon,
  CheckCircleIcon,
  EyeSlashIcon,
  FolderIcon,
  Squares2X2Icon,
  TrashIcon,
} from "@heroicons/react/24/outline";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import Link from "next/link";
import { useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";
import { get, post } from "../../lib/api";
import ConfirmationDialog from "../components/ConfirmationDialog";
import LoadingCard from "../components/Loading";
import { useNotifications } from "../components/NotificationContext";
import { useAppContext } from "../providers/AppContext";

interface DuplicateGroup {
  id: number;
  dataset: string;
  destination: string;
  hash: string;
  size: number;
  files: string[];
  status: string;
  created_at?: string;
  reviewed_at?: string;
  note?: string;
}

type SortField = "dataset" | "count" | "size" | "created_at";

type DeleteSelection = {
  isOpen: boolean;
  groups: Array<{ id: number; files: string[] }>;
};

function formatBytes(bytes: number) {
  if (!bytes) return "0 B";
  const sizes = ["B", "KB", "MB", "GB", "TB"];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
}

function makeFileKey(groupId: number, path: string) {
  return `${groupId}::${encodeURIComponent(path)}`;
}

function parseFileKey(key: string) {
  const [idStr, encodedPath] = key.split("::");
  return { groupId: Number(idStr), path: decodeURIComponent(encodedPath) };
}

export default function DuplicateList() {
  const queryClient = useQueryClient();
  const { datasets } = useAppContext();
  const { addNotification } = useNotifications();

  const {
    data: duplicateGroups,
    isLoading,
    error,
    refetch,
  } = useQuery<DuplicateGroup[]>({
    queryKey: ["duplicate-files"],
    queryFn: async () => get("/maintenance/duplicates?status=pending"),
  });

  const [enabledDatasets, setEnabledDatasets] = useState<Set<string>>(
    new Set()
  );
  const [searchTerm, setSearchTerm] = useState("");
  const [sortField, setSortField] = useState<SortField>("count");
  const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc");
  const [expandedRows, setExpandedRows] = useState<Set<number>>(new Set());
  const [selectedGroups, setSelectedGroups] = useState<Set<number>>(new Set());
  const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
  const [deleteSelection, setDeleteSelection] = useState<DeleteSelection>({
    isOpen: false,
    groups: [],
  });

  // Initialize enabled datasets on first load
  useEffect(() => {
    if (datasets && datasets.length > 0 && enabledDatasets.size === 0) {
      const names = datasets
        .map((p: string) => p.split("/").pop())
        .filter(Boolean) as string[];
      setEnabledDatasets(new Set(names));
    }
  }, [datasets]);

  const [scanController, setScanController] = useState<AbortController | null>(
    null
  );

  const scanMutation = useMutation({
    mutationFn: ({ signal }: { signal?: AbortSignal } = {}) =>
      post("/maintenance/duplicates/scan", { resetExisting: false }, signal),
    onSuccess: () => {
      toast.success("Duplicate scan completed");
      addNotification({
        type: "info",
        title: "Scan Finished",
        message: "Duplicate scan completed. Refreshing results.",
      });
      refetch();
    },
    onError: (err: any) => {
      if (err.message === "Request aborted") return;
      console.error(err);
      toast.error("Failed to start duplicate scan");
    },
    onSettled: () => setScanController(null),
  });

  const markNotDuplicateMutation = useMutation({
    mutationFn: async (ids: number[]) => {
      await Promise.all(
        ids.map((id) =>
          post(`/maintenance/duplicates/${id}/mark`, {
            status: "reviewed",
            note: "not_duplicate",
          })
        )
      );
    },
    onSuccess: () => {
      toast.success("Marked as not duplicate");
      setSelectedGroups(new Set());
      queryClient.invalidateQueries({ queryKey: ["duplicate-files"] });
    },
    onError: (err: any) => {
      console.error(err);
      toast.error("Failed to update duplicates");
    },
  });

  const deleteMutation = useMutation({
    mutationFn: async (groups: Array<{ id: number; files: string[] }>) => {
      if (!groups.length) return;
      await Promise.all(
        groups.map((group) =>
          post(`/maintenance/duplicates/${group.id}/purge`, {
            files: group.files,
          })
        )
      );
    },
    onSuccess: () => {
      toast.success("Selected files deleted");
      setSelectedFiles(new Set());
      setSelectedGroups(new Set());
      setDeleteSelection({ isOpen: false, groups: [] });
      queryClient.invalidateQueries({ queryKey: ["duplicate-files"] });
    },
    onError: (err: any) => {
      console.error(err);
      toast.error("Failed to delete selected files");
    },
  });

  const filteredData = useMemo(() => {
    if (!duplicateGroups) return [] as DuplicateGroup[];

    return duplicateGroups
      .filter((group) => enabledDatasets.has(group.dataset))
      .filter((group) => {
        if (!searchTerm) return true;
        const term = searchTerm.toLowerCase();
        const inFiles = group.files.some((f) => f.toLowerCase().includes(term));
        const inDest = group.destination?.toLowerCase().includes(term);
        return inFiles || inDest || group.hash.toLowerCase().includes(term);
      })
      .sort((a, b) => {
        let aVal: number | string = 0;
        let bVal: number | string = 0;

        switch (sortField) {
          case "dataset":
            aVal = a.dataset;
            bVal = b.dataset;
            break;
          case "count":
            aVal = a.files.length;
            bVal = b.files.length;
            break;
          case "size":
            aVal = a.size;
            bVal = b.size;
            break;
          case "created_at":
            aVal = a.created_at || "";
            bVal = b.created_at || "";
            break;
        }

        if (aVal < bVal) return sortDirection === "asc" ? -1 : 1;
        if (aVal > bVal) return sortDirection === "asc" ? 1 : -1;
        return 0;
      });
  }, [duplicateGroups, enabledDatasets, searchTerm, sortField, sortDirection]);

  const toggleDataset = (dataset: string) => {
    const next = new Set(enabledDatasets);
    if (next.has(dataset)) next.delete(dataset);
    else next.add(dataset);
    setEnabledDatasets(next);
  };

  const toggleExpanded = (id: number) => {
    const next = new Set(expandedRows);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    setExpandedRows(next);
  };

  const toggleGroupSelection = (id: number) => {
    const next = new Set(selectedGroups);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    setSelectedGroups(next);
  };

  const toggleFileSelection = (groupId: number, filePath: string) => {
    const key = makeFileKey(groupId, filePath);
    const next = new Set(selectedFiles);
    if (next.has(key)) next.delete(key);
    else next.add(key);
    setSelectedFiles(next);
  };

  const selectGroupFiles = (group: DuplicateGroup, checked: boolean) => {
    const next = new Set(selectedFiles);
    for (const file of group.files) {
      const key = makeFileKey(group.id, file);
      if (checked) next.add(key);
      else next.delete(key);
    }
    setSelectedFiles(next);
  };

  const handleBatchDelete = () => {
    if (!duplicateGroups) return;
    const grouped = new Map<number, string[]>();

    selectedFiles.forEach((key) => {
      const { groupId, path } = parseFileKey(key);
      const arr = grouped.get(groupId) || [];
      arr.push(path);
      grouped.set(groupId, arr);
    });

    if (grouped.size === 0) return;

    const groups = Array.from(grouped.entries()).map(([id, files]) => ({
      id,
      files,
    }));

    setDeleteSelection({ isOpen: true, groups });
  };

  const confirmDelete = () => {
    deleteMutation.mutate(deleteSelection.groups);
  };

  const markSelectedNotDuplicate = () => {
    if (selectedGroups.size === 0) return;
    markNotDuplicateMutation.mutate(Array.from(selectedGroups));
  };

  if (isLoading && !duplicateGroups) {
    return <LoadingCard message="Loading duplicate files..." />;
  }

  if (error) {
    return (
      <div className="text-center p-8 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
        <h2 className="text-lg font-semibold text-red-800 dark:text-red-200 mb-2">
          Failed to load duplicates
        </h2>
        <p className="text-gray-600 dark:text-gray-400 mb-4">
          There was an error loading duplicate file data.
        </p>
        <button
          onClick={() =>
            queryClient.refetchQueries({ queryKey: ["duplicate-files"] })
          }
          className="inline-flex items-center px-4 py-2 text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700"
        >
          Retry
        </button>
      </div>
    );
  }

  return (
    <div className="space-y-6">
      <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
        <div>
          <h3 className="text-lg font-semibold text-gray-900 dark:text-white">
            Duplicate Files
          </h3>
          <p className="text-sm text-gray-600 dark:text-gray-400">
            Review detected duplicates, delete unwanted files, or mark them as
            safe.
          </p>
        </div>
        <div className="flex flex-wrap gap-2">
          <Link
            href="/indexing"
            className="inline-flex items-center gap-2 rounded-md bg-purple-600 px-3 py-2 text-sm font-medium text-white shadow-sm hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
          >
            <FolderIcon className="h-4 w-4" />
            Manage Index
          </Link>
          <button
            onClick={() => {
              addNotification({
                type: "info",
                title: "Scan Started",
                message: "Scanning for duplicates in /Volumes/Shares/.Private",
              });
              const controller = new AbortController();
              setScanController(controller);
              scanMutation.mutate({ signal: controller.signal });
            }}
            disabled={scanMutation.isPending}
            className="inline-flex items-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
          >
            <ArrowPathIcon
              className={`h-4 w-4 ${scanMutation.isPending ? "animate-spin" : ""}`}
            />
            {scanMutation.isPending ? "Scanning..." : "Rescan"}
          </button>
          {scanController && (
            <button
              onClick={() => {
                scanController.abort();
                setScanController(null);
              }}
              className="inline-flex items-center gap-2 rounded-md bg-gray-600 px-3 py-2 text-sm font-medium text-white shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
            >
              Cancel Scan
            </button>
          )}
          <button
            onClick={markSelectedNotDuplicate}
            disabled={selectedGroups.size === 0}
            className="inline-flex items-center gap-2 rounded-md bg-emerald-600 px-3 py-2 text-sm font-medium text-white shadow-sm disabled:opacity-60 hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2"
          >
            <CheckCircleIcon className="h-4 w-4" />
            Mark Not Duplicate
          </button>
          <button
            onClick={handleBatchDelete}
            disabled={selectedFiles.size === 0}
            className="inline-flex items-center gap-2 rounded-md bg-red-600 px-3 py-2 text-sm font-medium text-white shadow-sm disabled:opacity-60 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
          >
            <TrashIcon className="h-4 w-4" />
            Delete Selected Files
          </button>
        </div>
      </div>

      <div className="rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-4 shadow-sm space-y-4">
        <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
          <div className="flex items-center gap-3">
            <Squares2X2Icon className="h-5 w-5 text-gray-500" />
            <div>
              <div className="text-sm text-gray-700 dark:text-gray-300">
                Showing {filteredData.length} group
                {filteredData.length === 1 ? "" : "s"}
              </div>
              <div className="text-xs text-gray-500 dark:text-gray-400">
                {selectedFiles.size} file{selectedFiles.size === 1 ? "" : "s"}{" "}
                selected
              </div>
            </div>
          </div>
          <div className="flex flex-wrap gap-3">
            <input
              type="text"
              placeholder="Search destination or file path..."
              value={searchTerm}
              onChange={(e) => setSearchTerm(e.target.value)}
              className="w-64 min-w-[200px] rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
            <select
              className="rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
              value={sortField}
              onChange={(e) => setSortField(e.target.value as SortField)}
            >
              <option value="count">Most files</option>
              <option value="size">Largest size</option>
              <option value="dataset">Dataset</option>
              <option value="created_at">Newest</option>
            </select>
            <button
              onClick={() =>
                setSortDirection((prev) => (prev === "asc" ? "desc" : "asc"))
              }
              className="rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700"
            >
              {sortDirection === "asc" ? "Asc" : "Desc"}
            </button>
          </div>
        </div>

        <div className="flex flex-wrap gap-2 items-center">
          <span className="text-sm text-gray-600 dark:text-gray-400">
            Datasets:
          </span>
          {datasets?.map((ds: string) => {
            const name = ds.split("/").pop();
            if (!name) return null;
            return (
              <label
                key={name}
                className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300"
              >
                <input
                  type="checkbox"
                  checked={enabledDatasets.has(name)}
                  onChange={() => toggleDataset(name)}
                  className="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
                />
                {name}
              </label>
            );
          })}
        </div>

        <div className="overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900">
          <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
            <thead className="bg-gray-50 dark:bg-gray-800">
              <tr>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Group
                </th>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Dataset
                </th>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Destination
                </th>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Files
                </th>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Size
                </th>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Hash
                </th>
                <th className="px-3 py-3 text-right text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Actions
                </th>
              </tr>
            </thead>
            <tbody className="divide-y divide-gray-200 dark:divide-gray-800">
              {filteredData.length === 0 && (
                <tr>
                  <td
                    colSpan={7}
                    className="px-3 py-4 text-center text-sm text-gray-500 dark:text-gray-400"
                  >
                    No duplicate groups match the current filters.
                  </td>
                </tr>
              )}
              {filteredData.map((group) => {
                const isExpanded = expandedRows.has(group.id);
                const allSelected = group.files.every((f) =>
                  selectedFiles.has(makeFileKey(group.id, f))
                );
                return (
                  <>
                    <tr
                      key={group.id}
                      className="hover:bg-gray-50 dark:hover:bg-gray-800"
                    >
                      <td className="px-3 py-3 text-sm text-gray-700 dark:text-gray-200 whitespace-nowrap">
                        <button
                          onClick={() => toggleExpanded(group.id)}
                          className="mr-2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
                        >
                          {isExpanded ? "−" : "+"}
                        </button>
                        <input
                          type="checkbox"
                          checked={selectedGroups.has(group.id)}
                          onChange={() => toggleGroupSelection(group.id)}
                          className="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
                        />
                      </td>
                      <td className="px-3 py-3 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">
                        <span className="px-2 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded text-xs font-medium">
                          {group.dataset}
                        </span>
                      </td>
                      <td className="px-3 py-3 text-sm text-gray-700 dark:text-gray-300 max-w-xs">
                        <div className="truncate" title={group.destination}>
                          {group.destination}
                        </div>
                      </td>
                      <td className="px-3 py-3 text-sm text-gray-700 dark:text-gray-300">
                        {group.files.length}
                      </td>
                      <td className="px-3 py-3 text-sm text-gray-700 dark:text-gray-300">
                        {formatBytes(group.size)}
                      </td>
                      <td className="px-3 py-3 text-sm text-gray-500 dark:text-gray-400">
                        {group.hash.slice(0, 10)}…
                      </td>
                      <td className="px-3 py-3 text-sm text-right whitespace-nowrap">
                        <div className="inline-flex items-center gap-1">
                          <button
                            onClick={() =>
                              markNotDuplicateMutation.mutate([group.id])
                            }
                            className="inline-flex items-center gap-1 rounded-md border border-gray-300 dark:border-gray-700 px-3 py-1 text-xs font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800"
                          >
                            <EyeSlashIcon className="h-4 w-4" /> Ignore
                          </button>
                          <button
                            onClick={() =>
                              selectGroupFiles(group, !allSelected)
                            }
                            className="inline-flex items-center gap-1 rounded-md border border-gray-300 dark:border-gray-700 px-3 py-1 text-xs font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800"
                          >
                            {allSelected ? "Unselect files" : "Select files"}
                          </button>
                        </div>
                      </td>
                    </tr>
                    {isExpanded && (
                      <tr
                        key={`${group.id}-expanded`}
                        className="bg-gray-50 dark:bg-gray-800/60"
                      >
                        <td colSpan={7} className="px-3 py-3">
                          <div className="flex items-center justify-between mb-2">
                            <div className="text-sm font-medium text-gray-800 dark:text-gray-100">
                              Files in group
                            </div>
                            <div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
                              <span>{selectedFiles.size} selected</span>
                              <button
                                onClick={() => selectGroupFiles(group, true)}
                                className="rounded-md border border-gray-300 dark:border-gray-700 px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-700"
                              >
                                Select all
                              </button>
                              <button
                                onClick={() => selectGroupFiles(group, false)}
                                className="rounded-md border border-gray-300 dark:border-gray-700 px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-700"
                              >
                                Clear
                              </button>
                            </div>
                          </div>
                          <div className="space-y-2 max-h-64 overflow-y-auto pr-1">
                            {group.files.map((file) => {
                              const key = makeFileKey(group.id, file);
                              return (
                                <label
                                  key={key}
                                  className="flex items-center gap-3 rounded-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-800 dark:text-gray-200"
                                >
                                  <input
                                    type="checkbox"
                                    checked={selectedFiles.has(key)}
                                    onChange={() =>
                                      toggleFileSelection(group.id, file)
                                    }
                                    className="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
                                  />
                                  <span className="truncate" title={file}>
                                    {file}
                                  </span>
                                </label>
                              );
                            })}
                          </div>
                        </td>
                      </tr>
                    )}
                  </>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>

      <ConfirmationDialog
        isOpen={deleteSelection.isOpen}
        title="Delete selected files"
        message="These files will be removed from disk. This action cannot be undone."
        confirmText="Delete"
        cancelText="Cancel"
        type="danger"
        isLoading={deleteMutation.isPending}
        onConfirm={confirmDelete}
        onClose={() => setDeleteSelection({ isOpen: false, groups: [] })}
      />
    </div>
  );
}
