#!/usr/bin/env node
"use strict";

const assert = require("node:assert");
const fs = require("node:fs");
const path = require("node:path");
const { execFileSync } = require("node:child_process");

const ROOT = process.env.MNOTE_REPO_ROOT || "/mnt/Data1T/mnote";
const TASK535 = path.join(ROOT, "scripts", "task535-page-ai-local-agent-clean-edit-smoke.js");
const OUTPUT_DIR = path.join(ROOT, "tmp", "task539-local-agent-audit-scope-contract");
const RESULT_PATH = path.join(OUTPUT_DIR, "result.json");

function run(command, args, options = {}) {
  return execFileSync(command, args, {
    cwd: ROOT,
    encoding: "utf8",
    stdio: ["ignore", "pipe", "pipe"],
    ...options,
  });
}

function parseLastJsonObject(output) {
  const trimmed = String(output || "").trim();
  const start = trimmed.lastIndexOf("\n{");
  const jsonText = start >= 0 ? trimmed.slice(start + 1) : trimmed;
  return JSON.parse(jsonText);
}

function parseRunBody(task535Result) {
  const runRecord = (task535Result.captured || []).find((item) => item.kind === "run");
  assert(runRecord, "task535 result 缺少 run payload");
  return JSON.parse(runRecord.body || "{}");
}

function main() {
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  const task535Output = run("node", [TASK535]);
  const task535Result = parseLastJsonObject(task535Output);
  assert.equal(task535Result.ok, true, "task535 clean edit smoke 必须通过");
  const runBody = parseRunBody(task535Result);
  const allowedFiles = runBody.targetPackage?.allowedFiles || [];
  assert.deepEqual(allowedFiles, [task535Result.relativePath], "targetPackage.allowedFiles 应只包含当前目标文件");
  assert.equal(runBody.targetPackage?.currentFile?.relativePath, task535Result.relativePath, "currentFile 应冻结为当前目标文件");
  assert.equal(runBody.runTargetSnapshot?.schema, "mnote.page_ai_run_target_snapshot.v1", "run payload 应携带 frozen target snapshot");
  assert(runBody.runTargetSnapshot?.frozenAt, "frozen target snapshot 应有 frozenAt");
  if (runBody.agentTargetPackage) {
    assert.deepEqual(runBody.agentTargetPackage.allowedFiles || [], [task535Result.relativePath], "agentTargetPackage.allowedFiles 应只包含当前目标文件");
  }
  assert.equal(task535Result.usedDocumentsSave, false, "clean edit 不应调用 /api/documents/save");
  assert.equal(task535Result.usedMarkdownEdit, false, "clean edit 不应调用 mnote.doc.markdown_edit");
  assert.equal(task535Result.usedPageSave, false, "clean edit 不应调用 mnote.page.save");

  const cargoOutput = run("cargo", ["test", "-p", "mnote-web", "local_agent_audit", "--", "--test-threads=1"], {
    cwd: path.join(ROOT, "rust"),
  });
  assert(cargoOutput.includes("local_agent_audit_allowed_files_override_folder_context"), "cargo test 应覆盖 allowedFiles 优先于 folder context");
  assert(cargoOutput.includes("local_agent_audit_reads_allowed_files_from_agent_run_envelope"), "cargo test 应覆盖 agentRunEnvelope.allowedFiles");
  assert(cargoOutput.includes("local_agent_audit_event_carries_agent_run_receipt"), "cargo test 应覆盖 receipt auditScope");

  const result = {
    ok: true,
    task: "task539-local-agent-audit-scope-contract",
    task535: {
      resultPath: path.join(ROOT, "tmp", "task535-page-ai-local-agent-clean-edit-smoke", "result.json"),
      relativePath: task535Result.relativePath,
      targetAllowedFiles: allowedFiles,
      frozenAt: runBody.runTargetSnapshot.frozenAt,
      usedDocumentsSave: task535Result.usedDocumentsSave,
      usedMarkdownEdit: task535Result.usedMarkdownEdit,
      usedPageSave: task535Result.usedPageSave,
    },
    backendAudit: {
      command: "cargo test -p mnote-web local_agent_audit -- --test-threads=1",
      expectedScope: "allowed_files",
      expectedFileCount: 1,
    },
  };
  fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8");
  console.log(JSON.stringify(result, null, 2));
}

try {
  main();
} catch (error) {
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  fs.writeFileSync(
    path.join(OUTPUT_DIR, "failure.json"),
    `${JSON.stringify({ ok: false, error: error.stack || error.message || String(error) }, null, 2)}\n`,
    "utf8",
  );
  console.error(error.stack || error.message || String(error));
  process.exit(1);
}
