"use client";

import { useRef, useState, useTransition, type ChangeEvent, type FormEvent } from "react";
import { useRouter } from "next/navigation";
import { CheckCircle2, FileSpreadsheet, LoaderCircle, Upload, X } from "lucide-react";
import { importProjectsAction, previewImportAction, type ImportResult } from "@/app/(dashboard)/apps/actions";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/cn";
import { IMPORT_COLUMNS, type ImportPreview, type ImportPreviewRow } from "@/lib/import/rows";

type Step =
  | { name: "upload" }
  | { name: "preview"; fileName: string; preview: ImportPreview }
  | { name: "done"; result: Extract<ImportResult, { ok: true }> };

const IMPORTABLE = new Set<ImportPreviewRow["kind"]>(["new", "existing"]);

/** Rows ticked by default: everything new, and existing apps whose details differ from the sheet. */
function defaultSelection(preview: ImportPreview): Set<number> {
  return new Set(
    preview.rows.filter((row) => row.kind === "new" || (row.kind === "existing" && row.existing?.changed)).map((row) => row.line),
  );
}

const KIND_LABEL: Record<ImportPreviewRow["kind"], { label: string; className: string }> = {
  new: { label: "New", className: "border-emerald-200 bg-emerald-50 text-emerald-800" },
  existing: { label: "Exists", className: "border-sky-200 bg-sky-50 text-sky-800" },
  duplicate: { label: "Duplicate", className: "border-slate-200 bg-slate-100 text-slate-600" },
  invalid: { label: "Invalid", className: "border-red-200 bg-red-50 text-red-800" },
};

export function ImportAppsButton({ variant = "secondary" }: { variant?: "primary" | "secondary" }) {
  const router = useRouter();
  const dialogRef = useRef<HTMLDialogElement>(null);
  const [step, setStep] = useState<Step>({ name: "upload" });
  const [selected, setSelected] = useState<Set<number>>(new Set());
  const [error, setError] = useState<string | null>(null);
  const [pending, startTransition] = useTransition();

  function open() {
    setStep({ name: "upload" });
    setError(null);
    dialogRef.current?.showModal();
  }

  function close() {
    dialogRef.current?.close();
    // A finished import changed the list behind the dialog.
    if (step.name === "done") router.refresh();
  }

  function preview(form: HTMLFormElement) {
    const formData = new FormData(form);
    startTransition(async () => {
      setError(null);
      const result = await previewImportAction(formData);
      if (!result.ok) {
        form.reset(); // so picking the same file again fires onChange
        return setError(result.error);
      }
      setSelected(defaultSelection(result.preview));
      setStep({ name: "preview", fileName: result.fileName, preview: result.preview });
    });
  }

  // The review step opens as soon as a file is chosen — no extra button press.
  function fileChosen(event: ChangeEvent<HTMLInputElement>) {
    if (event.currentTarget.files?.length) preview(event.currentTarget.form!);
  }

  // Keyboard fallback (Enter on the input) does the same thing.
  function upload(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    preview(event.currentTarget);
  }

  function runImport() {
    if (step.name !== "preview") return;
    const rows = step.preview.rows
      .filter((row) => selected.has(row.line))
      .map(({ projectCode, projectName, productionUrl }) => ({ projectCode, projectName, productionUrl }));
    startTransition(async () => {
      setError(null);
      const result = await importProjectsAction(rows);
      if (!result.ok) return setError(result.error);
      setStep({ name: "done", result });
    });
  }

  function toggle(line: number, checked: boolean) {
    setSelected((current) => {
      const next = new Set(current);
      if (checked) next.add(line);
      else next.delete(line);
      return next;
    });
  }

  function toggleGroup(kind: "new" | "existing", checked: boolean) {
    if (step.name !== "preview") return;
    setSelected((current) => {
      const next = new Set(current);
      for (const row of step.preview.rows) {
        if (row.kind !== kind) continue;
        if (checked) next.add(row.line);
        else next.delete(row.line);
      }
      return next;
    });
  }

  return (
    <>
      <Button variant={variant} onClick={open}>
        <Upload className="size-4" aria-hidden />
        Import Apps
      </Button>

      <dialog
        ref={dialogRef}
        aria-labelledby="import-title"
        onClose={() => step.name === "done" && router.refresh()}
        className={cn(
          "m-auto w-[calc(100%-2rem)] rounded-lg border border-slate-200 bg-white p-0 text-left shadow-xl backdrop:bg-slate-950/40",
          step.name === "preview" ? "max-w-[min(96vw,90rem)]" : "max-w-3xl",
        )}
      >
        <div className="flex items-center justify-between border-b border-slate-200 px-5 py-3">
          <h2 id="import-title" className="text-sm font-semibold text-slate-900">
            {step.name === "upload" && "Import apps from a sheet"}
            {step.name === "preview" && `Review import — ${step.fileName}`}
            {step.name === "done" && "Import complete"}
          </h2>
          <button
            type="button"
            onClick={close}
            aria-label="Close"
            className="rounded-md p-1 text-slate-500 hover:bg-slate-100 hover:text-slate-900"
          >
            <X className="size-4" aria-hidden />
          </button>
        </div>

        {step.name === "upload" && (
          <form onSubmit={upload} className="p-5">
            <p className="text-sm text-slate-600">
              Upload the sheet as <span className="font-medium text-slate-900">.csv</span> or{" "}
              <span className="font-medium text-slate-900">.xlsx</span>. The first worksheet is read; it needs a header row with
              the columns{" "}
              {IMPORT_COLUMNS.map((column, i) => (
                <span key={column}>
                  <span className="font-medium text-slate-900">{column}</span>
                  {i < IMPORT_COLUMNS.length - 1 ? ", " : "."}
                </span>
              ))}
            </p>
            <label
              className={cn(
                "mt-4 flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-slate-300 bg-slate-50 px-4 py-8 text-center",
                pending ? "opacity-70" : "cursor-pointer hover:border-slate-400",
              )}
            >
              {pending ? (
                <LoaderCircle className="size-6 animate-spin text-slate-500" aria-hidden />
              ) : (
                <FileSpreadsheet className="size-6 text-slate-400" aria-hidden />
              )}
              <span className="text-sm text-slate-600" role="status">
                {pending ? "Reading the sheet…" : "Choose a file (up to 4 MB) — the review opens automatically"}
              </span>
              <input
                type="file"
                name="file"
                required
                disabled={pending}
                onChange={fileChosen}
                accept=".csv,.tsv,.txt,.xlsx,text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                className="text-xs text-slate-600 file:mr-3 file:rounded-md file:border file:border-slate-300 file:bg-white file:px-3 file:py-1.5 file:text-xs file:font-medium file:text-slate-700 hover:file:bg-slate-50 disabled:pointer-events-none"
              />
            </label>
            <p className="mt-3 text-xs text-slate-500">
              Nothing is saved yet — the next step shows which apps are new and which already exist, and you choose what to
              import. Existing apps are matched by Project Code.
            </p>
            {error && (
              <p role="alert" className="mt-3 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
                {error}
              </p>
            )}
            <div className="mt-5 flex justify-end gap-2 border-t border-slate-200 pt-4">
              <Button variant="secondary" onClick={close} disabled={pending}>
                Cancel
              </Button>
            </div>
          </form>
        )}

        {step.name === "preview" && (
          <PreviewStep
            preview={step.preview}
            selected={selected}
            onToggle={toggle}
            onToggleGroup={toggleGroup}
            onBack={() => setStep({ name: "upload" })}
            onImport={runImport}
            pending={pending}
            error={error}
          />
        )}

        {step.name === "done" && (
          <div className="p-5">
            <p className="flex items-center gap-2 text-sm font-medium text-slate-900">
              <CheckCircle2 className="size-4 text-emerald-600" aria-hidden />
              {step.result.created} added, {step.result.updated} updated
              {step.result.unchanged > 0 && `, ${step.result.unchanged} already up to date`}
              {step.result.failed.length > 0 && `, ${step.result.failed.length} failed`}.
            </p>
            {step.result.created + step.result.updated > 0 && (
              <p className="mt-1 text-sm text-slate-600">
                New and re-targeted apps are being health-checked in the background; their status appears on the dashboard
                shortly.
              </p>
            )}
            {step.result.failed.length > 0 && (
              <ul className="mt-3 space-y-1 text-sm text-red-700">
                {step.result.failed.map((failure) => (
                  <li key={failure.projectCode}>
                    <span className="font-mono text-xs">{failure.projectCode}</span> — {failure.error}
                  </li>
                ))}
              </ul>
            )}
            <div className="mt-5 flex justify-end border-t border-slate-200 pt-4">
              <Button onClick={close}>Done</Button>
            </div>
          </div>
        )}
      </dialog>
    </>
  );
}

type PreviewStepProps = {
  preview: ImportPreview;
  selected: Set<number>;
  onToggle: (line: number, checked: boolean) => void;
  onToggleGroup: (kind: "new" | "existing", checked: boolean) => void;
  onBack: () => void;
  onImport: () => void;
  pending: boolean;
  error: string | null;
};

type PreviewFilter = "all" | ImportPreviewRow["kind"];

const FILTERS: Array<{ key: PreviewFilter; label: string }> = [
  { key: "all", label: "All" },
  { key: "new", label: "New" },
  { key: "existing", label: "Exists" },
  { key: "duplicate", label: "Duplicate" },
  { key: "invalid", label: "Invalid" },
];

function PreviewStep({ preview, selected, onToggle, onToggleGroup, onBack, onImport, pending, error }: PreviewStepProps) {
  const [filter, setFilter] = useState<PreviewFilter>("all");
  const { counts } = preview;
  const newRows = preview.rows.filter((row) => row.kind === "new");
  const existingRows = preview.rows.filter((row) => row.kind === "existing");
  const allNew = newRows.length > 0 && newRows.every((row) => selected.has(row.line));
  const allExisting = existingRows.length > 0 && existingRows.every((row) => selected.has(row.line));
  const selectedCount = preview.rows.filter((row) => IMPORTABLE.has(row.kind) && selected.has(row.line)).length;
  const visibleRows = filter === "all" ? preview.rows : preview.rows.filter((row) => row.kind === filter);
  const countFor = (key: PreviewFilter) => (key === "all" ? preview.rows.length : counts[key]);

  return (
    <div>
      <div className="flex flex-wrap items-center justify-between gap-3 border-b border-slate-200 bg-slate-50 px-5 py-2.5">
        <nav className="flex flex-wrap items-center gap-1" aria-label="Filter rows by result">
          {FILTERS.map((option) => {
            const active = filter === option.key;
            const count = countFor(option.key);
            return (
              <button
                key={option.key}
                type="button"
                onClick={() => setFilter(option.key)}
                aria-pressed={active}
                disabled={count === 0 && !active}
                className={cn(
                  "inline-flex h-7 items-center gap-1.5 rounded-md px-2.5 text-xs font-medium whitespace-nowrap transition-colors",
                  active ? "bg-slate-900 text-white" : "text-slate-600 hover:bg-slate-200/70 hover:text-slate-900",
                  "disabled:pointer-events-none disabled:opacity-40",
                )}
              >
                {option.label}
                <span className={cn("tabular-nums", active ? "text-slate-300" : "text-slate-400")}>{count}</span>
              </button>
            );
          })}
          {counts.existing > 0 && (
            <span className="ml-1 text-xs text-slate-500">
              {counts.changed} of {counts.existing} existing with changes
            </span>
          )}
        </nav>
        <span className="flex items-center gap-3 text-xs text-slate-600">
          <label className="flex items-center gap-1.5">
            <input
              type="checkbox"
              checked={allNew}
              disabled={newRows.length === 0}
              onChange={(e) => onToggleGroup("new", e.target.checked)}
              className="size-3.5 accent-slate-900"
            />
            All new
          </label>
          <label className="flex items-center gap-1.5">
            <input
              type="checkbox"
              checked={allExisting}
              disabled={existingRows.length === 0}
              onChange={(e) => onToggleGroup("existing", e.target.checked)}
              className="size-3.5 accent-slate-900"
            />
            All existing
          </label>
        </span>
      </div>

      <div className="max-h-[70vh] overflow-auto">
        <table className="w-full border-collapse text-sm">
          <thead className="sticky top-0 border-b border-slate-200 bg-white text-xs font-medium text-slate-500">
            <tr>
              <th scope="col" className="w-8 px-3 py-2">
                <span className="sr-only">Import</span>
              </th>
              <th scope="col" className="w-36 px-3 py-2.5 text-left">
                Project Code
              </th>
              <th scope="col" className="w-1/4 px-3 py-2.5 text-left">
                Project Name
              </th>
              <th scope="col" className="px-3 py-2.5 text-left">
                API Notifier URL
              </th>
              <th scope="col" className="w-64 px-3 py-2.5 text-left">
                Status
              </th>
            </tr>
          </thead>
          <tbody className="divide-y divide-slate-100">
            {visibleRows.length === 0 && (
              <tr>
                <td colSpan={5} className="px-5 py-10 text-center text-sm text-slate-500">
                  No {FILTERS.find((f) => f.key === filter)?.label.toLowerCase()} rows in this sheet.
                </td>
              </tr>
            )}
            {visibleRows.map((row) => {
              const importable = IMPORTABLE.has(row.kind);
              const checked = importable && selected.has(row.line);
              const kind = KIND_LABEL[row.kind];
              const nameChanged = row.kind === "existing" && row.existing && row.existing.projectName !== row.projectName;
              const urlChanged = row.kind === "existing" && row.existing && row.existing.productionUrl !== row.productionUrl;
              return (
                <tr key={row.line} className={cn(!importable && "text-slate-400", checked && "bg-slate-50/70")}>
                  <td className="px-3 py-3 align-top">
                    <input
                      type="checkbox"
                      aria-label={`Import ${row.projectCode || `row ${row.line}`}`}
                      checked={checked}
                      disabled={!importable}
                      onChange={(e) => onToggle(row.line, e.target.checked)}
                      className="mt-0.5 size-4 accent-slate-900 disabled:opacity-40"
                    />
                  </td>
                  <td className="px-3 py-3 align-top font-mono text-sm break-all">
                    {row.projectCode || <span className="font-sans italic text-slate-400">empty</span>}
                    <span className="block font-sans text-xs text-slate-400">row {row.line}</span>
                  </td>
                  <td className="px-3 py-3 align-top text-sm">
                    <span className="block break-words">
                      {row.projectName || <span className="italic text-slate-400">empty</span>}
                    </span>
                    {nameChanged && (
                      <span className="mt-1 block text-xs text-slate-500">
                        <span className="text-slate-400">currently </span>
                        <span className="line-through">{row.existing!.projectName}</span>
                      </span>
                    )}
                  </td>
                  <td className="px-3 py-3 align-top">
                    <span className="block font-mono text-xs break-all">
                      {row.productionUrl || <span className="font-sans italic text-slate-400">empty</span>}
                    </span>
                    {urlChanged && (
                      <span className="mt-1 block text-xs text-slate-500">
                        <span className="text-slate-400">currently </span>
                        <span className="font-mono line-through">{row.existing!.productionUrl}</span>
                      </span>
                    )}
                    {row.kind === "existing" && !row.existing?.changed && (
                      <span className="mt-1 block text-xs text-slate-400">Identical to the app on the dashboard</span>
                    )}
                  </td>
                  <td className="px-3 py-3 align-top">
                    <span className={cn("inline-flex rounded-full border px-2 py-0.5 text-xs font-medium", kind.className)}>
                      {kind.label}
                    </span>
                    {row.kind === "existing" && (
                      <span className="ml-1.5 text-xs text-slate-500">{row.existing?.changed ? "will be updated" : "no changes"}</span>
                    )}
                    {row.kind === "new" && <span className="ml-1.5 text-xs text-slate-500">will be added</span>}
                    {row.error && <span className="mt-1 block text-xs text-slate-600">{row.error}</span>}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      <div className="flex flex-wrap items-center justify-between gap-3 border-t border-slate-200 px-5 py-3">
        <p className="text-xs text-slate-500">
          {selectedCount === 0
            ? "Nothing selected."
            : `${selectedCount} row${selectedCount === 1 ? "" : "s"} selected${filter !== "all" ? " across all rows" : ""} — existing apps are updated, new ones are added with monitoring enabled.`}
        </p>
        <div className="flex items-center gap-2">
          {error && (
            <span role="alert" className="text-xs text-red-700">
              {error}
            </span>
          )}
          <Button variant="secondary" onClick={onBack} disabled={pending}>
            Back
          </Button>
          <Button onClick={onImport} disabled={pending || selectedCount === 0}>
            {pending ? "Importing…" : `Import selected (${selectedCount})`}
          </Button>
        </div>
      </div>
    </div>
  );
}
