import { describe, it, expect } from "vitest";
import JSZip from "jszip";
import { genereerWerkdossierWordDocument } from "./genereerWerkdossierWordDocument";
import { berekenSchenking } from "@/data/belastingen";

async function leesDocumentXml(buffer: Buffer): Promise<string> {
  const zip = await JSZip.loadAsync(buffer);
  return zip.file("word/document.xml")!.async("text");
}

async function leesSettingsXml(buffer: Buffer): Promise<string> {
  const zip = await JSZip.loadAsync(buffer);
  return zip.file("word/settings.xml")!.async("text");
}

describe("genereerWerkdossierWordDocument", () => {
  it("genereert een geldig .docx-bestand (ZIP-magicbytes)", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      modeldocumentTitel: "Testmodel",
      ontwerp: "1. IDENTIFICATIE\n\nDe heer Jan Janssens.",
      brieven: [],
      afrekening: {},
    });
    expect(buffer.subarray(0, 2).toString("latin1")).toBe("PK");
  });

  it("begint met de instructiesectie voor de AI Agent, gevolgd door het correct genummerde ontwerp", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      modeldocumentTitel: "Testmodel",
      ontwerp: "1. IDENTIFICATIE\n\nDe heer Jan Janssens.",
      brieven: [],
      afrekening: {},
    });
    const xml = await leesDocumentXml(buffer);
    const posInstructies = xml.indexOf("Instructies voor de AI Agent");
    const posOntwerp = xml.indexOf("Ontwerpdocument");
    expect(posInstructies).toBeGreaterThan(-1);
    expect(posOntwerp).toBeGreaterThan(-1);
    expect(posInstructies).toBeLessThan(posOntwerp);
    expect(xml).toContain("1. Instructies voor de AI Agent");
    // Zonder modelmails is het ontwerp sectie 2.
    expect(xml).toContain("2. Ontwerpdocument");
    expect(xml).toContain("Stuur deze gegevens nooit naar of via Notary.AI");
  });

  it("plaatst de modelmail(s) vóór het ontwerp en nummert door (mails 2, ontwerp 3, afrekening 4)", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      modeldocumentTitel: "Testmodel",
      ontwerp: "1. IDENTIFICATIE\n\nDe heer Jan Janssens.",
      brieven: [
        {
          titel: "Begeleidende mail",
          ingevuld: { onderwerp: "Onderwerp", tekst: "Beste,", bijlagen: [], rol: "client-koper" },
        },
      ],
      afrekening: {
        belasting: {
          resultaat: berekenSchenking("Vlaanderen", "onroerend", "rechte_lijn_partner", 100_000),
          soort: "schenking",
          gewest: "Vlaanderen",
        },
      },
    });
    const xml = await leesDocumentXml(buffer);
    expect(xml).toContain("2. Modelmail(s)");
    expect(xml).toContain("3. Ontwerpdocument");
    expect(xml).toContain("4. Afrekening");
    // De modelmail(s) komen vóór het ontwerp van compromis.
    expect(xml.indexOf("Modelmail(s)")).toBeLessThan(xml.indexOf("Ontwerpdocument"));
    // Backlog punt 32: de gestructureerde geadresseerde-rol staat vlak boven het onderwerp.
    expect(xml).toContain("Geadresseerde: Cliënt (koper)");
    expect(xml.indexOf("Geadresseerde:")).toBeLessThan(xml.indexOf("Onderwerp:"));
  });

  it("bevat het Structuuranker met de oorspronkelijke hoofdstukkenlijst van het ontwerp", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      modeldocumentTitel: "Testmodel",
      ontwerp: "## 1. IDENTIFICATIE\n\nDe heer Jan Janssens.\n\n## 2. PRIJS\n\nDe prijs bedraagt …",
      brieven: [],
      afrekening: {},
    });
    const xml = await leesDocumentXml(buffer);
    expect(xml).toContain("Structuuranker");
    expect(xml).toContain("1. IDENTIFICATIE");
    expect(xml).toContain("2. PRIJS");
  });

  it("bevat een klikbare inhoudsopgave (TOC-veld) ná de instructiepagina's en vóór de aktetekst (backlog punt 31)", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      modeldocumentTitel: "Testmodel",
      ontwerp: "1. IDENTIFICATIE\n\nDe heer Jan Janssens.",
      brieven: [],
      afrekening: {},
    });
    const xml = await leesDocumentXml(buffer);
    expect(xml).toContain("Inhoudsopgave");
    expect(xml).toMatch(/TOC[^<]*\\o[^<]*1-4/);
    const posOntwerpKop = xml.indexOf("2. Ontwerpdocument");
    const posToc = xml.search(/TOC[^<]*\\o/);
    const posAktetekst = xml.indexOf("1. IDENTIFICATIE", posToc);
    expect(posOntwerpKop).toBeGreaterThan(-1);
    expect(posToc).toBeGreaterThan(posOntwerpKop);
    expect(posAktetekst).toBeGreaterThan(posToc);
    const settings = await leesSettingsXml(buffer);
    expect(settings).toMatch(/<w:updateFields\s*\/>|<w:updateFields\b[^>]*w:val="(1|true)"/);
  });

  it("schakelt 'Wijzigingen bijhouden' (track changes) in op documentniveau", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      modeldocumentTitel: "Testmodel",
      ontwerp: "1. IDENTIFICATIE",
      brieven: [],
      afrekening: {},
    });
    const xml = await leesSettingsXml(buffer);
    // Het echte OOXML-element (w:trackChanges) staat aan; de onwerkzame
    // w:trackRevisions-marker van de docx-bibliotheek is opgeruimd.
    expect(xml).toMatch(/<w:trackChanges\s*\/>/);
    expect(xml).not.toContain("trackRevisions");
  });
});

describe("bevindingen van de voorbereidende agent", () => {
  const basis = {
    modeldocumentTitel: "Testmodel",
    ontwerp: "1. IDENTIFICATIE\n\nDe heer {{naam}}.",
    brieven: [],
    afrekening: {},
  };

  it("rendert de bevindingen als tabel mét zekerheidsgraad, bron en de dubbelcheck-instructie", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      ...basis,
      bevindingen: [
        { parameter: "prijs_cijfers", waarde: "395.000", zekerheid: 95, bron: "compromis p. 1" },
        { parameter: "epc_label", waarde: "D", zekerheid: "hoog", toelichting: "EPC-attest aangeleverd" },
      ],
    });
    const xml = await leesDocumentXml(buffer);
    expect(xml).toContain("Bevindingen van de voorbereidende agent");
    expect(xml).toContain("prijs_cijfers");
    expect(xml).toContain("395.000");
    expect(xml).toContain("95 %"); // numerieke zekerheid leesbaar gemaakt
    expect(xml).toContain("compromis p. 1");
    expect(xml).toContain("epc_label");
    expect(xml).toContain("hoog");
    expect(xml).toContain("EPC-attest aangeleverd");
    // De Word-agent krijgt de dubbelcheck-instructie (geen heridentificatie).
    expect(xml).toContain("identificeer ze niet opnieuw");
    // Tabelstructuur aanwezig (echte Word-tabel, geen platte tekst).
    expect(xml).toContain("<w:tbl>");
  });

  it("slaat onvolledige bevindingen over mét één duidelijke markering en weigert nooit", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      ...basis,
      bevindingen: [
        { parameter: "prijs_cijfers", waarde: "395.000", zekerheid: 90 },
        { parameter: "", waarde: "zonder parameternaam" },
        { parameter: "zonder_waarde", waarde: "" },
      ],
    });
    const xml = await leesDocumentXml(buffer);
    expect(xml).toContain("prijs_cijfers");
    expect(xml).toContain("onvolledig"); // markering voor de weggelaten bevindingen
    expect(xml).not.toContain("zonder parameternaam");
  });

  it("toont zonder bevindingen de expliciete 'geen bevindingen aangeleverd'-melding (geen tabel, geen impliciete afwezigheid)", async () => {
    const buffer = await genereerWerkdossierWordDocument(basis);
    const xml = await leesDocumentXml(buffer);
    // Geen tabel of kolomkoppen — maar wél de sectie met de expliciete melding,
    // zodat agent 2 "vanaf nul identificeren" nooit uit een afwezige tabel
    // hoeft af te leiden (zie OVERDRACHT-TWEE-AGENTS.md).
    expect(xml).not.toContain("<w:tbl>");
    expect(xml).not.toContain("Bron / toelichting");
    expect(xml).toContain("Geen bevindingen aangeleverd door de voorbereidende agent");
    expect(xml).toContain("identificeer alle parameters zelf vanaf nul");
  });

  it("zet de spellingtaal, documentmetadata en paginavoettekst mee (per taal)", async () => {
    const zipVan = async (taal: "nl" | "fr") =>
      JSZip.loadAsync(
        await genereerWerkdossierWordDocument({
          modeldocumentTitel: "Testmodel",
          ontwerp: "1. IDENTIFICATIE\n\nDe heer Jan Janssens.",
          brieven: [],
          afrekening: {},
          taal,
        })
      );
    const nl = await zipVan("nl");
    const stylesNl = await nl.file("word/styles.xml")!.async("text");
    expect(stylesNl.match(/<w:docDefaults>[\s\S]*?<\/w:docDefaults>/)?.[0]).toMatch(/<w:lang w:val="nl-BE"/);
    const core = await nl.file("docProps/core.xml")!.async("text");
    expect(core).toContain("<dc:creator>Notary.AI</dc:creator>");
    expect(core).toMatch(/<dc:title>Werkdossier — Testmodel<\/dc:title>/);
    const footerNaamNl = Object.keys(nl.files).find((n) => /word\/footer\d*\.xml/.test(n))!;
    expect(await nl.file(footerNaamNl)!.async("text")).toContain("Pagina ");

    const fr = await zipVan("fr");
    const stylesFr = await fr.file("word/styles.xml")!.async("text");
    expect(stylesFr.match(/<w:docDefaults>[\s\S]*?<\/w:docDefaults>/)?.[0]).toMatch(/<w:lang w:val="fr-BE"/);
    const footerNaamFr = Object.keys(fr.files).find((n) => /word\/footer\d*\.xml/.test(n))!;
    expect(await fr.file(footerNaamFr)!.async("text")).toContain("Page ");
  });
});

describe("geraadpleegde brondocumenten (stukkenmanifest, twee-agent gouden pad)", () => {
  const basis = {
    modeldocumentTitel: "Testmodel",
    ontwerp: "1. IDENTIFICATIE\n\nDe heer {{naam}}.",
    brieven: [],
    afrekening: {},
  };

  it("herleidt het manifest uit de unieke 'bron'-vermeldingen van de bevindingen", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      ...basis,
      bevindingen: [
        { parameter: "prijs_cijfers", waarde: "395.000", zekerheid: 95, bron: "compromis p. 1" },
        { parameter: "epc_label", waarde: "D", zekerheid: "hoog", bron: "EPC-attest" },
        { parameter: "voorschot", waarde: "39.500", zekerheid: 90, bron: "compromis p. 1" }, // duplicaat bron
      ],
    });
    const xml = await leesDocumentXml(buffer);
    // De intro-tekst van de sectie verschijnt enkel wanneer ze effectief rendert.
    expect(xml).toContain("Controleer dat je exact deze stukken");
    expect(xml).toContain("compromis p. 1");
    expect(xml).toContain("EPC-attest");
  });

  it("toont een expliciete leeg-melding wanneer bevindingen aanwezig zijn maar geen enkele een 'bron' draagt", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      ...basis,
      bevindingen: [{ parameter: "prijs_cijfers", waarde: "395.000", zekerheid: 95 }],
    });
    const xml = await leesDocumentXml(buffer);
    expect(xml).toContain("Geen brondocumenten vermeld");
  });

  it("laat de sectie zelf volledig weg zonder bevindingen (niets om uit af te leiden)", async () => {
    const buffer = await genereerWerkdossierWordDocument(basis);
    const xml = await leesDocumentXml(buffer);
    // De sectienaam staat wél in de instructietekst (beschrijft de opbouw),
    // maar de sectie zelf (met haar intro-tekst of leeg-melding) mag niet verschijnen.
    expect(xml).not.toContain("Controleer dat je exact deze stukken");
    expect(xml).not.toContain("Geen brondocumenten vermeld");
  });
});

// ── Golden path: de volledige werkbladopbouw in samenhang ────────────────────
// De losse bouwers zijn elk apart getest; deze test roept de volledige
// generatie aan voor één representatief scenario en controleert dat élke
// werkbladsectie effectief verschijnt, in de vaste volgorde (korte prompt →
// instructies → ontbrekende gegevens → structuuranker → bevindingen →
// stukkenmanifest → notities → mails → ontwerp → afrekening). Een sectie die
// per ongeluk uit de samenstelling valt (genereerWerkdossierWordDocument
// vergeet een bouwer aan te roepen), faalt hier — de per-bouwer-tests zien
// dat niet.
describe("golden path — volledige werkbladopbouw in samenhang", () => {
  it("rendert alle werkbladsecties in de vaste volgorde voor een representatief, volledig scenario", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      modeldocumentTitel: "Verkoopovereenkomst (heldere taal)",
      ontwerp: "## 1. IDENTIFICATIE\n\nDe heer [AAN TE VULLEN: naam_koper].\n\n## 2. PRIJS\n\nDe prijs bedraagt [AAN TE VULLEN: prijs_cijfers] EUR.",
      brieven: [
        {
          titel: "Begeleidende mail",
          ingevuld: { onderwerp: "Uw dossier", tekst: "Beste,", bijlagen: [], rol: "client-koper" },
        },
      ],
      afrekening: {
        belasting: {
          resultaat: berekenSchenking("Vlaanderen", "onroerend", "rechte_lijn_partner", 100_000),
          soort: "schenking",
          gewest: "Vlaanderen",
        },
      },
      taal: "nl",
      notitiesGebruiker: "Cliënt belde: sleuteloverdracht vervroegd naar 1 augustus.",
      bevindingen: [
        { parameter: "prijs_cijfers", waarde: "395.000", zekerheid: 95, bron: "compromis p. 1" },
        { parameter: "epc_label", waarde: "D", zekerheid: "hoog", bron: "EPC-attest" },
      ],
    });
    const xml = await leesDocumentXml(buffer);

    // Elke sectie herkend aan een tekst die uniek is voor die sectie (niet aan
    // de sectienaam, die ook in de instructie-/verwijdertekst voorkomt).
    const ankers = [
      ["korte prompt", "Te kopiëren in Word M365 Copilot"],
      ["instructies", "GOUDEN REGEL — LEVER ALTIJD AF"],
      ["ontbrekende gegevens", "Ontbrekende documenten (op prioriteit):"],
      ["structuuranker", "Dit is de oorspronkelijke, doorlopend genummerde hoofdstukkenlijst"],
      ["bevindingen", "De voorbereidende agent identificeerde onderstaande parameters"],
      ["stukkenmanifest", "De voorbereidende agent baseerde de bevindingen hierboven"],
      ["notities", "sleuteloverdracht vervroegd naar 1 augustus"],
      ["modelmails", "Modelmail(s)"],
      ["ontwerp", "Ontwerpdocument — Verkoopovereenkomst (heldere taal)"],
      ["afrekening", "Schenkbelasting (Vlaanderen)"],
    ] as const;
    let vorige = -1;
    for (const [naam, anker] of ankers) {
      const positie = xml.indexOf(anker);
      expect(positie, `sectie '${naam}' (anker "${anker}") ontbreekt`).toBeGreaterThan(-1);
      expect(positie, `sectie '${naam}' staat niet ná de vorige sectie`).toBeGreaterThan(vorige);
      vorige = positie;
    }

    // De inhoud van de secties is effectief gevuld (geen loze koppen).
    expect(xml).toContain("prijs_cijfers");
    expect(xml).toContain("compromis p. 1");
    expect(xml).toContain("EPC-attest");
    expect(xml).toContain("2. PRIJS");
  });

  it("kale variant (geen bevindingen/notities): expliciete geen-bevindingen-melding, geen stukkenmanifest of notities", async () => {
    const buffer = await genereerWerkdossierWordDocument({
      modeldocumentTitel: "Testmodel",
      ontwerp: "## 1. IDENTIFICATIE\n\nDe heer Jan Janssens.",
      brieven: [],
      afrekening: {},
    });
    const xml = await leesDocumentXml(buffer);
    expect(xml).toContain("Geen bevindingen aangeleverd door de voorbereidende agent");
    expect(xml).not.toContain("De voorbereidende agent baseerde de bevindingen hierboven");
    expect(xml).not.toContain("Geen brondocumenten vermeld");
    expect(xml).not.toContain("Informations complémentaires");
  });
});
