import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { checkConformance, inventoryBundle } from "../src/conformance.js";
import { extractGraph } from "../src/graph.js";

const roots: string[] = [];

function freshBundle(version = "0.2"): string {
  const bundle = mkdtempSync(join(tmpdir(), "kb-v02-"));
  roots.push(bundle);
  writeFileSync(
    join(bundle, "index.md"),
    `---\nokf_version: "${version}"\n---\n\n# Test Bundle\n`,
    "utf8",
  );
  return bundle;
}

afterEach(() => {
  for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
});

describe("OKF v0.2 producer profile", () => {
  it("ships a warning-free v0.2 example bundle", () => {
    const report = checkConformance(resolve("skills/kb/example-bundle"));
    expect(report.okf_version).toBe("0.2");
    expect(report.errors).toEqual([]);
    expect(report.warnings).toEqual([]);
  });

  it("accepts structured provenance, keyed footnotes, actors, lifecycle, and attested computation", () => {
    const bundle = freshBundle();
    mkdirSync(join(bundle, "references"));
    writeFileSync(
      join(bundle, "references", "policy.md"),
      `---
type: Reference
title: Policy
resource: https://example.com/policy
generated: { by: process:test-fixture, at: 2026-07-31T12:00:00Z }
---

# Policy
`,
      "utf8",
    );
    writeFileSync(
      join(bundle, "metric.md"),
      `---
type: Metric
title: Revenue
generated: { by: janet/0.1.0-beta.4, at: 2026-07-31T12:00:00Z }
verified: { by: human:reviewer, at: 2026-07-31T13:00:00Z }
status: stable
sources:
  - id: policy
    resource: /references/policy.md
    title: Policy
---

Revenue follows the policy.[^policy]

[^policy]: Policy
`,
      "utf8",
    );
    writeFileSync(
      join(bundle, "compute.md"),
      `---
type: Attested Computation
runtime: bigquery
parameters:
  - { name: year, type: integer, required: true }
generated: { by: janet/0.1.0-beta.4, at: 2026-07-31T12:00:00Z }
---

# Computation

    SELECT @year
`,
      "utf8",
    );

    const report = checkConformance(bundle);
    expect(report.okf_version).toBe("0.2");
    expect(report.errors).toEqual([]);
    expect(report.warnings).toEqual([]);
  });

  it("keeps malformed optional families as warnings rather than conformance errors", () => {
    const bundle = freshBundle();
    writeFileSync(
      join(bundle, "bad.md"),
      `---
type: Attested Computation
generated: { by: janet/0.1.0-beta.4, at: yesterday }
verified: []
status: active
stale_after: someday
sources: [not-a-mapping]
---

Unsupported claim.[^missing]
`,
      "utf8",
    );

    const report = checkConformance(bundle);
    expect(report.errors).toEqual([]);
    expect(report.warnings).toEqual(expect.arrayContaining([
      "bad.md: sources[0] requires non-empty 'resource'",
      "bad.md: footnote 'missing' has no matching sources[].id",
      "bad.md: generated.at must be an ISO 8601 datetime",
      "bad.md: verified must be a mapping or non-empty list of mappings",
      "bad.md: status must be draft, stable, or deprecated",
      "bad.md: stale_after must be an ISO 8601 YYYY-MM-DD date",
      "bad.md: Attested Computation requires non-empty 'runtime'",
      "bad.md: Attested Computation must use exactly one computation path or body section",
    ]));
  });

  it("enforces reserved-file structure as hard conformance", () => {
    const bundle = freshBundle();
    writeFileSync(
      join(bundle, "index.md"),
      `---\nokf_version: "0.2"\ndocumented_revision: abc\n---\n# Invalid root\n`,
      "utf8",
    );
    writeFileSync(join(bundle, "log.md"), "# Log\n\n## someday\n\n* Update\n", "utf8");

    expect(checkConformance(bundle).errors).toEqual([
      "index.md: reserved file must not carry frontmatter",
      "log.md: log date heading not ISO 8601: 'someday'",
    ]);
  });

  it("requires okf_version to be a quoted major.minor string", () => {
    const bundle = freshBundle();
    writeFileSync(
      join(bundle, "index.md"),
      "---\nokf_version: 0.2\n---\n\n# Invalid version type\n",
      "utf8",
    );

    expect(checkConformance(bundle).errors).toEqual([
      "index.md: okf_version must be a string in <major>.<minor> form",
    ]);
  });
});

describe("OKF v0.1 compatibility", () => {
  it("consumes declared v0.1 timestamp and citation conventions without v0.2 warnings", () => {
    const bundle = freshBundle("0.1");
    writeFileSync(
      join(bundle, "legacy.md"),
      `---\ntype: Concept\ntimestamp: 2026-01-01T00:00:00Z\n---\n\n# Legacy\n\n# Citations\n\n1. Example\n`,
      "utf8",
    );

    const report = checkConformance(bundle);
    expect(report.okf_version).toBe("0.1");
    expect(report.errors).toEqual([]);
    expect(report.warnings).toEqual([]);
  });
});

describe("migration inventory", () => {
  it("classifies only parsed frontmatter as active metadata", () => {
    const bundle = freshBundle("0.1");
    writeFileSync(
      join(bundle, "legacy.md"),
      `---
type: Concept
timestamp: 2026-01-01T00:00:00Z
status: deprecated
---

# Legacy

\`\`\`yaml
generated: { by: example/model, at: 2026-01-01T00:00:00Z }
sources: [{ resource: https://example.com }]
resource: https://example.com/not-active
status: superseded
\`\`\`

# Citations

1. [Linked](https://example.com/linked)
2. Site — <https://example.com/site>
3. Private record
[^extra]: [Extra](https://example.com/extra)
`,
      "utf8",
    );

    const inventory = inventoryBundle(bundle);
    expect(inventory.concepts).toBe(1);
    expect(inventory.parsed_concepts).toBe(1);
    expect(inventory.frontmatter_errors).toEqual([]);
    expect(inventory.observations["frontmatter.timestamp"]?.count).toBe(1);
    expect(inventory.observations["frontmatter.status=deprecated"]?.count).toBe(1);
    expect(inventory.observations["body.# Citations"]?.count).toBe(1);
    expect(inventory.legacy_citations).toEqual({
      sections: 1,
      numbered_items: 3,
      footnote_definitions: 1,
      source_records: 4,
      markdown_link_items: 1,
      angle_url_items: 1,
      scope_descriptor_items: 1,
      footnote_files: ["legacy.md"],
    });
    expect(inventory.structured_sources).toEqual({
      concepts: 0,
      entries: 0,
      entries_with_resource: 0,
    });
    expect(inventory.observations["frontmatter.generated"]).toBeUndefined();
    expect(inventory.observations["frontmatter.sources"]).toBeUndefined();
    expect(inventory.observations["frontmatter.resource"]).toBeUndefined();
    expect(inventory.observations["frontmatter.status=superseded"]).toBeUndefined();
  });

  it("locks the repository migration baseline against prose false positives", () => {
    const inventory = inventoryBundle(resolve("knowledge"));
    expect(inventory.concepts).toBe(52);
    expect(inventory.parsed_concepts).toBe(52);
    expect(inventory.frontmatter_errors).toEqual([]);
    expect(inventory.observations["frontmatter.timestamp"]?.count).toBe(52);
    expect(inventory.observations["frontmatter.resource"]?.count).toBe(15);
    expect(inventory.observations["frontmatter.status=deprecated"]?.count).toBe(2);
    expect(inventory.observations["frontmatter.supersedes"]?.count).toBe(2);
    expect(inventory.observations["frontmatter.superseded_by"]?.count).toBe(2);
    expect(inventory.observations["body.# Citations"]?.count).toBe(52);
    expect(inventory.legacy_citations).toEqual({
      sections: 52,
      numbered_items: 71,
      footnote_definitions: 1,
      source_records: 72,
      markdown_link_items: 45,
      angle_url_items: 20,
      scope_descriptor_items: 6,
      footnote_files: ["spec/log_files.md"],
    });
    expect(inventory.structured_sources).toEqual({
      concepts: 0,
      entries: 0,
      entries_with_resource: 0,
    });
    expect(inventory.observations["frontmatter.generated"]).toBeUndefined();
    expect(inventory.observations["frontmatter.sources"]).toBeUndefined();
    expect(inventory.observations["frontmatter.status=superseded"]).toBeUndefined();
    expect(inventory.observations["frontmatter.conflicts_with"]).toBeUndefined();
  });

  it("counts structured source entries for postflight preservation checks", () => {
    const bundle = freshBundle();
    writeFileSync(
      join(bundle, "current.md"),
      `---
type: Concept
sources:
  - resource: https://example.com/one
  - resource: Private evidence scope
---

# Current
`,
      "utf8",
    );

    const inventory = inventoryBundle(bundle);
    expect(inventory.structured_sources).toEqual({
      concepts: 1,
      entries: 2,
      entries_with_resource: 2,
    });
    expect(inventory.legacy_citations.source_records).toBe(0);
  });
});

describe("OKF v0.2 graph consumption", () => {
  it("derives trust, freshness, last-change, and provenance edges from v0.2 fields", () => {
    const bundle = freshBundle();
    mkdirSync(join(bundle, "references"));
    writeFileSync(
      join(bundle, "references", "source.md"),
      "---\ntype: Reference\n---\n\n# Source\n",
      "utf8",
    );
    writeFileSync(
      join(bundle, "item.md"),
      `---
type: Concept
generated: { by: janet/0.1.0-beta.4, at: 2026-07-31T12:00:00Z }
verified: { by: human:reviewer, at: 2026-07-31T13:00:00Z }
stale_after: 2020-01-01
sources:
  - id: source
    resource: /references/source.md
---

# Item
`,
      "utf8",
    );

    const graph = extractGraph(bundle);
    const item = graph.nodes.find((node) => node.id === "item")!;
    expect(item.status).toBe("stable");
    expect(item.trust_tier).toBe("human-reviewed");
    expect(item.last_changed).toBe("2026-07-31T12:00:00Z");
    expect(item.is_stale).toBe(true);
    expect(item.links).toContain("references/source");
    expect(graph.edges).toContainEqual({ source: "item", target: "references/source" });
  });
});
