"use server";

import { z } from "zod";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { db, schema } from "@/lib/db";
import { requireAdmin } from "@/lib/guards";
import {
  computeTtcCents,
  nextInvoiceNumber,
} from "@/lib/invoices";
import type {
  InvoiceClientInfo,
  InvoiceEmitterInfo,
  InvoiceLineItem,
} from "@/lib/db/schema";

/**
 * Actions admin sur les factures depuis la page liste `/socialexadmin/factures`.
 * Distinctes de `dossiers/[id]/actions.ts` car ne nécessitent pas de dossierId
 * (une facture peut survivre à la suppression de son dossier : FK `set null`).
 */

const updateInput = z.object({
  invoiceId: z.string().min(1),
  description: z.string().min(1, "Description requise").max(500),
  amountEur: z.coerce
    .number({ message: "Montant invalide" })
    .nonnegative("Le montant doit être positif")
    .max(1_000_000, "Montant trop élevé"),
  status: z.enum(["brouillon", "envoyee", "payee", "annulee"]),
  paymentMethod: z.string().max(40).optional().nullable(),
  paymentReference: z.string().max(200).optional().nullable(),
  /** Date ISO YYYY-MM-DD (depuis l'input date) ou vide. */
  issuedAt: z.string().optional().nullable(),
  dueAt: z.string().optional().nullable(),
  paidAt: z.string().optional().nullable(),
});

export type UpdateInvoiceInput = z.infer<typeof updateInput>;

function parseDateOrNull(v: string | null | undefined): Date | null {
  if (!v) return null;
  const d = new Date(v);
  return Number.isNaN(d.getTime()) ? null : d;
}

export async function updateInvoiceAdmin(
  input: UpdateInvoiceInput
): Promise<{ ok: true } | { ok: false; error: string }> {
  await requireAdmin();
  const parsed = updateInput.safeParse(input);
  if (!parsed.success) {
    return {
      ok: false,
      error: parsed.error.issues[0]?.message ?? "Données invalides",
    };
  }
  const d = parsed.data;

  // Si on passe à "payee" et qu'il n'y a pas de paidAt explicite, on met
  // maintenant. Si on revient en arrière, on efface paidAt.
  const paidAt =
    d.status === "payee"
      ? parseDateOrNull(d.paidAt) ?? new Date()
      : null;

  await db
    .update(schema.invoice)
    .set({
      description: d.description,
      amountCents: Math.round(d.amountEur * 100),
      status: d.status,
      paymentMethod: d.paymentMethod || null,
      paymentReference: d.paymentReference || null,
      issuedAt: parseDateOrNull(d.issuedAt),
      dueAt: parseDateOrNull(d.dueAt),
      paidAt,
      updatedAt: new Date(),
    })
    .where(eq(schema.invoice.id, d.invoiceId));

  // Si on marque payée et qu'un dossier est lié, on bascule le dossier en
  // cours (mêmes règles que le webhook Stripe / MarkPaidButton).
  if (d.status === "payee") {
    const inv = (
      await db
        .select()
        .from(schema.invoice)
        .where(eq(schema.invoice.id, d.invoiceId))
        .limit(1)
    )[0];
    if (inv?.dossierId) {
      const dossier = (
        await db
          .select()
          .from(schema.dossier)
          .where(eq(schema.dossier.id, inv.dossierId))
          .limit(1)
      )[0];
      if (
        dossier &&
        (dossier.status === "en_attente_paiement" ||
          dossier.status === "formulaire")
      ) {
        await db
          .update(schema.dossier)
          .set({ status: "acompte_paye", updatedAt: new Date() })
          .where(eq(schema.dossier.id, inv.dossierId));
      } else if (dossier && dossier.status === "solde_a_payer") {
        await db
          .update(schema.dossier)
          .set({ status: "termine", updatedAt: new Date() })
          .where(eq(schema.dossier.id, inv.dossierId));
      }
    }
  }

  revalidatePath("/socialexadmin/factures");
  revalidatePath(`/socialexadmin/factures/${d.invoiceId}`);
  return { ok: true };
}

export async function deleteInvoiceAdmin(invoiceId: string): Promise<void> {
  await requireAdmin();
  if (!invoiceId) return;
  await db.delete(schema.invoice).where(eq(schema.invoice.id, invoiceId));
  revalidatePath("/socialexadmin/factures");
  redirect("/socialexadmin/factures");
}

/* ─── Création manuelle de facture ───────────────────────────────────── */

const lineItemSchema = z.object({
  description: z.string().min(1, "Description de ligne requise").max(500),
  quantity: z.coerce
    .number({ message: "Quantité invalide" })
    .positive("La quantité doit être positive")
    .max(100_000, "Quantité trop élevée"),
  /** Prix unitaire HT saisi en euros côté UI, converti en centimes ici. */
  unitPriceHtEur: z.coerce
    .number({ message: "Prix HT invalide" })
    .min(0, "Le prix HT doit être positif ou nul")
    .max(1_000_000, "Prix HT trop élevé"),
});

const createInput = z.object({
  /** Si renseigné, lie la facture à ce user (et préfère ses infos). */
  clientUserId: z.string().nullable().optional(),
  /** Override coordonnées émetteur (toujours stocké, même si identique au défaut). */
  emitter: z.object({
    name: z.string().min(1, "Nom de l'émetteur requis").max(120),
    tagline: z.string().max(120).optional().nullable(),
    email: z.string().max(200).optional().nullable(),
    addressLines: z.string().max(500).optional().nullable(),
    siret: z.string().max(40).optional().nullable(),
  }),
  /** Coordonnées client effectivement imprimées sur la facture. */
  client: z.object({
    name: z.string().min(1, "Nom du client requis").max(200),
    company: z.string().max(200).optional().nullable(),
    email: z.string().max(200).optional().nullable(),
    addressStreet: z.string().max(300).optional().nullable(),
    addressZip: z.string().max(20).optional().nullable(),
    addressCity: z.string().max(120).optional().nullable(),
    addressCountry: z.string().max(120).optional().nullable(),
    siret: z.string().max(40).optional().nullable(),
  }),
  lineItems: z.array(lineItemSchema).min(1, "Au moins une ligne requise"),
  /** Active la TVA. Si false : pas de TVA, mention "non applicable". */
  vatEnabled: z.boolean(),
  /** Taux TVA en % (UI). Converti en points de base à l'enregistrement. */
  vatRatePercent: z.coerce
    .number({ message: "Taux TVA invalide" })
    .min(0, "Le taux TVA doit être positif ou nul")
    .max(100, "Taux TVA hors borne"),
  status: z.enum(["brouillon", "envoyee", "payee", "annulee"]),
  issuedAt: z.string().optional().nullable(),
  dueAt: z.string().optional().nullable(),
  paidAt: z.string().optional().nullable(),
  paymentMethod: z.string().max(40).optional().nullable(),
  paymentReference: z.string().max(200).optional().nullable(),
});

export type CreateInvoiceInput = z.infer<typeof createInput>;

export async function createInvoiceAdmin(
  input: CreateInvoiceInput
): Promise<
  | { ok: true; invoiceId: string; invoiceNumber: string }
  | { ok: false; error: string }
> {
  await requireAdmin();
  const parsed = createInput.safeParse(input);
  if (!parsed.success) {
    return {
      ok: false,
      error: parsed.error.issues[0]?.message ?? "Données invalides",
    };
  }
  const d = parsed.data;

  // Lignes converties (euros → centimes).
  const lineItems: InvoiceLineItem[] = d.lineItems.map((li) => ({
    description: li.description,
    quantity: li.quantity,
    unitPriceHtCents: Math.round(li.unitPriceHtEur * 100),
  }));

  // Si toutes les lignes sont à zéro, on bloque pour éviter une facture vide.
  if (lineItems.every((li) => li.quantity * li.unitPriceHtCents === 0)) {
    return { ok: false, error: "Le montant total doit être supérieur à 0." };
  }

  const vatRateBps = d.vatEnabled ? Math.round(d.vatRatePercent * 100) : null;
  const { ttcCents } = computeTtcCents(lineItems, vatRateBps);

  // Si le client choisi n'existe pas en base : on garde null + clientInfo.
  const clientId = d.clientUserId?.trim() || null;
  if (clientId) {
    const user = (
      await db
        .select({ id: schema.user.id })
        .from(schema.user)
        .where(eq(schema.user.id, clientId))
        .limit(1)
    )[0];
    if (!user) {
      return { ok: false, error: "Client introuvable en base." };
    }
  }

  // Override émetteur : on normalise les addressLines (multilignes → array).
  const emitterInfo: InvoiceEmitterInfo = {
    name: d.emitter.name,
    tagline: d.emitter.tagline || null,
    email: d.emitter.email || null,
    addressLines: d.emitter.addressLines
      ? d.emitter.addressLines
          .split(/\r?\n/)
          .map((l) => l.trim())
          .filter(Boolean)
      : null,
    siret: d.emitter.siret || null,
  };

  const clientInfo: InvoiceClientInfo = {
    name: d.client.name,
    company: d.client.company || null,
    email: d.client.email || null,
    addressStreet: d.client.addressStreet || null,
    addressZip: d.client.addressZip || null,
    addressCity: d.client.addressCity || null,
    addressCountry: d.client.addressCountry || null,
    siret: d.client.siret || null,
  };

  const year = new Date().getFullYear();
  const number = await nextInvoiceNumber(year);

  // Description courte (récap pour la liste et l'email) : 1re ligne ou "X lignes".
  const summary =
    lineItems.length === 1
      ? lineItems[0].description
      : `${lineItems.length} lignes — ${lineItems[0].description}`;

  const issuedAt = parseDateOrNull(d.issuedAt) ?? new Date();
  const paidAt =
    d.status === "payee" ? parseDateOrNull(d.paidAt) ?? new Date() : null;

  const [inv] = await db
    .insert(schema.invoice)
    .values({
      number,
      clientId,
      dossierId: null,
      status: d.status,
      amountCents: ttcCents,
      description: summary,
      lineItems,
      emitterInfo,
      clientInfo,
      vatRateBps,
      isManual: true,
      issuedAt,
      dueAt: parseDateOrNull(d.dueAt),
      paidAt,
      paymentMethod: d.paymentMethod || null,
      paymentReference: d.paymentReference || null,
    })
    .returning({ id: schema.invoice.id, number: schema.invoice.number });

  revalidatePath("/socialexadmin/factures");
  return { ok: true, invoiceId: inv.id, invoiceNumber: inv.number };
}
