import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { moduleModelCategory } from "./categories";
import { runModelSyncCheck, parseModelsSection } from "./models";

describe("runModelSyncCheck", () => {
  let tmpDir: string;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "sync-check-model-"));
  });

  afterEach(() => {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  it("matches db files against ### Models section entries (many-to-one)", async () => {
    const dbDir = path.join(tmpDir, "modules", "foo", "db");
    const docDir = path.join(tmpDir, "modules", "foo", "docs", "model");
    fs.mkdirSync(dbDir, { recursive: true });
    fs.mkdirSync(docDir, { recursive: true });
    fs.writeFileSync(path.join(dbDir, "order.ts"), "export {}");
    fs.writeFileSync(path.join(dbDir, "orderLine.ts"), "export {}");
    fs.writeFileSync(
      path.join(docDir, "Order.md"),
      "# Order\n\n### Models\n\n- Order\n- OrderLine\n\n### Invariants\n",
    );

    const result = await runModelSyncCheck(moduleModelCategory("modules"), tmpDir);
    expect(result.errors).toHaveLength(0);
    // 2 db source files + 1 model doc
    expect(result.filesChecked).toBe(3);
  });

  it("reports missing-doc when db file has no matching ### Models entry", async () => {
    const dbDir = path.join(tmpDir, "modules", "foo", "db");
    const docDir = path.join(tmpDir, "modules", "foo", "docs", "model");
    fs.mkdirSync(dbDir, { recursive: true });
    fs.mkdirSync(docDir, { recursive: true });
    fs.writeFileSync(path.join(dbDir, "order.ts"), "export {}");
    fs.writeFileSync(path.join(dbDir, "orderLine.ts"), "export {}");
    fs.writeFileSync(
      path.join(docDir, "Order.md"),
      "# Order\n\n### Models\n\n- Order\n\n### Invariants\n",
    );

    const result = await runModelSyncCheck(moduleModelCategory("modules"), tmpDir);
    expect(result.errors).toContainEqual(
      expect.objectContaining({
        type: "missing-doc",
        category: "model",
        expectedBasename: "orderline",
      }),
    );
  });

  it("reports orphaned-doc when ### Models entry has no db file", async () => {
    const dbDir = path.join(tmpDir, "modules", "foo", "db");
    const docDir = path.join(tmpDir, "modules", "foo", "docs", "model");
    fs.mkdirSync(dbDir, { recursive: true });
    fs.mkdirSync(docDir, { recursive: true });
    fs.writeFileSync(path.join(dbDir, "order.ts"), "export {}");
    fs.writeFileSync(
      path.join(docDir, "Order.md"),
      "# Order\n\n### Models\n\n- Order\n- OrderLine\n\n### Invariants\n",
    );

    const result = await runModelSyncCheck(moduleModelCategory("modules"), tmpDir);
    expect(result.errors).toContainEqual(
      expect.objectContaining({
        type: "orphaned-doc",
        category: "model",
        expectedBasename: "orderline",
      }),
    );
  });
});

describe("parseModelsSection", () => {
  it("extracts model names from ### Models section", () => {
    const content =
      "# Order\n\n### Models\n\n- Order\n- OrderLine\n- OrderSnapshot\n\n### Invariants\n";
    expect(parseModelsSection(content)).toEqual(["Order", "OrderLine", "OrderSnapshot"]);
  });

  it("returns empty array when no ### Models section exists", () => {
    expect(parseModelsSection("# Order\n\n### Invariants\n")).toEqual([]);
  });

  it("stops at next heading", () => {
    const content = "### Models\n\n- Foo\n\n### Invariants\n\n- Bar\n";
    expect(parseModelsSection(content)).toEqual(["Foo"]);
  });
});
