/**
 * @file Tests scripts/check-mcp-build.js — the freshness gate for the committed
 * mcp/build artifact. Content hashing is the whole point: mtimes are meaningless
 * after a clone, where every file is stamped at checkout time in arbitrary order.
 * Runs against synthetic trees, never the real mcp/.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

const { describe, it, beforeEach, after } = require("node:test");
const assert = require("node:assert/strict");
const fs = require("fs");
const os = require("os");
const path = require("path");

const { sourceHash, mcpBuildStatus, writeHash } = require("../../scripts/check-mcp-build");

const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "ccam-mcpbuild-"));
const SRC = path.join(ROOT, "mcp", "src");
const BUILD = path.join(ROOT, "mcp", "build");

function makeTree() {
  fs.rmSync(path.join(ROOT, "mcp"), { recursive: true, force: true });
  fs.mkdirSync(SRC, { recursive: true });
  fs.mkdirSync(BUILD, { recursive: true });
  fs.writeFileSync(path.join(SRC, "index.ts"), "export const a = 1;\n");
  fs.writeFileSync(path.join(ROOT, "mcp", "package.json"), '{"name":"x"}\n');
  fs.writeFileSync(path.join(BUILD, "index.js"), "exports.a = 1;\n");
}

after(() => fs.rmSync(ROOT, { recursive: true, force: true }));

describe("mcp build freshness", () => {
  beforeEach(makeTree);

  it("fails when the build has no recorded hash", () => {
    const status = mcpBuildStatus(ROOT);
    assert.equal(status.ok, false);
    assert.match(status.reason, /no recorded source hash/);
  });

  it("passes right after the hash is stamped", () => {
    writeHash(ROOT);
    assert.equal(mcpBuildStatus(ROOT).ok, true);
  });

  it("fails when a source file changes after the build", () => {
    writeHash(ROOT);
    fs.writeFileSync(path.join(SRC, "index.ts"), "export const a = 2;\n");
    const status = mcpBuildStatus(ROOT);
    assert.equal(status.ok, false);
    assert.match(status.reason, /stale/);
  });

  it("fails when a source file is added after the build", () => {
    writeHash(ROOT);
    fs.writeFileSync(path.join(SRC, "extra.ts"), "export const b = 1;\n");
    assert.equal(mcpBuildStatus(ROOT).ok, false);
  });

  it("fails when the build output is missing entirely", () => {
    writeHash(ROOT);
    fs.rmSync(path.join(BUILD, "index.js"));
    assert.match(mcpBuildStatus(ROOT).reason, /missing/);
  });

  it("ignores modification times — only content counts", () => {
    const before = sourceHash(ROOT);
    const future = Date.now() / 1000 + 10_000;
    fs.utimesSync(path.join(SRC, "index.ts"), future, future);
    assert.equal(sourceHash(ROOT), before);
  });

  it("tracks the manifest, so a dependency bump invalidates the build", () => {
    writeHash(ROOT);
    fs.writeFileSync(path.join(ROOT, "mcp", "package.json"), '{"name":"x","version":"2"}\n');
    assert.equal(mcpBuildStatus(ROOT).ok, false);
  });
});

describe("the committed mcp/build in this repo", () => {
  it("matches mcp/src", () => {
    const status = mcpBuildStatus(path.resolve(__dirname, "..", ".."));
    assert.equal(status.ok, true, status.reason);
  });
});
