import { test, describe } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { validateTrace, parseTrace, parseFrontmatter } from "./schema.mjs";

function validRaw(overrides = {}) {
  return {
    package: "example-lib",
    version: "^1.0.0",
    tier: "feature",
    decision: "approved",
    date: "2026-05-14",
    deciders: ["alice"],
    adr: null,
    lastRevalidated: null,
    "filter-results": {
      license: "MIT",
      types: "native",
      maintenance: "active",
      "boundary-fit": "pass",
      "shadow-check": "pass",
      "eu-residency": "ok",
      "cve-scan": "clean",
      "named-consumer": "pass",
      socketRisk: "clean",
    },
    "verification-commands": ["pnpm audit --audit-level=moderate"],
    ...overrides,
  };
}

function writeTempTrace(frontmatter, body = "") {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "trace-"));
  const file = path.join(dir, "trace.md");
  fs.writeFileSync(file, `---\n${frontmatter}\n---\n\n${body}`);
  return file;
}

const VALID_FM = `package: example-lib
version: "^1.0.0"
tier: feature
decision: approved
date: 2026-05-14
deciders: [alice, bob]
adr: null
lastRevalidated: null
filter-results:
  license: MIT
  types: native
  maintenance: active
  boundary-fit: pass
  shadow-check: pass
  eu-residency: ok
  cve-scan: clean
  named-consumer: pass
  socketRisk: clean
verification-commands:
  - pnpm audit --audit-level=moderate`;

describe("validateTrace > valid cases", () => {
  test("valid trace round-trips without error", () => {
    const result = validateTrace(validRaw());
    assert.equal(result.package, "example-lib");
    assert.equal(result["filter-results"].license, "MIT");
    assert.deepEqual(result["verification-commands"], [
      "pnpm audit --audit-level=moderate",
    ]);
  });

  test("accepted-cves absent is valid", () => {
    const result = validateTrace(validRaw());
    assert.equal(result["accepted-cves"], undefined);
  });

  test("accepted-cves present is valid", () => {
    const result = validateTrace(
      validRaw({ "accepted-cves": ["CVE-2024-0001"] }),
    );
    assert.deepEqual(result["accepted-cves"], ["CVE-2024-0001"]);
  });

  test("null adr is valid", () => {
    assert.equal(validateTrace(validRaw({ adr: null })).adr, null);
  });

  test("string adr is valid", () => {
    assert.equal(validateTrace(validRaw({ adr: "adr-022" })).adr, "adr-022");
  });
});

describe("validateTrace > rejection cases", () => {
  test("missing required field throws", () => {
    const raw = validRaw();
    delete raw.package;
    assert.throws(() => validateTrace(raw), /invalid_type|Required/i);
  });

  test("invalid tier enum throws", () => {
    assert.throws(
      () => validateTrace(validRaw({ tier: "invalid" })),
      /invalid_enum_value|Invalid enum value/i,
    );
  });

  test("invalid maintenance enum throws", () => {
    const raw = validRaw({
      "filter-results": {
        ...validRaw()["filter-results"],
        maintenance: "stale",
      },
    });
    assert.throws(
      () => validateTrace(raw),
      /invalid_enum_value|Invalid enum value/i,
    );
  });

  test("unknown key in filter-results rejected by strict schema", () => {
    const raw = validRaw({
      "filter-results": {
        ...validRaw()["filter-results"],
        "unknown-filter": "x",
      },
    });
    assert.throws(
      () => validateTrace(raw),
      /unrecognized_keys|Unrecognized key/i,
    );
  });

  test("missing socketRisk in filter-results fails validation", () => {
    const raw = validRaw();
    delete raw["filter-results"].socketRisk;
    assert.throws(() => validateTrace(raw), /invalid_type|Required/i);
  });
});

describe("validateTrace > socketRisk", () => {
  test('socketRisk "clean" round-trips', () => {
    const result = validateTrace(validRaw());
    assert.equal(result["filter-results"].socketRisk, "clean");
  });

  test('socketRisk "flagged" round-trips', () => {
    const raw = validRaw({
      "filter-results": {
        ...validRaw()["filter-results"],
        socketRisk: "flagged",
      },
    });
    assert.equal(validateTrace(raw)["filter-results"].socketRisk, "flagged");
  });

  test("socketRisk arbitrary string round-trips", () => {
    const raw = validRaw({
      "filter-results": {
        ...validRaw()["filter-results"],
        socketRisk: "obfuscated-code",
      },
    });
    assert.equal(
      validateTrace(raw)["filter-results"].socketRisk,
      "obfuscated-code",
    );
  });
});

describe("validateTrace > lastRevalidated", () => {
  test("lastRevalidated null is valid", () => {
    assert.equal(
      validateTrace(validRaw({ lastRevalidated: null })).lastRevalidated,
      null,
    );
  });

  test("lastRevalidated ISO date string is valid", () => {
    assert.equal(
      validateTrace(validRaw({ lastRevalidated: "2026-05-14" }))
        .lastRevalidated,
      "2026-05-14",
    );
  });
});

describe("parseFrontmatter", () => {
  test("parses scalar, inline array, nested object, block array", () => {
    const text = `---\n${VALID_FM}\n---\n\n## Filter: license`;
    const raw = parseFrontmatter(text);
    assert.equal(raw.package, "example-lib");
    assert.equal(raw.version, "^1.0.0");
    assert.deepEqual(raw.deciders, ["alice", "bob"]);
    assert.equal(raw.adr, null);
    assert.equal(raw["filter-results"].license, "MIT");
    assert.deepEqual(raw["verification-commands"], [
      "pnpm audit --audit-level=moderate",
    ]);
  });

  test("throws when no frontmatter delimiters found", () => {
    assert.throws(
      () => parseFrontmatter("# No frontmatter"),
      /No YAML frontmatter/,
    );
  });
});

describe("parseTrace", () => {
  test("reads and validates a valid trace file", () => {
    const file = writeTempTrace(VALID_FM, "## Filter: license\n\nok");
    assert.equal(parseTrace(file).package, "example-lib");
  });

  test("throws on missing required field in file", () => {
    const fm = VALID_FM.replace(/^package: example-lib\n/m, "");
    const file = writeTempTrace(fm, "");
    assert.throws(() => parseTrace(file), /invalid_type|Required/i);
  });
});
