import { describe, it, expect } from "vitest";
// ── Vierde pijler: opvolging tot aan de ondertekening ────────────────────────
// Bewaakt dat de opvolging deterministisch en eerlijk is: termijnen enkel
// berekend op een gekende grondslag (compromisdatum + overeengekomen termijn),
// de vaste route met de juiste huidige fase, en uitdrukkelijke vlaggen bij
// verstreken termijnen of inconsistenties — nooit een blinde gok.
import {
  stelOpvolgingSamenAutonoom,
  bepaalDossierUrgentie,
  voegDagenToe,
  voegWekenToe,
  voegMaandenToe,
  dagenTussen,
} from "./opvolging";
import { maakVeld, type Dossier } from "./types";

function maakVerkoop(overschrijf: Partial<Dossier> = {}): Dossier {
  return {
    id: "ov-1",
    dossiertype: "verkoop-met-krediet",
    status: "ontwerp-in-opmaak",
    partijen: maakVeld(
      [
        { naam: "PEETERS", voornaam: "Jan", rol: "verkoper" },
        { naam: "MAES", voornaam: "Sofie", rol: "koper" },
      ],
      "compromis"
    ),
    goed: maakVeld({ adres: "Bosstraat 45, 3080 Tervuren", type: "woning" }, "compromis"),
    prijs: maakVeld(395000, "compromis"),
    compromisdatum: maakVeld("2026-05-12", "compromis"),
    kredietofferte: maakVeld({ bank: "KBC", bedrag: 300000, aanvaard: false, termijnWeken: 4 }, "kredietofferte"),
    ontbrekendeStukken: [],
    tegenstrijdigheden: [],
    aangemaakt: "2026-06-01T00:00:00.000Z",
    aangepast: "2026-06-01T00:00:00.000Z",
    ...overschrijf,
  } as Dossier;
}

describe("datumhulpfuncties", () => {
  it("telt dagen en weken deterministisch op, over maand- en jaargrenzen heen", () => {
    expect(voegDagenToe("2026-12-30", 3)).toBe("2027-01-02");
    expect(voegWekenToe("2026-05-12", 4)).toBe("2026-06-09");
  });

  it("telt maanden 'van datum tot datum' en klemt op de laatste dag van de doelmaand", () => {
    expect(voegMaandenToe("2026-05-12", 4)).toBe("2026-09-12");
    expect(voegMaandenToe("2026-10-31", 4)).toBe("2027-02-28"); // 31 feb bestaat niet
    expect(voegMaandenToe("2027-10-31", 4)).toBe("2028-02-29"); // schrikkeljaar
    expect(voegMaandenToe("2026-11-15", 4)).toBe("2027-03-15"); // jaargrens
  });

  it("berekent hele dagen tussen twee data (negatief voor het verleden)", () => {
    expect(dagenTussen("2026-06-01", "2026-06-09")).toBe(8);
    expect(dagenTussen("2026-06-09", "2026-06-01")).toBe(-8);
  });
});

describe("stelOpvolgingSamenAutonoom — termijnen", () => {
  it("berekent de vervaldag van de opschortende voorwaarde en de uiterste aktedatum uit het compromis", () => {
    const o = stelOpvolgingSamenAutonoom(maakVerkoop());
    const krediet = o.termijnen.find((t) => t.id === "opschortende-voorwaarde-krediet");
    const akte = o.termijnen.find((t) => t.id === "uiterste-datum-akte");
    expect(krediet?.vervaldatum).toBe("2026-06-09"); // 12 mei + 4 weken
    expect(krediet?.grondslag).toContain("4 weken");
    expect(akte?.vervaldatum).toBe("2026-09-12"); // 12 mei + 4 maanden
    expect(akte?.grondslag).toContain("vier maanden");
    // Zonder "vandaag" geen dag-tot-dag-vergelijking (pure functie).
    expect(krediet?.dagenResterend).toBeUndefined();
    expect(o.onzekerheden).toEqual([]);
  });

  it("laat een contractueel bedongen aktetermijn voorgaan en bewaakt de fiscale registratietermijn apart wanneer die eerder valt", () => {
    // 6 maanden bedongen: de aktedatum schuift op, maar de fiscale vier
    // maanden blijven een eigen vervaldag (belastingverhoging bij laattijdige
    // registratie, los van de contractuele wanprestatie).
    const zes = stelOpvolgingSamenAutonoom(maakVerkoop({ aktetermijn: maakVeld({ maanden: 6 }, "compromis") }));
    const akte6 = zes.termijnen.find((t) => t.id === "uiterste-datum-akte");
    expect(akte6?.vervaldatum).toBe("2026-11-12"); // 12 mei + 6 maanden
    expect(akte6?.grondslag).toContain("contractueel bedongen aktetermijn");
    const fiscaal = zes.termijnen.find((t) => t.id === "fiscale-registratietermijn");
    expect(fiscaal?.vervaldatum).toBe("2026-09-12"); // 12 mei + 4 maanden
    // 3 maanden bedongen (vroeger dan fiscaal): één termijn volstaat.
    const drie = stelOpvolgingSamenAutonoom(maakVerkoop({ aktetermijn: maakVeld({ maanden: 3 }, "compromis") }));
    expect(drie.termijnen.find((t) => t.id === "uiterste-datum-akte")?.vervaldatum).toBe("2026-08-12");
    expect(drie.termijnen.find((t) => t.id === "fiscale-registratietermijn")).toBeUndefined();
    // Vaste uiterste datum bedongen.
    const vast = stelOpvolgingSamenAutonoom(maakVerkoop({ aktetermijn: maakVeld({ uiterlijkeDatum: "2026-10-01" }, "compromis") }));
    const akteVast = vast.termijnen.find((t) => t.id === "uiterste-datum-akte");
    expect(akteVast?.vervaldatum).toBe("2026-10-01");
    expect(akteVast?.grondslag).toContain("contractueel bedongen uiterste datum");
    expect(vast.termijnen.find((t) => t.id === "fiscale-registratietermijn")?.vervaldatum).toBe("2026-09-12");
  });

  it("laat de krediettermijn vallen zodra de kredietofferte aanvaard is (voorwaarde vervuld)", () => {
    const o = stelOpvolgingSamenAutonoom(
      maakVerkoop({ kredietofferte: maakVeld({ bank: "KBC", bedrag: 300000, aanvaard: true, termijnWeken: 4 }, "kredietofferte") })
    );
    expect(o.termijnen.find((t) => t.id === "opschortende-voorwaarde-krediet")).toBeUndefined();
    expect(o.termijnen.find((t) => t.id === "uiterste-datum-akte")).toBeDefined();
  });

  it("meldt eerlijk dat de krediettermijn niet bewaakt kan worden zonder overeengekomen termijn", () => {
    const o = stelOpvolgingSamenAutonoom(maakVerkoop({ kredietofferte: undefined }));
    expect(o.termijnen.find((t) => t.id === "opschortende-voorwaarde-krediet")).toBeUndefined();
    expect(o.onzekerheden.some((z) => /opschortende voorwaarde.*niet gekend/i.test(z))).toBe(true);
  });

  it("berekent geen termijnen zonder compromisdatum en meldt dat als onzekerheid", () => {
    const o = stelOpvolgingSamenAutonoom(maakVerkoop({ compromisdatum: undefined }));
    expect(o.termijnen).toEqual([]);
    expect(o.onzekerheden.some((z) => /geen compromisdatum/i.test(z))).toBe(true);
  });

  it("crasht niet op een niet-ISO compromisdatum (bv. vrije tekst uit een import) maar meldt ze als onzekerheid", () => {
    // Regressie: voorheen liep dit door tot new Date(NaN).toISOString() →
    // RangeError, waardoor de HELE werkdossier-samenstelling omviel.
    const o = stelOpvolgingSamenAutonoom(maakVerkoop({ compromisdatum: maakVeld("12 mei 2026", "compromis") }), "2026-06-01");
    expect(o.termijnen).toEqual([]);
    expect(o.onzekerheden.some((z) => /JJJJ-MM-DD/.test(z) && z.includes("12 mei 2026"))).toBe(true);
  });

  it("berekent met 'vandaag' de resterende dagen en waarschuwt bij nakende of verstreken termijnen", () => {
    // Ruim op tijd: resterende dagen, geen waarschuwing.
    const ruim = stelOpvolgingSamenAutonoom(maakVerkoop(), "2026-05-15");
    const krediet = ruim.termijnen.find((t) => t.id === "opschortende-voorwaarde-krediet");
    expect(krediet?.dagenResterend).toBe(25);
    expect(krediet?.verstreken).toBe(false);
    expect(ruim.onzekerheden).toEqual([]);

    // Binnen 14 dagen: uitdrukkelijke actie-vlag.
    const nakend = stelOpvolgingSamenAutonoom(maakVerkoop(), "2026-06-01");
    expect(nakend.onzekerheden.some((z) => /vervalt binnen 8 dag/.test(z))).toBe(true);

    // Verstreken: alarm.
    const verstreken = stelOpvolgingSamenAutonoom(maakVerkoop(), "2026-06-15");
    const kredietV = verstreken.termijnen.find((t) => t.id === "opschortende-voorwaarde-krediet");
    expect(kredietV?.verstreken).toBe(true);
    expect(verstreken.onzekerheden.some((z) => /verstreken op/.test(z))).toBe(true);
  });

  it("laat na de akte (status 'verleden') geen termijnen meer lopen", () => {
    const o = stelOpvolgingSamenAutonoom(maakVerkoop({ status: "verleden" }), "2026-12-01");
    expect(o.termijnen).toEqual([]);
    expect(o.onzekerheden.every((z) => !/verstreken/.test(z))).toBe(true);
  });
});

describe("stelOpvolgingSamenAutonoom — route en acties", () => {
  it("markeert de vaste route met afgerond/huidig/te-doen volgens de dossierstatus", () => {
    const o = stelOpvolgingSamenAutonoom(maakVerkoop({ status: "ontwerp-in-opmaak" }));
    expect(o.stappen.map((s) => s.toestand)).toEqual(["afgerond", "afgerond", "huidig", "te-doen", "te-doen", "te-doen"]);
    expect(o.huidigeStap?.status).toBe("ontwerp-in-opmaak");
  });

  it("verwijst in de ontwerpfase naar de pijlers en de passende mailcategorieën", () => {
    const o = stelOpvolgingSamenAutonoom(maakVerkoop());
    expect(o.huidigeStap?.acties.some((a) => /ontwerp autonoom samen/i.test(a))).toBe(true);
    expect(o.huidigeStap?.mailCategorieen).toContain("akteontwerp");
  });

  it("benoemt de ontbrekende stukken concreet in de opzoekingsfase", () => {
    const o = stelOpvolgingSamenAutonoom(maakVerkoop({ status: "opzoekingen-lopend" }));
    expect(o.ontbrekendeStukken.map((s) => s.id)).toContain("stedenbouw");
    expect(o.huidigeStap?.acties.some((a) => /Stedenbouwkundige inlichtingen/.test(a))).toBe(true);
  });

  it("voegt bij een appartement zonder syndicusantwoord de syndicus-actie toe", () => {
    const o = stelOpvolgingSamenAutonoom(
      maakVerkoop({
        status: "compromis-ontvangen",
        goed: maakVeld({ adres: "Bosstraat 45, 3080 Tervuren", type: "appartement" }, "compromis"),
      })
    );
    expect(o.huidigeStap?.acties.some((a) => /syndicus/i.test(a))).toBe(true);
  });

  it("bewaakt vóór de akte de opschortende voorwaarde en de betaling van saldo en kosten", () => {
    const o = stelOpvolgingSamenAutonoom(maakVerkoop({ status: "klaar-voor-akte" }));
    expect(o.huidigeStap?.acties.some((a) => /opschortende voorwaarde.*vervuld/i.test(a))).toBe(true);
    expect(o.huidigeStap?.acties.some((a) => /saldo van de prijs en de aktekosten/i.test(a))).toBe(true);
    expect(o.huidigeStap?.mailCategorieen).toContain("betaling");
  });

  it("volgt na de akte het royement op wanneer een hypothecaire last geroyeerd moet worden", () => {
    const o = stelOpvolgingSamenAutonoom(
      maakVerkoop({
        status: "verleden",
        hypothecaireLasten: maakVeld([{ schuldeiser: "KBC Bank", bedrag: 180000, moetGeroyeerdWorden: true }], "hypotheekattest"),
      })
    );
    expect(o.huidigeStap?.acties.some((a) => /royement/i.test(a))).toBe(true);
  });

  it("vlagt een dossier dat klaar-voor-akte heet maar waar nog verplichte stukken ontbreken", () => {
    const o = stelOpvolgingSamenAutonoom(maakVerkoop({ status: "klaar-voor-akte" }));
    expect(o.onzekerheden.some((z) => /ontbreken nog verplichte stukken/i.test(z))).toBe(true);
  });

  it("blijft eerlijk over niet-gemodelleerde dossiertypes (geen valse route)", () => {
    const o = stelOpvolgingSamenAutonoom(maakVerkoop({ dossiertype: "schenking" }));
    expect(o.stappen).toEqual([]);
    expect(o.termijnen).toEqual([]);
    expect(o.onzekerheden.some((z) => /enkel gemodelleerd voor verkoopdossiers/i.test(z))).toBe(true);
  });

  it("is deterministisch: tweemaal dezelfde invoer levert exact dezelfde opvolging", () => {
    const a = stelOpvolgingSamenAutonoom(maakVerkoop(), "2026-06-01");
    const b = stelOpvolgingSamenAutonoom(maakVerkoop(), "2026-06-01");
    expect(b).toEqual(a);
  });
});

describe("bepaalDossierUrgentie — samenvatting voor het dossieroverzicht", () => {
  // Vaste feiten uit maakVerkoop: compromis 2026-05-12, krediettermijn 4 weken
  // (vervaldag 2026-06-09), uiterste aktedatum +4 maanden (2026-09-12).

  it("markeert een nakende termijn als 'nabij' met de dringendste termijn in het label", () => {
    const u = bepaalDossierUrgentie(maakVerkoop(), "2026-06-01");
    expect(u.niveau).toBe("nabij");
    expect(u.label).toBe("Krediettermijn binnen 8 d");
    expect(u.termijn?.id).toBe("opschortende-voorwaarde-krediet");
    expect(u.sorteersleutel).toBe(8);
  });

  it("markeert een verstreken termijn als 'verstreken' (negatieve sorteersleutel: meest verstreken eerst)", () => {
    const u = bepaalDossierUrgentie(maakVerkoop(), "2026-06-15");
    expect(u.niveau).toBe("verstreken");
    expect(u.label).toBe("Krediettermijn: termijn verstreken");
    expect(u.sorteersleutel).toBe(-6);
  });

  it("valt terug op de aktetermijn zodra het krediet aanvaard is ('ok' zolang ze niet nadert)", () => {
    const dossier = maakVerkoop({
      kredietofferte: maakVeld({ bank: "KBC", bedrag: 300000, aanvaard: true }, "kredietofferte"),
    });
    const u = bepaalDossierUrgentie(dossier, "2026-06-01");
    expect(u.niveau).toBe("ok");
    expect(u.label).toBeUndefined();
    expect(u.termijn?.id).toBe("uiterste-datum-akte");
  });

  it("geeft 'geen-termijnen' zonder compromisdatum, na de akte en voor niet-gemodelleerde types", () => {
    for (const dossier of [
      maakVerkoop({ compromisdatum: undefined }),
      maakVerkoop({ status: "verleden" }),
      maakVerkoop({ dossiertype: "schenking" }),
    ]) {
      const u = bepaalDossierUrgentie(dossier, "2026-06-01");
      expect(u.niveau).toBe("geen-termijnen");
      expect(u.label).toBeUndefined();
      expect(u.sorteersleutel).toBe(Number.MAX_SAFE_INTEGER);
    }
  });

  it("de sorteersleutels ordenen verstreken vóór nabij vóór ok vóór geen-termijnen", () => {
    const sleutels = [
      bepaalDossierUrgentie(maakVerkoop(), "2026-06-15"), // verstreken
      bepaalDossierUrgentie(maakVerkoop(), "2026-06-01"), // nabij
      bepaalDossierUrgentie(maakVerkoop({ kredietofferte: maakVeld({ bank: "KBC", bedrag: 300000, aanvaard: true }, "kredietofferte") }), "2026-06-01"), // ok
      bepaalDossierUrgentie(maakVerkoop({ dossiertype: "schenking" }), "2026-06-01"), // geen
    ].map((u) => u.sorteersleutel);
    expect([...sleutels].sort((a, b) => a - b)).toEqual(sleutels);
  });
});
