import { test, describe } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import {
  parseFrontmatter,
  serializeFrontmatter,
  flipPrdStatus,
  findPrdPath,
} from "./prd-ship.mjs";

describe("parseFrontmatter", () => {
  test("parses scalar keys from a basic frontmatter block", () => {
    const text = `---
id: test-1
title: Hello
status: approved
---

body
`;
    const { frontmatter, body } = parseFrontmatter(text);
    assert.equal(frontmatter.id, "test-1");
    assert.equal(frontmatter.title, "Hello");
    assert.equal(frontmatter.status, "approved");
    assert.match(body, /body/);
  });

  test("parses YAML list values (indented '- item')", () => {
    const text = `---
id: test-2
shipping-commits:
  - abc123
  - def456
---

body
`;
    const { frontmatter } = parseFrontmatter(text);
    assert.deepEqual(frontmatter["shipping-commits"], ["abc123", "def456"]);
  });

  test("returns empty frontmatter when none present", () => {
    const { frontmatter, body } = parseFrontmatter("just body content\n");
    assert.deepEqual(frontmatter, {});
    assert.equal(body, "just body content\n");
  });
});

describe("flipPrdStatus", () => {
  const baseText = `---
id: test-prd
title: Test PRD
type: prd
status: approved
created: 2026-01-01
---

# body
`;

  test("flips approved -> shipped with today's date", () => {
    const result = flipPrdStatus(baseText, { shippedDate: "2026-05-13" });
    const { frontmatter } = parseFrontmatter(result);
    assert.equal(frontmatter.status, "shipped");
    assert.equal(frontmatter.shipped, "2026-05-13");
  });

  test("flips in-review -> shipped", () => {
    const text = baseText.replace("status: approved", "status: in-review");
    const result = flipPrdStatus(text, { shippedDate: "2026-05-13" });
    const { frontmatter } = parseFrontmatter(result);
    assert.equal(frontmatter.status, "shipped");
  });

  test("preserves body", () => {
    const result = flipPrdStatus(baseText, { shippedDate: "2026-05-13" });
    assert.match(result, /\n# body\n/);
  });

  test("preserves other frontmatter keys", () => {
    const result = flipPrdStatus(baseText, { shippedDate: "2026-05-13" });
    const { frontmatter } = parseFrontmatter(result);
    assert.equal(frontmatter.id, "test-prd");
    assert.equal(frontmatter.title, "Test PRD");
    assert.equal(frontmatter.created, "2026-01-01");
  });

  test("adds shipping-commits when supplied", () => {
    const result = flipPrdStatus(baseText, {
      shippedDate: "2026-05-13",
      commits: ["abc123", "def456"],
    });
    const { frontmatter } = parseFrontmatter(result);
    assert.deepEqual(frontmatter["shipping-commits"], ["abc123", "def456"]);
  });

  test("refuses to flip draft (must go through human review)", () => {
    const text = baseText.replace("status: approved", "status: draft");
    assert.throws(() => flipPrdStatus(text), /still draft/);
  });

  test("refuses to flip already-shipped (idempotent fail-soft)", () => {
    const text = baseText.replace("status: approved", "status: shipped");
    assert.throws(() => flipPrdStatus(text), /already marked shipped/);
  });

  test("refuses unexpected statuses", () => {
    const text = baseText.replace("status: approved", "status: cancelled");
    assert.throws(() => flipPrdStatus(text), /Unexpected PRD status/);
  });

  test("refuses files without frontmatter", () => {
    assert.throws(
      () => flipPrdStatus("no frontmatter here"),
      /no parseable frontmatter/,
    );
  });
});

describe("findPrdPath", () => {
  test("finds a PRD by its id field", () => {
    const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "prd-find-"));
    try {
      const prdsDir = path.join(tmpRoot, "prds");
      fs.mkdirSync(prdsDir, { recursive: true });
      fs.writeFileSync(
        path.join(prdsDir, "2026-05-13-something.prd.md"),
        `---
id: 2026-05-13-something
status: approved
---
body
`,
      );
      const found = findPrdPath(tmpRoot, "2026-05-13-something");
      assert.ok(found);
      assert.match(found, /something\.prd\.md$/);
    } finally {
      fs.rmSync(tmpRoot, { recursive: true, force: true });
    }
  });

  test("returns null when no PRD matches", () => {
    const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "prd-find2-"));
    try {
      fs.mkdirSync(path.join(tmpRoot, "prds"), { recursive: true });
      const found = findPrdPath(tmpRoot, "nonexistent");
      assert.equal(found, null);
    } finally {
      fs.rmSync(tmpRoot, { recursive: true, force: true });
    }
  });
});

describe("serializeFrontmatter", () => {
  test("preserves key order from the original", () => {
    const orig = `id: x
title: y
status: approved`;
    const result = serializeFrontmatter(orig, {
      id: "x",
      title: "y",
      status: "shipped",
    });
    const lines = result.split("\n");
    assert.equal(lines[0], "id: x");
    assert.equal(lines[1], "title: y");
    assert.equal(lines[2], "status: shipped");
  });

  test("appends new keys at the end", () => {
    const orig = `id: x
status: approved`;
    const result = serializeFrontmatter(orig, {
      id: "x",
      status: "shipped",
      shipped: "2026-05-13",
    });
    assert.match(result, /shipped: 2026-05-13\n?$/);
  });

  test("emits array values as YAML lists", () => {
    const result = serializeFrontmatter("", {
      "shipping-commits": ["abc", "def"],
    });
    assert.match(result, /shipping-commits:\n {2}- abc\n {2}- def/);
  });
});
