import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { useMutation, useQuery } from "@tanstack/react-query";
import { Plus } from "lucide-react";
import { toast } from "sonner";
import { z } from "zod";

import {
  invoiceListFiltersSchema,
  type InvoiceStatus,
} from "@rubis/shared";
import { api } from "@/lib/api";
import { queryKeys } from "@/lib/queryKeys";

import { Dropzone } from "@/components/factures/Dropzone";
import { FilterChips, type FilterOption } from "@/components/factures/FilterChips";
import { PlanLimitBanner } from "@/components/billing/PlanLimitBanner";
import {
  InvoiceTable,
  type InvoiceListItem,
} from "@/components/factures/InvoiceTable";
import { InvoiceCardList } from "@/components/factures/InvoiceCardList";
import { Button } from "@/components/ui/Button";
import { Pagination } from "@/components/ui/Pagination";
import { useManualInvoice } from "@/hooks/useManualInvoiceDialog";
import { uploadInvoiceFiles } from "@/lib/invoices";

const INVOICES_PAGE_SIZE = 50;

/** Status filter key — superset des InvoiceStatus + "all" pour "Toutes". */
const FILTER_KEYS = [
  "all",
  "pending",
  "in_relance",
  "awaiting_user_confirmation",
  "paid",
  "litigation",
] as const;
type FilterKey = (typeof FILTER_KEYS)[number];

/** Sous-ensemble du schéma shared, adapté aux clés UI. */
const searchSchema = invoiceListFiltersSchema.extend({
  status: z.enum(FILTER_KEYS).optional().default("all"),
});

type StatusCounts = {
  all: number;
  pending: number;
  in_relance: number;
  awaiting_user_confirmation: number;
  paid: number;
  litigation: number;
  cancelled: number;
};

export const Route = createFileRoute("/_app/factures")({
  validateSearch: searchSchema,
  component: FacturesPage,
  loader: ({ context }) => {
    void context.queryClient.prefetchQuery({
      queryKey: queryKeys.invoices.list({}),
      queryFn: () => api.getList<InvoiceListItem>("/api/v1/invoices"),
    });
  },
});

function useUploadInvoices() {
  const navigate = useNavigate();
  return useMutation({
    mutationFn: uploadInvoiceFiles,
    onSuccess: (batch) => {
      toast.success(
        `${batch.drafts.length} facture${batch.drafts.length > 1 ? "s" : ""} extraite${
          batch.drafts.length > 1 ? "s" : ""
        }. Vérifions ensemble.`,
      );
      void navigate({
        to: "/factures/import/$batchId",
        params: { batchId: batch.id },
      });
    },
    onError: () => {
      toast.error("L'upload a échoué. Réessayez dans un instant.");
    },
  });
}

function FacturesPage() {
  const navigate = useNavigate();
  const search = Route.useSearch();
  const upload = useUploadInvoices();
  const manual = useManualInvoice();

  // Drop-catcher global au niveau de la route : si l'utilisateur lâche un
  // fichier hors de la dropzone (ailleurs sur /factures), on intercepte
  // pour éviter que le navigateur ouvre le PDF dans un nouvel onglet, et
  // on déclenche l'upload comme si le drop avait visé la dropzone.
  const onPageDragOver = (e: React.DragEvent<HTMLDivElement>) => {
    if (e.dataTransfer.types.includes("Files")) e.preventDefault();
  };
  const onPageDrop = (e: React.DragEvent<HTMLDivElement>) => {
    if (!e.dataTransfer.types.includes("Files")) return;
    e.preventDefault();
    const files = Array.from(e.dataTransfer.files).filter(
      (f) => /\.(pdf|png|jpe?g)$/iu.test(f.name) || f.type !== "",
    );
    if (files.length > 0 && !upload.isPending) upload.mutate(files);
  };

  const currentPage = search.page ?? 1;
  const { data: response, isPending } = useQuery({
    queryKey: queryKeys.invoices.list({
      status: search.status as InvoiceStatus | "all" | undefined,
      q: search.q,
      clientId: search.clientId,
      page: currentPage,
    }),
    queryFn: () => {
      const params = new URLSearchParams();
      if (search.status && search.status !== "all") params.set("status", search.status);
      if (search.q) params.set("q", search.q);
      if (search.clientId) params.set("clientId", search.clientId);
      if (currentPage > 1) params.set("page", String(currentPage));
      const qs = params.toString();
      return api.getList<InvoiceListItem>(
        `/api/v1/invoices${qs ? `?${qs}` : ""}`,
      );
    },
  });
  const invoices = response?.data ?? [];
  const paginationTotal = response?.meta?.total ?? invoices.length;

  const { data: counts } = useQuery({
    queryKey: ["invoices", "counts"] as const,
    queryFn: () => api.get<StatusCounts>("/api/v1/invoices/counts"),
  });

  const totalInvoices = counts?.all ?? null;
  const isFilteredEmpty = invoices.length === 0 && totalInvoices !== 0;

  if (totalInvoices === 0) {
    return (
      <div onDragOver={onPageDragOver} onDrop={onPageDrop}>
        <FacturesEmpty
          onFiles={(files) => upload.mutate(files)}
          isUploading={upload.isPending}
          onManualEntry={manual.open}
        />
      </div>
    );
  }

  const filterOptions: ReadonlyArray<FilterOption<FilterKey>> = [
    { key: "all", label: "Toutes", count: counts?.all },
    { key: "pending", label: "À relancer", count: counts?.pending },
    { key: "in_relance", label: "En relance", count: counts?.in_relance },
    {
      key: "awaiting_user_confirmation",
      label: "À valider",
      count: counts?.awaiting_user_confirmation,
    },
    { key: "paid", label: "Encaissées", count: counts?.paid },
    { key: "litigation", label: "Litige", count: counts?.litigation },
  ];

  const handleFilter = (key: FilterKey): void => {
    void navigate({
      to: "/factures",
      search: (prev) => ({ ...prev, status: key, page: 1 }),
    });
  };

  return (
    <div
      className="flex flex-col gap-5"
      onDragOver={onPageDragOver}
      onDrop={onPageDrop}
    >
      <div className="flex items-start justify-between gap-3">
        <div className="min-w-0 flex-1">
          <h1 className="font-display text-[26px] font-bold tracking-[-0.022em] text-ink">
            Factures{" "}
            <span className="font-sans font-normal text-[14px] text-ink-3 align-middle">
              · {totalInvoices ?? 0} active{(totalInvoices ?? 0) > 1 ? "s" : ""}
            </span>
          </h1>
          <p className="mt-1 text-[13.5px] text-ink-3">
            Vos factures à relancer, en relance et encaissées. Cliquez une ligne
            pour voir la timeline.
          </p>
        </div>
        <Button size="sm" asChild className="shrink-0">
          <Link to="/factures/import">
            <Plus size={14} aria-hidden="true" />
            <span className="hidden sm:inline">Nouvelle facture</span>
            <span className="sm:hidden">Nouvelle</span>
          </Link>
        </Button>
      </div>

      <PlanLimitBanner />

      <FilterChips
        options={filterOptions}
        value={(search.status as FilterKey | undefined) ?? "all"}
        onChange={handleFilter}
      />

      {isFilteredEmpty ? (
        <FilteredEmpty onReset={() => handleFilter("all")} />
      ) : isPending ? (
        <SkeletonRows />
      ) : (
        <>
          <div className="hidden lg:block">
            <InvoiceTable invoices={invoices} />
          </div>
          <div className="lg:hidden">
            <InvoiceCardList invoices={invoices} />
          </div>

          <Pagination
            page={currentPage}
            total={paginationTotal}
            pageSize={INVOICES_PAGE_SIZE}
            itemLabel="factures"
            onPageChange={(next) =>
              void navigate({
                to: "/factures",
                search: (prev) => ({ ...prev, page: next }),
              })
            }
          />

          {/* Compact dropzone en bas — toujours là pour drag-and-drop rapide
              sans avoir à vider la liste. */}
          <div className="mt-4">
            <Dropzone
              variant="compact"
              onFiles={(files) => upload.mutate(files)}
              isUploading={upload.isPending}
            />
          </div>
        </>
      )}
    </div>
  );
}

/** Empty state global : pas une seule facture. La dropzone EST l'écran. */
function FacturesEmpty({
  onFiles,
  isUploading,
  onManualEntry,
}: {
  onFiles: (files: File[]) => void;
  isUploading: boolean;
  onManualEntry: () => void;
}) {
  return (
    <div className="flex flex-col gap-5">
      <div>
        <h1 className="font-display text-[26px] font-bold tracking-[-0.022em] text-ink">
          <em className="text-rubis">Première</em> facture ?
        </h1>
        <p className="mt-1 text-[14px] text-ink-3">
          Glissez vos PDF ici, l&apos;OCR se charge du reste. Vous validez
          en 30 secondes.
        </p>
      </div>
      <Dropzone
        variant="full"
        onFiles={onFiles}
        isUploading={isUploading}
        onManualEntry={onManualEntry}
      />
    </div>
  );
}

/** Empty state filtré : on a des factures mais pas dans ce filtre. */
function FilteredEmpty({ onReset }: { onReset: () => void }) {
  return (
    <div className="rounded-card border border-dashed border-line bg-cream-2/30 px-6 py-12 text-center">
      <p className="font-display text-[18px] font-semibold text-ink">
        Rien dans <em className="text-rubis">ce filtre</em>.
      </p>
      <p className="mt-2 text-[13.5px] text-ink-2">
        Aucune facture ne correspond. C&apos;est plutôt bon signe.
      </p>
      <button
        type="button"
        onClick={onReset}
        className="mt-4 text-[13px] font-semibold text-rubis underline-offset-4 hover:underline"
      >
        Voir toutes les factures
      </button>
    </div>
  );
}

function SkeletonRows() {
  return (
    <div className="rounded-card border border-line bg-white">
      {Array.from({ length: 6 }).map((_, idx) => (
        <div
          key={idx}
          className="flex items-center gap-4 px-5 py-4 border-t border-line first:border-t-0 animate-pulse"
        >
          <div className="h-3 flex-1 rounded bg-cream-2" />
          <div className="h-3 w-20 rounded bg-cream-2" />
          <div className="h-3 w-24 rounded bg-cream-2" />
          <div className="h-3 w-28 rounded bg-cream-2" />
        </div>
      ))}
    </div>
  );
}
