"use server";

import {
  StatutCommercial,
  StatutFinancier,
  StatutOperationnel,
  StatutTache,
  TypeDossier,
} from "@prisma/client";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { z } from "zod";
import { peutModifierDossiers, utilisateurCourant } from "@/lib/auth";
import { db } from "@/lib/db";
import { STATUTS_COMMERCIAUX, STATUTS_FINANCIERS, STATUTS_OPERATIONNELS } from "@/lib/libelles";
import { debutJour, genererTaches, recalculerEcheances } from "@/lib/procedure";

export type EtatFormulaire = { erreur?: string; ok?: boolean };

const dateOptionnelle = z
  .string()
  .optional()
  .transform((v) => (v ? debutJour(new Date(v)) : null));

const texteOptionnel = z
  .string()
  .optional()
  .transform((v) => v?.trim() || null);

const schemaDossier = z.object({
  intitule: z.string().trim().min(2, "L'intitulé est obligatoire."),
  societeId: z.string().min(1, "Choisissez la société."),
  clientId: texteOptionnel,
  nouveauClient: texteOptionnel,
  type: z.nativeEnum(TypeDossier),
  lieu: texteOptionnel,
  chargeId: texteOptionnel,
  montantEstime: z
    .string()
    .optional()
    .transform((v) => (v ? Number(v.replace(/\s/g, "").replace(",", ".")) : null))
    .refine((v) => v === null || (Number.isFinite(v) && v >= 0), "Montant invalide."),
  dateReception: dateOptionnelle,
  dateReperage: dateOptionnelle,
  dateConfirmation: dateOptionnelle,
  dateDebut: dateOptionnelle,
  dateFin: dateOptionnelle,
  notes: texteOptionnel,
});

function lireFormulaire(form: FormData) {
  return Object.fromEntries([...form.entries()].map(([k, v]) => [k, typeof v === "string" ? v : ""]));
}

async function exigerEditeur() {
  const moi = await utilisateurCourant();
  if (!peutModifierDossiers(moi.role)) throw new Error("Action non autorisée pour votre rôle.");
  return moi;
}

async function clientDuFormulaire(clientId: string | null, nouveauClient: string | null) {
  if (nouveauClient) {
    const existant = await db.contact.findFirst({ where: { nom: { equals: nouveauClient, mode: "insensitive" } } });
    return existant?.id ?? (await db.contact.create({ data: { nom: nouveauClient, type: "CLIENT" } })).id;
  }
  return clientId;
}

export async function creerDossier(_: EtatFormulaire, form: FormData): Promise<EtatFormulaire> {
  const moi = await exigerEditeur();
  const r = schemaDossier.safeParse(lireFormulaire(form));
  if (!r.success) return { erreur: r.error.issues[0].message };
  const { nouveauClient, clientId, ...donnees } = r.data;
  if (donnees.dateDebut && donnees.dateFin && donnees.dateFin < donnees.dateDebut) {
    return { erreur: "La date de fin précède la date de début." };
  }

  const id = await db.$transaction(async (tx) => {
    // Numéro unique, toutes sociétés confondues, comme dans le registre Excel.
    const dernier = await tx.dossier.aggregate({ _max: { numero: true }, where: { numero: { lt: 9000 } } });
    const dossier = await tx.dossier.create({
      data: {
        ...donnees,
        numero: (dernier._max.numero ?? 0) + 1,
        clientId: await clientDuFormulaire(clientId, nouveauClient),
        dateReception: donnees.dateReception ?? debutJour(),
        chargeId: donnees.chargeId ?? (moi.role === "CHARGE_COMPTE" ? moi.id : null),
      },
    });
    await genererTaches(tx, dossier.id);
    await tx.journal.create({ data: { dossierId: dossier.id, auteurId: moi.id, action: "Dossier créé" } });
    return dossier.id;
  });
  redirect(`/dossiers/${id}`);
}

export async function modifierDossier(id: string, _: EtatFormulaire, form: FormData): Promise<EtatFormulaire> {
  const moi = await exigerEditeur();
  const r = schemaDossier.safeParse(lireFormulaire(form));
  if (!r.success) return { erreur: r.error.issues[0].message };
  const { nouveauClient, clientId, ...donnees } = r.data;
  if (donnees.dateDebut && donnees.dateFin && donnees.dateFin < donnees.dateDebut) {
    return { erreur: "La date de fin précède la date de début." };
  }
  await db.$transaction(async (tx) => {
    await tx.dossier.update({
      where: { id },
      data: { ...donnees, clientId: await clientDuFormulaire(clientId, nouveauClient) },
    });
    await recalculerEcheances(tx, id);
    await tx.journal.create({ data: { dossierId: id, auteurId: moi.id, action: "Informations modifiées" } });
  });
  revalidatePath(`/dossiers/${id}`);
  return { ok: true };
}

const schemaStatuts = z.object({
  statutCommercial: z.nativeEnum(StatutCommercial),
  statutOperationnel: z.nativeEnum(StatutOperationnel),
  statutFinancier: z.nativeEnum(StatutFinancier),
  motif: texteOptionnel,
});

const EXIGENT_MOTIF: StatutCommercial[] = ["PERDU", "ANNULE_CLIENT", "ABANDONNE_AGENCE", "REPORTE"];

export async function changerStatuts(id: string, _: EtatFormulaire, form: FormData): Promise<EtatFormulaire> {
  const moi = await exigerEditeur();
  const r = schemaStatuts.safeParse(lireFormulaire(form));
  if (!r.success) return { erreur: r.error.issues[0].message };
  const avant = await db.dossier.findUniqueOrThrow({ where: { id } });
  const s = r.data;
  if (EXIGENT_MOTIF.includes(s.statutCommercial) && !s.motif) {
    return { erreur: "Indiquez le motif (perte, annulation, abandon ou report)." };
  }

  const changements = [
    avant.statutCommercial !== s.statutCommercial &&
      `Commercial : ${STATUTS_COMMERCIAUX[avant.statutCommercial].libelle} → ${STATUTS_COMMERCIAUX[s.statutCommercial].libelle}`,
    avant.statutOperationnel !== s.statutOperationnel &&
      `Opérationnel : ${STATUTS_OPERATIONNELS[avant.statutOperationnel].libelle} → ${STATUTS_OPERATIONNELS[s.statutOperationnel].libelle}`,
    avant.statutFinancier !== s.statutFinancier &&
      `Financier : ${STATUTS_FINANCIERS[avant.statutFinancier].libelle} → ${STATUTS_FINANCIERS[s.statutFinancier].libelle}`,
  ].filter(Boolean);
  if (!changements.length && s.motif === avant.motif) return { ok: true };

  await db.$transaction(async (tx) => {
    await tx.dossier.update({
      where: { id },
      data: {
        ...s,
        // Les dates de confirmation et de perte alimentent les échéances (CONF+n, P+0).
        dateConfirmation: s.statutCommercial === "CONFIRME" ? (avant.dateConfirmation ?? debutJour()) : avant.dateConfirmation,
        datePerte: EXIGENT_MOTIF.includes(s.statutCommercial) ? (avant.datePerte ?? debutJour()) : null,
      },
    });
    await recalculerEcheances(tx, id);
    await tx.journal.create({
      data: { dossierId: id, auteurId: moi.id, action: "Statut modifié", details: [...changements, s.motif && `Motif : ${s.motif}`].filter(Boolean).join("\n") },
    });
  });
  revalidatePath(`/dossiers/${id}`);
  revalidatePath("/");
  return { ok: true };
}

export async function ouvrirProcedure(id: string) {
  const moi = await exigerEditeur();
  await db.$transaction(async (tx) => {
    const n = await genererTaches(tx, id);
    await tx.journal.create({ data: { dossierId: id, auteurId: moi.id, action: "Procédure ouverte", details: `${n} étapes créées` } });
  });
  revalidatePath(`/dossiers/${id}`);
}

const schemaTache = z.object({
  statut: z.nativeEnum(StatutTache),
});

/** Coche, décoche ou passe en « non applicable » une étape de la procédure. */
export async function changerStatutTache(tacheId: string, statut: StatutTache) {
  const moi = await utilisateurCourant();
  const { statut: s } = schemaTache.parse({ statut });
  const tache = await db.tacheDossier.findUniqueOrThrow({ where: { id: tacheId } });
  // Le terrain ne peut cocher que les étapes qui lui sont assignées.
  if (!peutModifierDossiers(moi.role) && tache.assigneId !== moi.id) throw new Error("Action non autorisée.");
  await db.$transaction([
    db.tacheDossier.update({
      where: { id: tacheId },
      data: s === "A_FAIRE" ? { statut: s, faitLe: null, faitParId: null } : { statut: s, faitLe: new Date(), faitParId: moi.id },
    }),
    db.journal.create({
      data: {
        dossierId: tache.dossierId,
        auteurId: moi.id,
        action: s === "FAIT" ? "Étape faite" : s === "NON_APPLICABLE" ? "Étape non applicable" : "Étape rouverte",
        details: `${tache.numero}. ${tache.libelle}`,
      },
    }),
  ]);
  revalidatePath(`/dossiers/${tache.dossierId}`);
  revalidatePath("/taches");
}

export async function modifierTache(tacheId: string, form: FormData) {
  await exigerEditeur();
  const assigneId = String(form.get("assigneId") ?? "") || null;
  const echeance = String(form.get("echeance") ?? "");
  const commentaire = String(form.get("commentaire") ?? "").trim() || null;
  const tache = await db.tacheDossier.update({
    where: { id: tacheId },
    data: { assigneId, commentaire, echeance: echeance ? debutJour(new Date(echeance)) : null },
  });
  revalidatePath(`/dossiers/${tache.dossierId}`);
  revalidatePath("/taches");
}

export async function ajouterNote(id: string, form: FormData) {
  const moi = await utilisateurCourant();
  const texte = String(form.get("note") ?? "").trim();
  if (!texte) return;
  await db.journal.create({ data: { dossierId: id, auteurId: moi.id, action: "Note", details: texte } });
  revalidatePath(`/dossiers/${id}`);
}
