import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";

// Resolve the package root (scripts/editions) so the subprocess `src/cli.ts` path +
// the `tsx` loader resolve regardless of the runner's cwd.
const editionsDir = fileURLToPath(new URL("..", import.meta.url));

function run(args: string[]): { status: number | null; stdout: string; stderr: string } {
  const res = spawnSync("node", ["--import", "tsx", "src/cli.ts", ...args], {
    encoding: "utf8",
    cwd: editionsDir,
  });
  return { status: res.status, stdout: res.stdout, stderr: res.stderr };
}

const tmp = () => mkdtempSync(join(tmpdir(), "editions-cli-"));

const EXTRACT = {
  editionId: "05",
  title: "Da pipelinen brøt sammen",
  lockedAt: "2026-07-20",
  anecdotes: ["Jeg brukte tre uker på å migrere pipeline-en, og halvparten av tiden gikk til testdata."],
  arguments: ["Modellen var ikke problemet — det var dataene ingen hadde ryddet på fem år."],
  hooks: ["De fleste AI-prosjekter dør ikke av dårlig modell. De dør av dårlig data."],
};

describe("editions CLI — distil-append / distil-check round-trip (N11 AC)", () => {
  test("append -> check round-trips through a real file and flags the reuse", () => {
    const dir = tmp();
    try {
      const distillate = join(dir, "linkedin", "series-distillate.json");
      const extract = join(dir, "extract.json");
      const skeleton = join(dir, "skeleton.json");
      writeFileSync(extract, JSON.stringify(EXTRACT), "utf8");

      // 1. lock writes the distillate (file did not exist)
      const appended = run(["distil-append", "--distillate", distillate, "--series", "seres", "--extract", extract]);
      assert.equal(appended.status, 0, appended.stderr);
      assert.ok(existsSync(distillate), "distil-append must create the distillate file");
      const onDisk = JSON.parse(readFileSync(distillate, "utf8"));
      assert.equal(onDisk.series, "seres");
      assert.equal(onDisk.editions.length, 1);
      assert.equal(onDisk.editions[0].editionId, "05");

      // 2. a later skeleton reusing the same hook is flagged, with attribution
      writeFileSync(skeleton, JSON.stringify({ hooks: [EXTRACT.hooks[0]] }), "utf8");
      const checked = run(["distil-check", "--distillate", distillate, "--skeleton", skeleton, "--json"]);
      assert.equal(checked.status, 0, checked.stderr);
      const report = JSON.parse(checked.stdout);
      assert.equal(report.verdict, "REUSE");
      assert.equal(report.hits[0].editionId, "05");
      assert.equal(report.hits[0].kind, "hook");

      // 3. fresh material on the same distillate is CLEAR
      writeFileSync(
        skeleton,
        JSON.stringify({ hooks: ["Innkjøpsavdelingen krevde en garanti vi ikke kunne gi uten revisjon."] }),
        "utf8",
      );
      const clear = run(["distil-check", "--distillate", distillate, "--skeleton", skeleton, "--json"]);
      assert.equal(clear.status, 0, clear.stderr);
      assert.equal(JSON.parse(clear.stdout).verdict, "CLEAR");
    } finally {
      rmSync(dir, { recursive: true, force: true });
    }
  });

  test("re-appending the same edition replaces it — a pivot re-lock does not duplicate", () => {
    const dir = tmp();
    try {
      const distillate = join(dir, "series-distillate.json");
      const extract = join(dir, "extract.json");
      writeFileSync(extract, JSON.stringify(EXTRACT), "utf8");
      run(["distil-append", "--distillate", distillate, "--series", "seres", "--extract", extract]);

      writeFileSync(extract, JSON.stringify({ ...EXTRACT, title: "Revidert" }), "utf8");
      const again = run(["distil-append", "--distillate", distillate, "--series", "seres", "--extract", extract]);
      assert.equal(again.status, 0, again.stderr);

      const onDisk = JSON.parse(readFileSync(distillate, "utf8"));
      assert.equal(onDisk.editions.length, 1);
      assert.equal(onDisk.editions[0].title, "Revidert");
    } finally {
      rmSync(dir, { recursive: true, force: true });
    }
  });

  test("distil-check against a distillate that does not exist yet is CLEAR, not an error", () => {
    const dir = tmp();
    try {
      const skeleton = join(dir, "skeleton.json");
      writeFileSync(skeleton, JSON.stringify({ hooks: ["første utgave i serien"] }), "utf8");
      const res = run([
        "distil-check",
        "--distillate",
        join(dir, "absent.json"),
        "--skeleton",
        skeleton,
        "--json",
      ]);
      assert.equal(res.status, 0, res.stderr);
      const report = JSON.parse(res.stdout);
      assert.equal(report.verdict, "CLEAR");
      assert.equal(report.comparedAgainst, 0);
    } finally {
      rmSync(dir, { recursive: true, force: true });
    }
  });

  test("human-readable output names the reused unit and its edition", () => {
    const dir = tmp();
    try {
      const distillate = join(dir, "d.json");
      const extract = join(dir, "extract.json");
      const skeleton = join(dir, "skeleton.json");
      writeFileSync(extract, JSON.stringify(EXTRACT), "utf8");
      run(["distil-append", "--distillate", distillate, "--series", "seres", "--extract", extract]);
      writeFileSync(skeleton, JSON.stringify({ hooks: [EXTRACT.hooks[0]] }), "utf8");

      const res = run(["distil-check", "--distillate", distillate, "--skeleton", skeleton]);
      assert.equal(res.status, 0, res.stderr);
      assert.match(res.stdout, /REUSE/);
      assert.match(res.stdout, /05/);
    } finally {
      rmSync(dir, { recursive: true, force: true });
    }
  });

  test("bad invocations exit 2 with usage on stderr", () => {
    const dir = tmp();
    try {
      assert.equal(run(["distil-append", "--series", "seres"]).status, 2); // no --extract/--distillate
      assert.equal(run(["distil-check"]).status, 2); // no --skeleton
      assert.equal(run(["nonsense"]).status, 2);
      assert.equal(run([]).status, 2);

      // an extract missing required fields is a usage error, not a silent empty entry
      const bad = join(dir, "bad.json");
      writeFileSync(bad, JSON.stringify({ title: "no id" }), "utf8");
      const res = run(["distil-append", "--distillate", join(dir, "d.json"), "--series", "s", "--extract", bad]);
      assert.equal(res.status, 2);
      assert.match(res.stderr, /editionId/);
    } finally {
      rmSync(dir, { recursive: true, force: true });
    }
  });
});
