/**
 * redact-audit-log — append-only forensic trail for the Phase 4.5a semantic
 * review (D5). Records WHETHER the semantic pass marked a body clean/flagged and
 * WHICH categories fired — never the body content. A body_sha256 lets a later
 * investigation confirm "the pass saw this exact draft and called it clean."
 *
 * The file (`~/.gstack/security/semantic-reviews.jsonl`) is sensitive metadata,
 * not "safe": it leaks repo names, timing, and a membership oracle via the hash.
 * Written 0600. Local-only — no third-party egress.
 *
 * Usable two ways:
 *   - CLI:  bun lib/redact-audit-log.ts '<json-line-without-ts/hash>' [body-file]
 *           (the skill passes the outcome JSON + a path to the scanned body; we
 *            stamp ts + body_sha256 and append.)
 *   - import { appendSemanticReview } from "./redact-audit-log";
 */
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { createHash } from "crypto";
import { appendJsonl } from "./jsonl-store";

export interface SemanticReviewEntry {
  ts: string;
  spec_archive_path?: string;
  repo_visibility: string;
  outcome: "clean" | "flagged";
  categories_flagged: string[];
  body_sha256: string;
}

function securityDir(): string {
  const home = process.env.GSTACK_HOME || path.join(os.homedir(), ".gstack");
  return path.join(home, "security");
}

export function sha256(s: string): string {
  return createHash("sha256").update(s, "utf8").digest("hex");
}

/** Append one entry. Best-effort: never throws into the caller's flow. */
export function appendSemanticReview(entry: SemanticReviewEntry): void {
  try {
    const dir = securityDir();
    fs.mkdirSync(dir, { recursive: true });
    const file = path.join(dir, "semantic-reviews.jsonl");
    // 0600 at create via appendJsonl's mode opt; the chmod backstop covers
    // files created looser by pre-mode versions.
    appendJsonl(file, entry, { mode: 0o600 });
    try {
      fs.chmodSync(file, 0o600);
    } catch {
      // chmod can fail on some filesystems; the append still happened.
    }
  } catch {
    // audit log is best-effort, not the security boundary
  }
}

// ── CLI ───────────────────────────────────────────────────────────────────────

function now(): string {
  // Date is allowed here (CLI process, not a resumable workflow).
  return new Date().toISOString();
}

if (import.meta.main) {
  const json = process.argv[2];
  const bodyFile = process.argv[3];
  if (!json) {
    process.stderr.write(
      'usage: redact-audit-log \'{"repo_visibility":"public","outcome":"flagged","categories_flagged":["legal"],"spec_archive_path":"..."}\' [body-file]\n',
    );
    process.exit(1);
  }
  let partial: Partial<SemanticReviewEntry>;
  try {
    partial = JSON.parse(json);
  } catch {
    process.stderr.write("redact-audit-log: invalid JSON\n");
    process.exit(1);
  }
  const body = bodyFile && fs.existsSync(bodyFile) ? fs.readFileSync(bodyFile, "utf8") : "";
  appendSemanticReview({
    ts: now(),
    repo_visibility: partial.repo_visibility ?? "unknown",
    outcome: partial.outcome === "flagged" ? "flagged" : "clean",
    categories_flagged: partial.categories_flagged ?? [],
    body_sha256: sha256(body),
    ...(partial.spec_archive_path ? { spec_archive_path: partial.spec_archive_path } : {}),
  });
}
