import { useEffect, useMemo, useState } from "react";
import {
  createFileRoute,
  Link,
  useNavigate,
} from "@tanstack/react-router";
import {
  useMutation,
  useQuery,
  useQueryClient,
} from "@tanstack/react-query";
import {
  X,
  Check,
  ArrowRight,
  AlertTriangle,
  SkipForward,
} from "lucide-react";
import { toast } from "sonner";

import type { Invoice, Plan } from "@rubis/shared";
import { api, ApiError } from "@/lib/api";
import { queryKeys } from "@/lib/queryKeys";
import { cn } from "@/lib/utils";
import { formatEuros } from "@/lib/format";

import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card";
import { DatePicker } from "@/components/ui/DatePicker";
import { Eyebrow } from "@/components/ui/Eyebrow";
import { Field } from "@/components/ui/Field";
import { Input } from "@/components/ui/Input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/Select";
import { PdfPreview } from "@/components/factures/PdfPreview";
import { ClientCombobox } from "@/components/factures/ClientCombobox";

type DraftFields = {
  /** Si l'OCR matche un client existant, on pré-remplit clientId pour que
   *  le combobox soit déjà branché à la bonne fiche. Sinon null = création. */
  clientId: string | null;
  clientName: string;
  clientEmail: string | null;
  numero: string;
  amountTtcCents: number;
  issueDate: string;
  dueDate: string;
  planId: string | null;
};

type ImportDraft = {
  id: string;
  filename: string;
  /** Clé de stockage S3/MinIO. null en mode démo MSW (pas de vrai fichier). */
  pdfStorageKey: string | null;
  extracted: DraftFields;
  edited: DraftFields;
  confidence: Partial<Record<keyof DraftFields, number>>;
  status: "pending" | "validated" | "skipped";
};

type ImportBatch = {
  id: string;
  drafts: ImportDraft[];
  createdAt: string;
};

export const Route = createFileRoute("/_app/factures_/import_/$batchId")({
  component: ImportReviewPage,
  loader: ({ context, params }) => {
    void context.queryClient.prefetchQuery({
      queryKey: ["import-batch", params.batchId] as const,
      queryFn: () => api.get<ImportBatch>(`/api/v1/invoices/import-batch/${params.batchId}`),
    });
    void context.queryClient.prefetchQuery({
      queryKey: queryKeys.plans.all(),
      queryFn: () => api.get<Plan[]>("/api/v1/plans"),
    });
  },
});

const LOW_CONFIDENCE = 0.7;

function ImportReviewPage() {
  const { batchId } = Route.useParams();
  const navigate = useNavigate();
  const queryClient = useQueryClient();

  const { data: batch, isPending, isError } = useQuery({
    queryKey: ["import-batch", batchId] as const,
    queryFn: () => api.get<ImportBatch>(`/api/v1/invoices/import-batch/${batchId}`),
  });

  const { data: plans = [] } = useQuery({
    queryKey: queryKeys.plans.all(),
    queryFn: () => api.get<Plan[]>("/api/v1/plans"),
  });

  // Le brouillon courant à éditer = le 1er draft pending. Quand l'utilisateur
  // valide ou skip, MSW met à jour le statut, et le prochain pending devient
  // automatiquement actif au refetch.
  const pendingDrafts = useMemo(
    () => batch?.drafts.filter((d) => d.status === "pending") ?? [],
    [batch],
  );
  const currentDraft = pendingDrafts[0];
  const completedCount =
    batch?.drafts.filter((d) => d.status !== "pending").length ?? 0;
  const totalCount = batch?.drafts.length ?? 0;

  // État local pour les éditions du draft courant. Reset quand le draft change.
  const [draft, setDraft] = useState<DraftFields | null>(null);
  useEffect(() => {
    setDraft(currentDraft ? { ...currentDraft.edited } : null);
  }, [currentDraft?.id]); // eslint-disable-line react-hooks/exhaustive-deps

  const validateMutation = useMutation({
    mutationFn: (input: DraftFields) =>
      api.post<Invoice>(
        `/api/v1/invoices/import-batch/${batchId}/drafts/${currentDraft?.id}/validate`,
        input,
      ),
    onSuccess: () => {
      void queryClient.invalidateQueries({ queryKey: ["import-batch", batchId] });
      void queryClient.invalidateQueries({ queryKey: queryKeys.invoices.all() });
      void queryClient.invalidateQueries({ queryKey: ["invoices", "counts"] });
      toast.success("Facture validée. + 1 rubis.");
    },
    onError: (err: unknown) => {
      // 402 → limite Free atteinte. Message dédié + propose l'upgrade.
      if (
        err instanceof ApiError &&
        err.status === 402 &&
        err.code === "plan_limit_reached"
      ) {
        toast.error(err.message ?? "Limite Free atteinte — passez Pro.", {
          action: {
            label: "Passer Pro",
            onClick: () =>
              void navigate({ to: "/parametres/abonnement" }),
          },
          duration: 8000,
        });
        return;
      }
      toast.error("Impossible de valider la facture. Vérifiez les champs.");
    },
  });

  const skipMutation = useMutation({
    mutationFn: () =>
      api.post<unknown>(
        `/api/v1/invoices/import-batch/${batchId}/drafts/${currentDraft?.id}/skip`,
      ),
    onSuccess: () => {
      void queryClient.invalidateQueries({ queryKey: ["import-batch", batchId] });
      toast("Facture ignorée.");
    },
  });

  const cancelBatchMutation = useMutation({
    mutationFn: () => api.delete<void>(`/api/v1/invoices/import-batch/${batchId}`),
    onSuccess: () => {
      toast("Import annulé.");
      void navigate({ to: "/factures" });
    },
  });

  // Quand tous les drafts sont processed, on file vers /factures avec un toast
  // bilan. On utilise un effect pour ne déclencher qu'une fois.
  const allProcessed =
    !!batch && batch.drafts.length > 0 && pendingDrafts.length === 0;
  useEffect(() => {
    if (!allProcessed || !batch) return;
    const validated = batch.drafts.filter((d) => d.status === "validated").length;
    const skipped = batch.drafts.filter((d) => d.status === "skipped").length;
    toast.success(
      validated > 0
        ? `${validated} facture${validated > 1 ? "s" : ""} ajoutée${
            validated > 1 ? "s" : ""
          }${skipped > 0 ? ` · ${skipped} ignorée${skipped > 1 ? "s" : ""}` : ""}.`
        : "Import terminé sans facture validée.",
    );
    void navigate({ to: "/factures" });
  }, [allProcessed]); // eslint-disable-line react-hooks/exhaustive-deps

  if (isError) {
    return (
      <div className="text-center py-12">
        <p className="font-display text-[20px] font-semibold text-ink">
          Lot d&apos;import introuvable.
        </p>
        <Link
          to="/factures"
          className="mt-3 inline-block text-[13px] text-rubis underline-offset-4 hover:underline"
        >
          ← Retour aux factures
        </Link>
      </div>
    );
  }

  if (isPending || !batch || !currentDraft || !draft) {
    return <ImportSkeleton />;
  }

  const indexInBatch =
    batch.drafts.findIndex((d) => d.id === currentDraft.id) + 1;

  const update = (patch: Partial<DraftFields>) =>
    setDraft((prev) => (prev ? { ...prev, ...patch } : prev));

  const isLowConfidence = (key: keyof DraftFields) =>
    (currentDraft.confidence[key] ?? 1) < LOW_CONFIDENCE;

  return (
    <div className="flex flex-col gap-5">
      {/* === Header === */}
      <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
        <div>
          <Eyebrow>
            Import OCR · facture {indexInBatch} sur {totalCount}
          </Eyebrow>
          <h1 className="mt-2 font-display text-[24px] font-bold tracking-[-0.022em] text-ink lg:text-[26px]">
            Vérifiez les <em className="text-rubis">infos extraites</em>
          </h1>
          <p className="mt-1 text-[13px] text-ink-3">
            Modifiez si l&apos;OCR s&apos;est trompé, puis validez. On continue
            avec la suivante automatiquement.
          </p>
        </div>

        <Button
          variant="ghost"
          size="sm"
          onClick={() => cancelBatchMutation.mutate()}
          loading={cancelBatchMutation.isPending}
        >
          <X size={14} aria-hidden="true" /> Annuler l&apos;import
        </Button>
      </div>

      {/* Progression du batch */}
      <div className="flex items-center gap-2">
        <div className="h-1 flex-1 overflow-hidden rounded-full bg-cream-2">
          <div
            className="h-full bg-rubis transition-[width] duration-300"
            style={{ width: `${(completedCount / totalCount) * 100}%` }}
          />
        </div>
        <span className="text-[11.5px] tabular-nums text-ink-3">
          {completedCount}/{totalCount}
        </span>
      </div>

      {/* === Body : 2 cols === */}
      <div className="grid grid-cols-1 gap-5 lg:grid-cols-[1fr_1.1fr]">
        <PdfPreview
          filename={currentDraft.filename}
          batchId={batchId}
          draftId={currentDraft.id}
          pdfAvailable={!!currentDraft.pdfStorageKey}
        />

        <Card padding="md" className="flex flex-col gap-5">
          <div className="flex flex-col gap-1">
            <Eyebrow tone="ink">Champs extraits</Eyebrow>
            <p className="text-[12px] text-ink-3">
              Les champs en rubis sont à <em className="not-italic font-medium">vérifier en priorité</em>{" "}
              — l&apos;OCR n&apos;était pas sûr.
            </p>
          </div>

          {/* Client — combobox autocomplete : si le nom matche un client
              existant, on lie tout de suite à sa fiche ; sinon proposition
              de créer un nouveau client à la validation. */}
          <Field
            label="Client"
            htmlFor="clientName"
            hint={
              draft.clientId
                ? "Lié à un client existant ✓"
                : draft.clientName.length >= 2
                ? "Nouveau client — sera créé à la validation."
                : "Tapez pour rechercher ou créer un client."
            }
            error={draft.clientName.length < 2 ? "Au moins 2 caractères" : undefined}
          >
            <ClientCombobox
              id="clientName"
              value={draft.clientName}
              selectedClientId={draft.clientId}
              onChange={({ value, clientId }) =>
                update({ clientName: value, clientId })
              }
            />
          </Field>

          {/* Email + Numéro côte-à-côte */}
          <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <Field
              label="Email du contact"
              htmlFor="clientEmail"
              hint={
                isLowConfidence("clientEmail")
                  ? "OCR peu sûr — vérifiez."
                  : undefined
              }
            >
              <Input
                id="clientEmail"
                type="email"
                placeholder="compta@…"
                value={draft.clientEmail ?? ""}
                onChange={(e) =>
                  update({ clientEmail: e.target.value || null })
                }
                className={cn(isLowConfidence("clientEmail") && "border-rubis")}
              />
            </Field>

            <Field label="Numéro de facture" htmlFor="numero">
              <Input
                id="numero"
                value={draft.numero}
                onChange={(e) => update({ numero: e.target.value })}
                className={cn(isLowConfidence("numero") && "border-rubis")}
              />
            </Field>
          </div>

          {/* Montant + Échéance */}
          <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <Field
              label="Montant TTC"
              htmlFor="amount"
              hint={`= ${formatEuros(draft.amountTtcCents)}`}
            >
              <Input
                id="amount"
                type="number"
                step={0.01}
                min={0}
                value={(draft.amountTtcCents / 100).toFixed(2)}
                onChange={(e) =>
                  update({
                    amountTtcCents: Math.round(
                      parseFloat(e.target.value || "0") * 100,
                    ),
                  })
                }
                className={cn(isLowConfidence("amountTtcCents") && "border-rubis")}
              />
            </Field>

            <Field label="Date d'échéance" htmlFor="dueDate">
              <DatePicker
                id="dueDate"
                value={draft.dueDate}
                onChange={(d) =>
                  update({
                    dueDate: d.toISOString(),
                  })
                }
                className={cn(isLowConfidence("dueDate") && "border-rubis")}
              />
            </Field>
          </div>

          {/* Plan */}
          <Field
            label="Plan de relance"
            hint="Vous pouvez changer plus tard depuis la fiche facture."
          >
            <Select
              value={draft.planId ?? ""}
              onValueChange={(v) => update({ planId: v || null })}
            >
              <SelectTrigger>
                <SelectValue placeholder="Choisir un plan…" />
              </SelectTrigger>
              <SelectContent>
                {plans.map((plan) => (
                  <SelectItem key={plan.id} value={plan.id}>
                    {plan.name}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </Field>

          {/* Hint global si plusieurs champs douteux */}
          {Object.entries(currentDraft.confidence).some(
            ([, c]) => (c ?? 1) < LOW_CONFIDENCE,
          ) && (
            <div className="flex items-start gap-2 rounded-default bg-rubis-glow/60 px-3 py-2 text-[12.5px] text-rubis-deep">
              <AlertTriangle size={14} className="mt-0.5 shrink-0" aria-hidden="true" />
              <p>
                Quelques champs sont surlignés en rubis — l&apos;OCR n&apos;était
                pas confiant. Un coup d&apos;œil rapide et c&apos;est bon.
              </p>
            </div>
          )}
        </Card>
      </div>

      {/* === Actions footer === */}
      <div className="flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
        <Button
          variant="ghost"
          size="sm"
          onClick={() => skipMutation.mutate()}
          loading={skipMutation.isPending}
        >
          <SkipForward size={14} aria-hidden="true" /> Ignorer cette facture
        </Button>

        <Button
          size="md"
          loading={validateMutation.isPending}
          onClick={() => validateMutation.mutate(draft)}
          disabled={draft.clientName.length < 2 || draft.numero.length < 1}
        >
          {indexInBatch < totalCount ? (
            <>
              <Check size={15} aria-hidden="true" /> Valider & suivante{" "}
              <ArrowRight size={14} aria-hidden="true" />
            </>
          ) : (
            <>
              <Check size={15} aria-hidden="true" /> Valider & terminer
            </>
          )}
        </Button>
      </div>
    </div>
  );
}

function ImportSkeleton() {
  return (
    <div className="flex flex-col gap-5 animate-pulse">
      <div className="space-y-2">
        <div className="h-3 w-32 rounded bg-cream-2" />
        <div className="h-7 w-1/2 rounded bg-cream-2" />
      </div>
      <div className="grid grid-cols-1 gap-5 lg:grid-cols-[1fr_1.1fr]">
        <div className="h-[460px] rounded-card bg-cream-2" />
        <div className="h-[460px] rounded-card bg-cream-2" />
      </div>
    </div>
  );
}
