import type { Ancre, Dossier, Prisma } from "@prisma/client";

type DatesDossier = Pick<
  Dossier,
  "dateReception" | "dateReperage" | "dateConfirmation" | "dateDebut" | "dateFin" | "datePerte"
>;

type EcheanceRelative = {
  ancre: Ancre | null;
  decalage: number;
  ancreRepli: Ancre | null;
  decalageRepli: number;
};

const JOUR_MS = 24 * 60 * 60 * 1000;

export function ajouterJours(date: Date, jours: number) {
  return new Date(date.getTime() + jours * JOUR_MS);
}

/** Minuit (UTC) du jour donné : toutes les échéances sont des dates sans heure. */
export function debutJour(date: Date = new Date()) {
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
}

/** Premier samedi à partir de la date donnée (réunion d'évaluation hebdomadaire). */
function samediSuivant(date: Date) {
  const decalage = (6 - date.getUTCDay() + 7) % 7;
  return ajouterJours(debutJour(date), decalage);
}

function dateAncre(dossier: DatesDossier, ancre: Ancre, decalage: number): Date | null {
  switch (ancre) {
    case "RECEPTION":
      return dossier.dateReception;
    case "REPERAGE":
      return dossier.dateReperage;
    case "CONFIRMATION":
      return dossier.dateConfirmation;
    case "EVENEMENT":
      // « EVENT-3 » se compte depuis le premier jour, « EVENT+1 » depuis le dernier.
      return decalage > 0 ? (dossier.dateFin ?? dossier.dateDebut) : dossier.dateDebut;
    case "PERTE":
      return dossier.datePerte;
    case "HEBDO": {
      const fin = dossier.dateFin ?? dossier.dateDebut;
      return fin ? samediSuivant(ajouterJours(fin, 1)) : null;
    }
  }
}

/** Échéance d'une étape pour un dossier ; null tant que la date de référence n'est pas connue. */
export function calculerEcheance(etape: EcheanceRelative, dossier: DatesDossier): Date | null {
  const candidates: [Ancre | null, number][] = [
    [etape.ancre, etape.decalage],
    [etape.ancreRepli, etape.decalageRepli],
  ];
  for (const [ancre, decalage] of candidates) {
    if (!ancre) continue;
    const base = dateAncre(dossier, ancre, decalage);
    if (base) return ajouterJours(debutJour(base), ancre === "HEBDO" ? 0 : decalage);
  }
  return null;
}

/** Les étapes « CC » reviennent au chargé de compte du dossier. */
function assigneParDefaut(responsable: string, chargeId: string | null) {
  return /^\s*CC\b/i.test(responsable) ? chargeId : null;
}

/** Crée les tâches du dossier à partir du modèle de procédure actif. */
export async function genererTaches(tx: Prisma.TransactionClient, dossierId: string) {
  const dossier = await tx.dossier.findUniqueOrThrow({ where: { id: dossierId } });
  const modele = await tx.modeleProcedure.findFirst({
    where: { actif: true },
    include: { etapes: { orderBy: { numero: "asc" } } },
  });
  if (!modele) return 0;
  const { count } = await tx.tacheDossier.createMany({
    data: modele.etapes.map((e) => ({
      dossierId,
      etapeId: e.id,
      numero: e.numero,
      section: e.section,
      libelle: e.libelle,
      responsable: e.responsable,
      assigneId: assigneParDefaut(e.responsable, dossier.chargeId),
      echeance: calculerEcheance(e, dossier),
    })),
    skipDuplicates: true,
  });
  return count;
}

/** À appeler quand une date du dossier change : les échéances suivent. */
export async function recalculerEcheances(tx: Prisma.TransactionClient, dossierId: string) {
  const dossier = await tx.dossier.findUniqueOrThrow({ where: { id: dossierId } });
  const taches = await tx.tacheDossier.findMany({
    where: { dossierId, statut: "A_FAIRE", etapeId: { not: null } },
    include: { etape: true },
  });
  for (const t of taches) {
    if (!t.etape) continue;
    const echeance = calculerEcheance(t.etape, dossier);
    if (echeance?.getTime() !== t.echeance?.getTime()) {
      await tx.tacheDossier.update({ where: { id: t.id }, data: { echeance } });
    }
  }
}

/** Filtre Prisma des tâches en retard à une date donnée. */
export function filtreEnRetard(aujourdHui: Date = debutJour()): Prisma.TacheDossierWhereInput {
  return { statut: "A_FAIRE", echeance: { lt: aujourdHui } };
}
