import { describe, it, expect } from "vitest";
import JSZip from "jszip";
import { genereerOntwerpWordDocument } from "./genereerOntwerpWordDocument";
import { vulPlaceholdersLokaalIn } from "./vulPlaceholdersLokaalIn";

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("vulPlaceholdersLokaalIn", () => {
  it("vervangt een [AAN TE VULLEN: …]-plaatshouder door een echte getrackte wijziging (invoeging + doorhaling)", async () => {
    const ontwerp = "1. IDENTIFICATIE\n\nDe prijs bedraagt [AAN TE VULLEN: prijs_cijfers] euro.";
    const bron = await genereerOntwerpWordDocument(ontwerp, "nl");
    const r = await vulPlaceholdersLokaalIn(bron, [{ parameter: "prijs_cijfers", waarde: "395.000" }]);

    expect(r.ingevuld).toEqual(["prijs_cijfers"]);
    expect(r.nietGevonden).toEqual([]);
    expect(r.aantalVervangen).toBe(1);

    const xml = await leesDocumentXml(r.buffer);
    // De oude plaatshouder blijft zichtbaar als doorgehaalde tekst.
    expect(xml).toContain("<w:delText");
    expect(xml).toContain("*[*AAN TE VULLEN: prijs_cijfers*]*");
    // De nieuwe waarde staat als ingevoegde tekst.
    expect(xml).toContain("<w:ins");
    expect(xml).toContain("395.000");
    // Geen plaatshouder meer als gewone, niet-doorgehaalde tekst.
    expect(xml.replace(/<w:delText[^>]*>[^<]*<\/w:delText>/g, "")).not.toContain("AAN TE VULLEN: prijs_cijfers");
  });

  it("meldt een niet-gevonden parameter als typfout-signaal, zonder de andere invullingen te blokkeren", async () => {
    const ontwerp = "1. IDENTIFICATIE\n\nDe koper is [AAN TE VULLEN: naam_koper].";
    const bron = await genereerOntwerpWordDocument(ontwerp, "nl");
    const r = await vulPlaceholdersLokaalIn(bron, [
      { parameter: "naam_koper", waarde: "Jan Janssens" },
      { parameter: "naam_koperr", waarde: "tikfout" }, // dubbele 'r': bestaat niet
    ]);

    expect(r.ingevuld).toEqual(["naam_koper"]);
    expect(r.nietGevonden).toEqual(["naam_koperr"]);
    const xml = await leesDocumentXml(r.buffer);
    expect(xml).toContain("Jan Janssens");
    // De niet-gevonden waarde wordt nergens als ingevoegde tekst geschreven
    // (de vaste AI-agentprompt bevat toevallig zelf het woord "tikfouten",
    // vandaar de precieze <w:ins>-check i.p.v. een documentbrede zoekopdracht).
    expect(xml).not.toMatch(/<w:ins[^>]*>(?:(?!<\/w:ins>)[\s\S])*tikfout/);
  });

  it("vervangt de Franse tegenhanger [À COMPLÉTER: …]", async () => {
    const ontwerp = "1. IDENTIFICATION\n\nLe prix s'élève à [AAN TE VULLEN: prix_chiffres] euros.";
    const bron = await genereerOntwerpWordDocument(ontwerp, "fr");
    const r = await vulPlaceholdersLokaalIn(bron, [{ parameter: "prix_chiffres", waarde: "395.000" }]);

    expect(r.ingevuld).toEqual(["prix_chiffres"]);
    const xml = await leesDocumentXml(r.buffer);
    expect(xml).toContain("À COMPLÉTER: prix_chiffres");
    expect(xml).toContain("395.000");
  });

  it("vervangt elke herhaling van dezelfde parameter (meermaals in het document)", async () => {
    const ontwerp =
      "1. IDENTIFICATIE\n\nDe prijs is [AAN TE VULLEN: prijs_cijfers] euro.\n\n2. BETALING\n\nTe betalen: [AAN TE VULLEN: prijs_cijfers] euro.";
    const bron = await genereerOntwerpWordDocument(ontwerp, "nl");
    const r = await vulPlaceholdersLokaalIn(bron, [{ parameter: "prijs_cijfers", waarde: "395.000" }]);

    expect(r.aantalVervangen).toBe(2);
    const xml = await leesDocumentXml(r.buffer);
    expect((xml.match(/395\.000/g) ?? []).length).toBe(2);
  });

  it("splitst een meerregelige waarde over Word-regeleindes (<w:br/>) i.p.v. letterlijke '\\n'", async () => {
    const ontwerp = "1. IDENTIFICATIE\n\n[AAN TE VULLEN: erfdienstbaarheden]";
    const bron = await genereerOntwerpWordDocument(ontwerp, "nl");
    const r = await vulPlaceholdersLokaalIn(bron, [
      { parameter: "erfdienstbaarheden", waarde: "Eerste erfdienstbaarheid.\nTweede erfdienstbaarheid." },
    ]);
    const xml = await leesDocumentXml(r.buffer);
    expect(xml).toContain("Eerste erfdienstbaarheid.");
    expect(xml).toContain("<w:br/>");
    expect(xml).toContain("Tweede erfdienstbaarheid.");
  });

  it("forceert 'Wijzigingen bijhouden' aan, ook al stond het al aan", async () => {
    const ontwerp = "1. IDENTIFICATIE\n\n[AAN TE VULLEN: naam_koper]";
    const bron = await genereerOntwerpWordDocument(ontwerp, "nl");
    const r = await vulPlaceholdersLokaalIn(bron, [{ parameter: "naam_koper", waarde: "Jan Janssens" }]);
    const settings = await leesSettingsXml(r.buffer);
    expect(settings).toContain("<w:trackChanges/>");
  });

  it("escaped XML-gevoelige tekens in de waarde (geen kapotte XML bij & < >)", async () => {
    const ontwerp = "1. IDENTIFICATIE\n\n[AAN TE VULLEN: naam_koper]";
    const bron = await genereerOntwerpWordDocument(ontwerp, "nl");
    const r = await vulPlaceholdersLokaalIn(bron, [{ parameter: "naam_koper", waarde: "Janssens & Peeters <BV>" }]);
    const xml = await leesDocumentXml(r.buffer);
    expect(xml).toContain("Janssens &amp; Peeters &lt;BV&gt;");
    expect(xml).not.toContain("Janssens & Peeters <BV>");
  });

  it("laat het document ongewijzigd wanneer geen enkele invulling matcht (alles nietGevonden)", async () => {
    const ontwerp = "1. IDENTIFICATIE\n\n[AAN TE VULLEN: naam_koper]";
    const bron = await genereerOntwerpWordDocument(ontwerp, "nl");
    const r = await vulPlaceholdersLokaalIn(bron, [{ parameter: "onbestaande_parameter", waarde: "x" }]);
    expect(r.ingevuld).toEqual([]);
    expect(r.nietGevonden).toEqual(["onbestaande_parameter"]);
    expect(r.aantalVervangen).toBe(0);
    const xml = await leesDocumentXml(r.buffer);
    expect(xml).toContain("AAN TE VULLEN: naam_koper");
  });

  it("raakt nooit de ingebedde AI-agentprompt aan wanneer een parameternaam niet matcht (regressie: begrensde rPr-match)", async () => {
    // De echte werkdossierdocumenten beginnen met een lange, vaste
    // instructiesectie (WERKDOSSIER_AGENT_PROMPT) vóór het eigenlijke
    // ontwerp. Een te ruime regex kan bij een mislukte poging dwars door die
    // sectie heen "eten" tot de eerstvolgende toevallige treffer — deze test
    // dekt precies dat scenario met een echt gegenereerd werkdossierdocument.
    const ontwerp = "1. IDENTIFICATIE\n\nDe koper is [AAN TE VULLEN: naam_koper].";
    const bron = await genereerOntwerpWordDocument(ontwerp, "nl");
    const vooraf = await leesDocumentXml(bron);

    const r = await vulPlaceholdersLokaalIn(bron, [{ parameter: "naam_koper", waarde: "Jan Janssens" }]);
    const xml = await leesDocumentXml(r.buffer);

    // De instructiesectie (bv. de titel "Te kopiëren in Word M365 Copilot:")
    // moet volledig onaangeroerd blijven — geen enkel deel ervan mag binnen
    // een <w:ins>/<w:del> terechtkomen.
    expect(xml).toContain("Te kopiëren in Word M365 Copilot:");
    expect(xml).not.toMatch(/<w:ins[^>]*>[\s\S]*Te kopiëren in Word M365 Copilot:[\s\S]*<\/w:ins>/);
    expect(xml).not.toMatch(/<w:del[^>]*>[\s\S]*Te kopiëren in Word M365 Copilot:[\s\S]*<\/w:del>/);
    // Het aantal instructie-tekens vóór en na de bewerking moet nagenoeg gelijk
    // blijven (enkel de kleine [AAN TE VULLEN]-vervanging maakt het verschil) —
    // een gigantische ongewenste opname zou de lengte met tienduizenden tekens
    // doen groeien.
    expect(xml.length - vooraf.length).toBeLessThan(500);
  });

  it("is deterministisch los van het tijdstip: dezelfde invoer + vaste datum geeft byte-identieke uitvoer", async () => {
    const ontwerp = "1. IDENTIFICATIE\n\n[AAN TE VULLEN: naam_koper]";
    const bron = await genereerOntwerpWordDocument(ontwerp, "nl");
    const opties = { auteur: "Test", datum: "2026-07-02T00:00:00.000Z" };
    const a = await vulPlaceholdersLokaalIn(bron, [{ parameter: "naam_koper", waarde: "Jan Janssens" }], opties);
    const b = await vulPlaceholdersLokaalIn(bron, [{ parameter: "naam_koper", waarde: "Jan Janssens" }], opties);
    expect(await leesDocumentXml(a.buffer)).toEqual(await leesDocumentXml(b.buffer));
  });
});
