// Suppression des jeux de démonstration importés.
//
// ⚠️ SEULE opération destructive de tout le module. Sa règle de sûreté :
//
//   On ne supprime QUE ce que l'import a créé — jamais ce qu'il a réutilisé.
//
// L'import est additif : s'il rencontre une organisation dont le code figure
// déjà en base, il la RÉUTILISE et y ajoute ses interventions. Deux situations
// coexistent donc après un import, et elles n'appellent pas le même traitement :
//
//   1. Organisation CRÉÉE par l'import  → supprimée en entier (cascade).
//   2. Organisation PRÉEXISTANTE réutilisée → l'organisation reste, seules les
//      interventions de démonstration (dont le numéro figure dans le fichier)
//      en sont retirées.
//
// Confondre les deux détruirait des données réelles. Cas rencontré pour de vrai
// sur une base de développement : les organisations NOVA, MEDC, BRHO et HELX
// existaient sous d'autres noms (« Novatel », « MédiaCorp », « Banque Rhône »,
// « Helix Santé ») et l'import les avait simplement réutilisées.
//
// DEUX MODES d'identification :
//   · « trace »     — les enregistrements `ImportDemo` listent les clés
//                     réellement créées. C'est la source fiable.
//   · « détection » — aucune trace (jeu importé avant que la traçabilité
//                     n'existe, en v0.83.0) : on retombe sur une comparaison
//                     avec le fichier de référence, en exigeant que le code ET
//                     le nom correspondent. Moins sûr, donc l'interface affiche
//                     la liste exacte pour relecture avant confirmation.

import type { PrismaClient } from "@prisma/client";

import { supprimer } from "@/server/storage/storage";

import type { JeuDemo } from "./format";

/** Organisation supprimée en entier. */
export type CibleOrganisation = { code: string; nom: string; interventions: number };
/** Organisation conservée, dont on ne retire que les interventions de démonstration. */
export type CibleInterventions = { code: string; nom: string; interventions: number };

export type EtatJeuxDemo = {
  present: boolean;
  mode: "trace" | "detection";
  imports: {
    id: string;
    libelle: string;
    genereLe: string;
    horodatage: Date;
    auteurNom: string | null;
  }[];
  organisationsSupprimees: CibleOrganisation[];
  organisationsConservees: CibleInterventions[];
  /** Total des interventions qui disparaîtront, tous cas confondus. */
  interventions: number;
  comptes: number;
  fournisseurs: number;
};

export type BilanPurge = {
  organisations: number;
  interventionsSeules: number;
  interventions: number;
  comptes: number;
  fournisseurs: number;
  fichiers: number;
  avertissements: string[];
};

function lireListe(json: string): string[] {
  try {
    const v = JSON.parse(json);
    return Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : [];
  } catch {
    return [];
  }
}

/**
 * Détermine ce qui appartient au jeu de démonstration.
 *
 * `reference` est le fichier officiel : il fournit les numéros d'intervention,
 * indispensables pour nettoyer les organisations réutilisées, et sert de base à
 * la détection quand aucune trace n'existe.
 */
async function analyser(prisma: PrismaClient, reference: JeuDemo | null) {
  const imports = await prisma.importDemo.findMany({ orderBy: { horodatage: "desc" } });

  const tracesTenants = new Set<string>();
  const tracesUtilisateurs = new Set<string>();
  const tracesFournisseurs = new Set<string>();
  for (const i of imports) {
    for (const c of lireListe(i.tenants)) tracesTenants.add(c);
    for (const e of lireListe(i.utilisateurs)) tracesUtilisateurs.add(e);
    for (const f of lireListe(i.fournisseurs)) tracesFournisseurs.add(f);
  }

  const mode: "trace" | "detection" = imports.length > 0 ? "trace" : "detection";

  // Numéros d'intervention du fichier, par code d'organisation.
  const numerosParCode = new Map<string, string[]>();
  for (const it of reference?.interventions ?? []) {
    const l = numerosParCode.get(it.client) ?? [];
    l.push(it.numero);
    numerosParCode.set(it.client, l);
  }

  const codesReference = new Set(reference?.clients.map((c) => c.code) ?? []);
  const nomParCode = new Map((reference?.clients ?? []).map((c) => [c.code, c.nom] as const));

  // ── Organisations à supprimer en entier ──────────────────────
  let aSupprimer: string[];
  if (mode === "trace") {
    aSupprimer = [...tracesTenants];
  } else {
    // Détection : code ET nom doivent correspondre. Exiger le nom écarte les
    // organisations préexistantes qui partageraient seulement un code — c'est
    // toute la différence entre nettoyer une démo et détruire un vrai client.
    const candidats = await prisma.tenant.findMany({
      where: { code: { in: [...codesReference] } },
      select: { code: true, nom: true },
    });
    aSupprimer = candidats.filter((t) => nomParCode.get(t.code) === t.nom).map((t) => t.code);
  }

  // ── Organisations conservées, à nettoyer intervention par intervention ──
  const conservees = [...codesReference].filter((c) => !aSupprimer.includes(c));

  const organisationsSupprimees: CibleOrganisation[] = [];
  for (const code of aSupprimer) {
    const t = await prisma.tenant.findUnique({ where: { code }, select: { code: true, nom: true } });
    if (!t) continue; // déjà supprimée
    organisationsSupprimees.push({
      code: t.code,
      nom: t.nom,
      interventions: await prisma.ticket.count({ where: { tenant: { code } } }),
    });
  }

  const organisationsConservees: CibleInterventions[] = [];
  for (const code of conservees) {
    const numeros = numerosParCode.get(code) ?? [];
    if (!numeros.length) continue;
    const t = await prisma.tenant.findUnique({ where: { code }, select: { id: true, code: true, nom: true } });
    if (!t) continue;
    const n = await prisma.ticket.count({ where: { tenantId: t.id, numero: { in: numeros } } });
    if (n > 0) organisationsConservees.push({ code: t.code, nom: t.nom, interventions: n });
  }

  // ── Comptes et fournisseurs ──────────────────────────────────
  const emailsReference = [
    ...(reference?.techniciens ?? []).map((t) => t.email),
    ...(reference?.exploitants ?? []).map((e) => e.email),
    ...(reference?.clients ?? []).flatMap((c) => c.contacts.map((k) => k.email)),
  ];
  const emails = mode === "trace" ? [...tracesUtilisateurs] : emailsReference;
  const nomsFournisseurs =
    mode === "trace" ? [...tracesFournisseurs] : (reference?.fournisseurs ?? []).map((f) => f.nom);

  return {
    mode,
    imports,
    organisationsSupprimees,
    organisationsConservees,
    emails,
    nomsFournisseurs,
    numerosParCode,
  };
}

/** Décrit ce qu'une purge supprimerait, sans rien modifier. */
export async function etatJeuxDemo(
  prisma: PrismaClient,
  reference: JeuDemo | null = null,
): Promise<EtatJeuxDemo> {
  const a = await analyser(prisma, reference);

  const [comptes, fournisseurs] = await Promise.all([
    a.emails.length ? prisma.user.count({ where: { email: { in: a.emails } } }) : 0,
    a.nomsFournisseurs.length
      ? prisma.fournisseur.count({ where: { nom: { in: a.nomsFournisseurs } } })
      : 0,
  ]);

  const interventions =
    a.organisationsSupprimees.reduce((n, o) => n + o.interventions, 0) +
    a.organisationsConservees.reduce((n, o) => n + o.interventions, 0);

  const present =
    a.organisationsSupprimees.length > 0 ||
    a.organisationsConservees.length > 0 ||
    comptes > 0 ||
    fournisseurs > 0;

  return {
    present,
    mode: a.mode,
    imports: a.imports.map((i) => ({
      id: i.id,
      libelle: i.libelle,
      genereLe: i.genereLe,
      horodatage: i.horodatage,
      auteurNom: i.auteurNom,
    })),
    organisationsSupprimees: a.organisationsSupprimees,
    organisationsConservees: a.organisationsConservees,
    interventions,
    comptes,
    fournisseurs,
  };
}

/**
 * Supprime le jeu de démonstration.
 *
 * `protegerId` est le compte qui déclenche la purge : il ne peut jamais être
 * supprimé, même s'il figurait par accident dans la liste. Se déconnecter
 * soi-même en purgeant serait un piège difficile à rattraper.
 */
export async function purgerJeuxDemo(
  prisma: PrismaClient,
  { protegerId, reference = null }: { protegerId: string; reference?: JeuDemo | null },
): Promise<BilanPurge> {
  const bilan: BilanPurge = {
    organisations: 0,
    interventionsSeules: 0,
    interventions: 0,
    comptes: 0,
    fournisseurs: 0,
    fichiers: 0,
    avertissements: [],
  };

  const a = await analyser(prisma, reference);
  const codesSupprimes = a.organisationsSupprimees.map((o) => o.code);

  /** Efface les fichiers de livrables du disque : la base ne les emporte pas. */
  async function effacerFichiers(where: object) {
    const livrables = await prisma.livrable.findMany({
      where,
      select: { cheminStockage: true },
    });
    for (const l of livrables) {
      try {
        await supprimer(l.cheminStockage);
        bilan.fichiers += 1;
      } catch {
        // Un fichier déjà absent n'est pas une erreur : le but est qu'il ne soit
        // plus là. On ne fait pas échouer la purge pour cela.
      }
    }
  }

  // ── 1. Organisations créées par l'import : suppression totale ──
  if (codesSupprimes.length) {
    // Chemins relevés AVANT la cascade, sinon ils disparaissent avec les lignes.
    await effacerFichiers({ tenant: { code: { in: codesSupprimes } } });
    bilan.interventions += a.organisationsSupprimees.reduce((n, o) => n + o.interventions, 0);
    const r = await prisma.tenant.deleteMany({ where: { code: { in: codesSupprimes } } });
    bilan.organisations = r.count;
  }

  // ── 2. Organisations réutilisées : retrait ciblé des interventions ──
  for (const o of a.organisationsConservees) {
    const numeros = a.numerosParCode.get(o.code) ?? [];
    const tenant = await prisma.tenant.findUnique({ where: { code: o.code }, select: { id: true } });
    if (!tenant || !numeros.length) continue;

    const tickets = await prisma.ticket.findMany({
      where: { tenantId: tenant.id, numero: { in: numeros } },
      select: { id: true },
    });
    const ids = tickets.map((t) => t.id);
    if (!ids.length) continue;

    await effacerFichiers({ ticketId: { in: ids } });

    // ⚠️ `LienOptique.ticket` est en SetNull, pas en Cascade : sans ce retrait
    // explicite, les liens et leurs mesures survivraient à leur intervention et
    // resteraient visibles dans une organisation qu'on croit nettoyée.
    await prisma.lienOptique.deleteMany({ where: { ticketId: { in: ids } } });

    // Le journal n'a pas de clé étrangère vers Ticket (`entiteId` est un champ
    // libre) : il faut donc le nettoyer à la main.
    await prisma.journalEvenement.deleteMany({ where: { entite: "Ticket", entiteId: { in: ids } } });

    const r = await prisma.ticket.deleteMany({ where: { id: { in: ids } } });
    bilan.interventionsSeules += r.count;
    bilan.interventions += r.count;
  }

  // ── 3. Comptes ────────────────────────────────────────────────
  if (a.emails.length) {
    const r = await prisma.user.deleteMany({
      where: { email: { in: a.emails }, id: { not: protegerId } },
    });
    bilan.comptes = r.count;
  }

  // ── 4. Fournisseurs ───────────────────────────────────────────
  // `PieceRemplacee.fournisseurId` est en SetNull : si l'exploitant a utilisé un
  // fournisseur de démonstration sur une intervention conservée, la pièce
  // survit et perd seulement sa référence. On le signale plutôt que de le taire.
  if (a.nomsFournisseurs.length) {
    const encoreUtilises = await prisma.pieceRemplacee.count({
      where: { fournisseur: { nom: { in: a.nomsFournisseurs } } },
    });
    if (encoreUtilises > 0) {
      bilan.avertissements.push(
        `${encoreUtilises} pièce(s) d'interventions conservées référençaient un fournisseur de démonstration : ` +
          `la référence a été retirée, la pièce est intacte.`,
      );
    }
    const r = await prisma.fournisseur.deleteMany({ where: { nom: { in: a.nomsFournisseurs } } });
    bilan.fournisseurs = r.count;
  }

  // ── 5. Traces ─────────────────────────────────────────────────
  // En dernier : si une étape échoue, elles subsistent et la purge reste
  // rejouable. Les effacer d'abord rendrait le reste orphelin.
  await prisma.importDemo.deleteMany({});

  return bilan;
}
