import { describe, it, expect } from "vitest";
import { openApiNaarToolOperaties } from "./openapi-naar-tools";
import { bouwOpenApiSpec } from "@/lib/openapi";

describe("openApiNaarToolOperaties", () => {
  const operaties = openApiNaarToolOperaties(bouwOpenApiSpec());

  it("bevat een tool-operatie voor berekenAktekosten (bestaand pad, regressie)", () => {
    const op = operaties.find((o) => o.tool.name === "berekenAktekosten");
    expect(op).toBeDefined();
    expect(op?.padSjabloon).toBe("/api/aktekosten");
    expect(op?.methode).toBe("GET");
  });

  it("bevat een tool-operatie voor berekenBelasting met het juiste padsjabloon", () => {
    const op = operaties.find((o) => o.tool.name === "berekenBelasting");
    expect(op).toBeDefined();
    expect(op?.padSjabloon).toBe("/api/belasting");
    expect(op?.methode).toBe("GET");
  });

  it("berekenBelasting vereist soort, relatie en waarde als queryparameters", () => {
    const op = operaties.find((o) => o.tool.name === "berekenBelasting");
    expect(op?.queryParameters).toEqual(
      expect.arrayContaining(["soort", "gewest", "goedType", "relatie", "waarde", "andereGoederen", "isPartner"])
    );
    expect(op?.tool.inputSchema.required).toEqual(
      expect.arrayContaining(["soort", "relatie", "waarde"])
    );
  });

  it("berekenBelasting heeft de juiste enum-waarden voor soort en relatie", () => {
    const op = operaties.find((o) => o.tool.name === "berekenBelasting");
    expect(op?.tool.inputSchema.properties.soort.enum).toEqual(["schenking", "erfenis"]);
    expect(op?.tool.inputSchema.properties.relatie.enum).toEqual([
      "rechte_lijn_partner",
      "broers_zussen",
      "ooms_nichten",
      "anderen",
    ]);
  });

  it("een GET-tool is read-only, idempotent en niet-destructief", () => {
    const op = operaties.find((o) => o.tool.name === "berekenAktekosten");
    expect(op?.methode).toBe("GET");
    expect(op?.tool.annotations).toMatchObject({
      readOnlyHint: true,
      idempotentHint: true,
      destructiveHint: false,
      openWorldHint: false,
    });
  });

  it("een POST-voorstel-tool schrijft (niet read-only/idempotent) maar is niet-destructief", () => {
    const op = operaties.find((o) => o.tool.name === "voegModelonderdeelVoorstelToe");
    expect(op).toBeDefined();
    expect(op?.methode).toBe("POST");
    // Twee-regimes-principe: een voorstel muteert geen gedeelde data — nooit destructief.
    expect(op?.tool.annotations).toMatchObject({
      readOnlyHint: false,
      idempotentHint: false,
      destructiveHint: false,
      openWorldHint: false,
    });
  });

  it.each([
    "bepaalAkteType",
    "bepaalModelmailCategorieen",
    "genereerOntwerpUitKenmerken",
    "genereerWerkdossierAlsWord",
  ])("de POST-tool %s leidt af/genereert en is dus read-only (geen mutatie van gedeelde data)", (naam) => {
    const op = operaties.find((o) => o.tool.name === naam);
    expect(op, `tool ${naam} bestaat`).toBeDefined();
    expect(op?.methode).toBe("POST");
    // Read-only ondanks POST → MCP-clients (Claude.ai) zetten ze niet in de
    // 'write/delete'-categorie die door een beheerdersbeleid geblokkeerd kan zijn.
    expect(op?.tool.annotations).toMatchObject({
      readOnlyHint: true,
      idempotentHint: true,
      destructiveHint: false,
      openWorldHint: false,
    });
  });

  it("enkel de twee wijzigingsvoorstel-tools zijn write (readOnlyHint === false)", () => {
    const writeTools = operaties.filter((o) => o.tool.annotations?.readOnlyHint === false).map((o) => o.tool.name).sort();
    expect(writeTools).toEqual(["voegModeldocumentVoorstelToe", "voegModelonderdeelVoorstelToe"]);
  });

  it("haalModeldocument biedt de invul-/generatieparameters aan (facultatief + invulParameters)", () => {
    const op = operaties.find((o) => o.tool.name === "haalModeldocument");
    expect(op).toBeDefined();
    expect(op?.queryParameters).toEqual(expect.arrayContaining(["facultatief"]));
    // De <parameternaam>-plaatshouder wordt een vrij invulobject.
    expect(op?.vrijeQueryEigenschap).toBe("invulParameters");
    expect(op?.tool.inputSchema.properties).toHaveProperty("facultatief");
    expect(op?.tool.inputSchema.properties).toHaveProperty("invulParameters");
  });

  it("elk inputSchema is gesloten (additionalProperties === false) zodat typfouten opvallen", () => {
    for (const op of operaties) {
      expect(op.tool.inputSchema.additionalProperties, op.tool.name).toBe(false);
    }
  });

  it("genereert een outputSchema uit het 200-responsschema (haalModeldocumentAlsWord)", () => {
    const op = operaties.find((o) => o.tool.name === "haalModeldocumentAlsWord");
    expect(op).toBeDefined();
    // De Word-tool belooft een gestructureerd antwoord (bestandsnaam, mimeType,
    // downloadUrl, …) → dat schema hoort mee als outputSchema.
    expect(op?.tool.outputSchema?.type).toBe("object");
    expect(op?.tool.outputSchema?.properties).toHaveProperty("downloadUrl");
    expect(op?.tool.outputSchema?.properties).toHaveProperty("bestandsnaam");
    // Outputschema blijft open: de endpoint mag extra velden meegeven zonder
    // een strikte client te laten struikelen.
    expect(op?.tool.outputSchema?.additionalProperties).toBeUndefined();
  });

  it("laat outputSchema weg wanneer de 200-respons enkel een beschrijving heeft", () => {
    // lijstModeldocumenten documenteert zijn 200 als vrije tekst, geen schema.
    const op = operaties.find((o) => o.tool.name === "lijstModeldocumenten");
    expect(op).toBeDefined();
    expect(op?.tool.outputSchema).toBeUndefined();
  });

  it("neemt geneste object-eigenschappen over in het inputSchema (kenmerken-blok)", () => {
    // genereerOntwerpUitKenmerken heeft een 'kenmerken'-object met deelvelden;
    // die deelvelden moeten in het toolschema staan, niet als kaal 'object'.
    const op = operaties.find((o) => o.tool.name === "genereerOntwerpUitKenmerken");
    expect(op).toBeDefined();
    const kenmerken = op?.tool.inputSchema.properties.kenmerken;
    expect(kenmerken?.type).toBe("object");
    expect(kenmerken?.properties, "kenmerken heeft deelvelden").toBeDefined();
    expect(Object.keys(kenmerken?.properties ?? {}).length).toBeGreaterThan(0);
  });
});
