import { useEffect } from "react";
import { useForm } from "@tanstack/react-form";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Paperclip } from "lucide-react";
import { toast } from "sonner";
import { z } from "zod";

import type { Plan } from "@rubis/shared";
import { api } from "@/lib/api";
import { queryKeys } from "@/lib/queryKeys";
import { Button } from "@/components/ui/Button";
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/Dialog";
import { DatePicker } from "@/components/ui/DatePicker";
import { Field } from "@/components/ui/Field";
import { Input } from "@/components/ui/Input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/Select";
import { ClientCombobox } from "./ClientCombobox";

/**
 * Saisie manuelle d'une facture (cf. wireframe 2.3).
 * Modal Radix Dialog avec form local TanStack Form + validateurs Zod par
 * champ (plus pratique que le validator global vu les contraintes de typing
 * imposées par TanStack Form sur le mapping schema↔defaultValues).
 *
 * Échéance gérée en RELATIF (15/30/45/60/90 jours après émission) car
 * c'est plus rapide que calculer une date — cf. annotation wireframe.
 * À la soumission, on convertit en absolu.
 */

const RELATIVE_DUE_OPTIONS = ["15", "30", "45", "60", "90"] as const;
type RelativeDueDays = (typeof RELATIVE_DUE_OPTIONS)[number];

const RELATIVE_DUE_LABELS: Record<RelativeDueDays, string> = {
  "15": "15 jours après émission",
  "30": "30 jours après émission (standard)",
  "45": "45 jours après émission",
  "60": "60 jours après émission",
  "90": "90 jours après émission",
};

type FormValues = {
  clientName: string;
  clientId: string | null;
  numero: string;
  amountTtcCents: number;
  issueDate: string;
  relativeDueDays: RelativeDueDays;
  planId: string;
};

// Validateurs par champ (TanStack Form supporte le validator par champ via
// validators.onChange, et le typing est plus clean qu'avec un schema global).
const validators = {
  clientName: z
    .string()
    .min(2, "Au moins 2 caractères")
    .max(120, "120 caractères max"),
  numero: z
    .string()
    .min(1, "Numéro requis")
    .max(50, "50 caractères max"),
  amountTtcCents: z
    .number({ invalid_type_error: "Montant invalide" })
    .int()
    .positive("Le montant doit être positif"),
  planId: z.string().min(1, "Choisissez un plan"),
};

type ManualInvoiceDialogProps = {
  open: boolean;
  onOpenChange: (open: boolean) => void;
};

export function ManualInvoiceDialog({ open, onOpenChange }: ManualInvoiceDialogProps) {
  const queryClient = useQueryClient();

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

  const createMutation = useMutation({
    mutationFn: (input: FormValues) => {
      const issueDate = new Date(input.issueDate);
      const dueDate = new Date(issueDate);
      dueDate.setDate(dueDate.getDate() + Number(input.relativeDueDays));
      return api.post(`/api/v1/invoices`, {
        clientId: input.clientId ?? undefined,
        clientName: input.clientName,
        clientEmail: null,
        numero: input.numero,
        amountTtcCents: input.amountTtcCents,
        issueDate: issueDate.toISOString(),
        dueDate: dueDate.toISOString(),
        planId: input.planId,
      });
    },
    onSuccess: () => {
      void queryClient.invalidateQueries({ queryKey: queryKeys.invoices.all() });
      void queryClient.invalidateQueries({ queryKey: ["invoices", "counts"] });
      void queryClient.invalidateQueries({ queryKey: queryKeys.dashboard.kpis() });
      void queryClient.invalidateQueries({ queryKey: queryKeys.clients.all() });
      toast.success("Facture créée. + 1 rubis.");
      onOpenChange(false);
    },
    onError: () => {
      toast.error("Création impossible. Vérifiez les champs.");
    },
  });

  const today = todayISO();
  const defaultPlanId = plans.find((p) => p.slug === "standard-30j")?.id ?? plans[0]?.id ?? "";

  const initialValues: FormValues = {
    clientName: "",
    clientId: null,
    numero: "",
    amountTtcCents: 0,
    issueDate: today,
    relativeDueDays: "30",
    planId: defaultPlanId,
  };

  const form = useForm({
    defaultValues: initialValues,
    onSubmit: async ({ value }) => {
      // Validation finale sur tous les champs avant submit. Si un champ est
      // invalide, on toast et on n'appelle pas l'API.
      const checks: Array<[keyof FormValues, z.ZodTypeAny]> = [
        ["clientName", validators.clientName],
        ["numero", validators.numero],
        ["amountTtcCents", validators.amountTtcCents],
        ["planId", validators.planId],
      ];
      for (const [key, schema] of checks) {
        const r = schema.safeParse(value[key]);
        if (!r.success) {
          toast.error(r.error.issues[0]?.message ?? "Champ invalide");
          return;
        }
      }
      await createMutation.mutateAsync(value);
    },
  });

  // Reset à chaque ouverture (et synchronise defaultPlanId une fois les
  // plans chargés — premier render le tableau peut être vide).
  useEffect(() => {
    if (open) form.reset(initialValues);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, defaultPlanId]);

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent maxWidth={560}>
        <DialogHeader>
          <DialogTitle>Nouvelle facture</DialogTitle>
          <DialogDescription>
            Saisie manuelle — pour les factures que vous avez sous la main mais
            sans PDF à OCRiser.
          </DialogDescription>
        </DialogHeader>

        <form
          noValidate
          onSubmit={(e) => {
            e.preventDefault();
            void form.handleSubmit();
          }}
          className="flex flex-col gap-5"
        >
          {/* === Client === */}
          <form.Field
            name="clientName"
            validators={{ onChange: validators.clientName }}
          >
            {(field) => (
              <form.Field name="clientId">
                {(idField) => (
                  <Field
                    label="Client"
                    htmlFor={field.name}
                    hint="Tapez pour rechercher un client existant ou en créer un."
                    error={firstError(field.state.meta.errors)}
                  >
                    <ClientCombobox
                      id={field.name}
                      value={field.state.value}
                      selectedClientId={idField.state.value}
                      onChange={({ value, clientId }) => {
                        field.handleChange(value);
                        idField.handleChange(clientId);
                      }}
                    />
                  </Field>
                )}
              </form.Field>
            )}
          </form.Field>

          {/* === N° + Date d'émission === */}
          <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <form.Field
              name="numero"
              validators={{ onChange: validators.numero }}
            >
              {(field) => (
                <Field
                  label="N° de facture"
                  htmlFor={field.name}
                  error={firstError(field.state.meta.errors)}
                >
                  <Input
                    id={field.name}
                    placeholder="F-2026-0043"
                    value={field.state.value}
                    onChange={(e) => field.handleChange(e.target.value)}
                  />
                </Field>
              )}
            </form.Field>

            <form.Field name="issueDate">
              {(field) => (
                <Field label="Date d'émission" htmlFor={field.name}>
                  <DatePicker
                    id={field.name}
                    value={field.state.value}
                    onChange={(d) => field.handleChange(d.toISOString())}
                  />
                </Field>
              )}
            </form.Field>
          </div>

          {/* === Montant + Échéance relative === */}
          <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <form.Field
              name="amountTtcCents"
              validators={{ onChange: validators.amountTtcCents }}
            >
              {(field) => (
                <Field
                  label="Montant TTC"
                  htmlFor={field.name}
                  hint="Montant en euros (ex: 1240 ou 1240.50)."
                  error={firstError(field.state.meta.errors)}
                >
                  {/* Input non-contrôlé (defaultValue) pour ne pas reformatter
                      à chaque frappe — sinon "1.00" écrase "10000" et l'user
                      ne peut pas taper de gros montants. La form state reçoit
                      les centimes via onChange. Re-monté à chaque ouverture
                      du Dialog (DialogContent unmounted on close), donc
                      defaultValue est ré-évalué à chaque réouverture. */}
                  <Input
                    id={field.name}
                    type="number"
                    inputMode="decimal"
                    step="any"
                    min={0}
                    placeholder="0,00"
                    defaultValue={
                      field.state.value === 0
                        ? ""
                        : (field.state.value / 100).toString()
                    }
                    onChange={(e) => {
                      const raw = e.target.value.replace(",", ".");
                      const val = parseFloat(raw);
                      field.handleChange(
                        Number.isNaN(val) ? 0 : Math.round(val * 100),
                      );
                    }}
                  />
                </Field>
              )}
            </form.Field>

            <form.Field name="relativeDueDays">
              {(field) => (
                <Field label="Échéance" htmlFor={field.name}>
                  <Select
                    value={field.state.value}
                    onValueChange={(v) =>
                      field.handleChange(v as RelativeDueDays)
                    }
                  >
                    <SelectTrigger id={field.name}>
                      <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                      {RELATIVE_DUE_OPTIONS.map((opt) => (
                        <SelectItem key={opt} value={opt}>
                          {RELATIVE_DUE_LABELS[opt]}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                </Field>
              )}
            </form.Field>
          </div>

          {/* === Plan === */}
          <form.Field
            name="planId"
            validators={{ onChange: validators.planId }}
          >
            {(field) => (
              <Field
                label="Plan de relance"
                htmlFor={field.name}
                error={firstError(field.state.meta.errors)}
              >
                <Select
                  value={field.state.value}
                  onValueChange={(v) => field.handleChange(v)}
                >
                  <SelectTrigger id={field.name}>
                    <SelectValue placeholder="Choisir un plan…" />
                  </SelectTrigger>
                  <SelectContent>
                    {plans.map((plan) => (
                      <SelectItem key={plan.id} value={plan.id}>
                        {plan.name}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </Field>
            )}
          </form.Field>

          <DialogFooter>
            <Button
              variant="ghost"
              size="sm"
              type="button"
              disabled
              className="sm:mr-auto"
            >
              <Paperclip size={14} aria-hidden="true" /> Joindre le PDF
              <span className="ml-1 text-[11px] italic text-ink-3">(bientôt)</span>
            </Button>
            <DialogClose asChild>
              <Button variant="secondary" size="sm" type="button">
                Annuler
              </Button>
            </DialogClose>
            <Button
              size="sm"
              type="submit"
              loading={createMutation.isPending}
            >
              Créer la facture
            </Button>
          </DialogFooter>
        </form>
      </DialogContent>
    </Dialog>
  );
}

/**
 * Petit helper pour extraire le 1er message d'erreur sans dépendre de la
 * shape exacte du retour TanStack Form (qui peut être Issue[] ou string[]
 * selon la version).
 */
function firstError(errors: unknown[]): string | undefined {
  const first = errors[0];
  if (!first) return undefined;
  if (typeof first === "string") return first;
  if (typeof first === "object" && first !== null && "message" in first) {
    const msg = (first as { message?: unknown }).message;
    if (typeof msg === "string") return msg;
  }
  return undefined;
}

function todayISO(): string {
  const d = new Date();
  d.setHours(9, 0, 0, 0);
  return d.toISOString();
}
