import assert from "node:assert/strict";
import test from "node:test";
import * as sanitizer from "./public-sanitizer.js";

const config = {
  projectsRoot: "C:\\Project",
  accounts: [{ codexHome: "C:\\Users\\operator\\.codex-main" }],
  workspaces: [{ path: "C:\\Project\\Codex-remote" }]
};

test("redacts secrets and configured absolute paths from public text", () => {
  const input = [
    "Bearer secret-token",
    "sk-secretkey",
    '"access_token":"access-secret"',
    '"refresh_token":"refresh-secret"',
    "CODEX_HOME=C:\\Users\\operator\\.codex-main",
    "workspace C:\\Project\\Codex-remote\\apps\\backend",
    "root C:\\Project\\another-project"
  ].join("\n");

  const output = sanitizer.sanitizePublicText(input, config);

  for (const secret of [
    "secret-token", "secretkey", "access-secret", "refresh-secret",
    "C:\\Users\\operator\\.codex-main", "C:\\Project\\Codex-remote", "C:\\Project"
  ]) {
    assert.equal(output.includes(secret), false, secret);
  }
});

test("redacts JSON-escaped configured Windows paths", () => {
  const input = JSON.stringify({ error: "failed at C:\\Project\\Codex-remote\\private.txt" });

  const output = sanitizer.sanitizePublicText(input, config);

  assert.equal(output.includes("C:\\\\Project\\\\Codex-remote"), false);
});

test("redacts auth.json after configured private paths", () => {
  const output = sanitizer.sanitizePublicText("Open C:\\Project\\Codex-remote\\auth.json", config);

  assert.equal(output.includes("auth.json"), false);
  assert.equal(output, "Open [private-path][redacted]");
});

test("redacts unconfigured Windows absolute paths from public text", () => {
  const output = sanitizer.sanitizePublicText("Open C:\\Users\\operator\\.codex\\auth.json");

  assert.equal(output.includes("C:\\Users\\operator"), false);
  assert.equal(output, "Open [private-path]");
});

test("redacts data URLs from every public text surface", () => {
  const output = sanitizer.sanitizePublicText("image data:image/png;base64,cHJpdmF0ZQ== end");

  assert.equal(output, "image [redacted] end");
});

test("preserves ordinary prose and workspace-relative Changes paths", () => {
  const input = "Please update apps/backend/src/index.js without changing normal assistant text.";

  assert.equal(sanitizer.sanitizePublicText(input, config), input);
});

test("redacts Basic credentials plus quoted UNC and spaced Windows paths", () => {
  const output = sanitizer.sanitizePublicText([
    "Authorization: Basic dXNlcjpwYXNzd29yZA==",
    'Open "C:\\Users\\Jane Doe\\Private Folder\\secret file.txt"',
    'Read "\\\\server\\private share\\Secret Folder\\notes.txt"'
  ].join("\n"));

  assert.equal(output.includes("dXNlcjpwYXNzd29yZA"), false);
  assert.equal(output.includes("Jane Doe"), false);
  assert.equal(output.includes("private share"), false);
  assert.match(output, /Authorization: "\[redacted\]"/);
  assert.match(output, /\[private-path\]/);
});

test("redacts structured credential keys while preserving safe metadata", () => {
  const output = sanitizer.sanitizePublicData({
    title: "Normal activity",
    details: {
      accessToken: "access secret with spaces",
      refresh_token: "refresh-secret",
      password: "plain password",
      authorization: "Basic dXNlcjpwYXNz"
    }
  });

  assert.equal(output.title, "Normal activity");
  assert.deepEqual(output.details, {
    accessToken: "[redacted]",
    refresh_token: "[redacted]",
    password: "[redacted]",
    authorization: "[redacted]"
  });
});

test("redacts cookie headers, token variants, auth filenames, and slash-style Windows paths", () => {
  const output = sanitizer.sanitizePublicText([
    "Cookie: device_session=cookie-secret; theme=dark",
    "Set-Cookie: refresh=cookie-refresh; HttpOnly; Secure",
    '"session_token":"session-secret"',
    '"csrfToken":"csrf-secret"',
    "Open C:/Users/operator/.codex/auth.json",
    "Failed at C:/Users/Jane Doe/Private Folder/secret.txt before retry",
    "Read C:\\Users\\operator\\.codex\\credentials.json",
    "Read C:\\Users\\operator\\AppData\\cookies.sqlite"
  ].join("\n"));

  for (const secret of ["cookie-secret", "cookie-refresh", "session-secret", "csrf-secret", "operator", "Jane Doe", "Private Folder", "auth.json", "credentials.json", "cookies.sqlite"]) {
    assert.equal(output.includes(secret), false, secret);
  }

  const structured = sanitizer.sanitizePublicData({
    safe: "keep me",
    cookieHeader: "device_session=structured-cookie",
    idToken: "structured-token",
    authFileName: "auth.json"
  });
  assert.deepEqual(structured, {
    safe: "keep me",
    cookieHeader: "[redacted]",
    idToken: "[redacted]",
    authFileName: "[redacted]"
  });
});

test("structured sanitizer fails closed on cycles and throwing properties", () => {
  const cyclic = { safe: "visible" };
  cyclic.self = cyclic;
  Object.defineProperty(cyclic, "danger", {
    enumerable: true,
    get() { throw new Error("Bearer getter-secret at C:\\Users\\operator\\auth.json"); }
  });

  const output = sanitizer.sanitizePublicData(cyclic);

  assert.equal(JSON.stringify(output).includes("getter-secret"), false);
  assert.equal(output.safe, "visible");
  assert.equal(output.self, "[redacted]");
  assert.equal(output.danger, "[redacted]");
});

test("structured sanitizer compiles configured private paths once per payload", () => {
  let accountReads = 0;
  const measuredConfig = {
    projectsRoot: "C:\\Project",
    get accounts() {
      accountReads += 1;
      return [{ codexHome: "C:\\Private\\Codex" }];
    },
    workspaces: [{ path: "C:\\Project\\App" }]
  };

  const output = sanitizer.sanitizePublicData([
    "Open C:\\Private\\Codex\\sessions",
    "Open C:\\Project\\App\\file.txt",
    "Open C:\\Project\\Other\\file.txt"
  ], measuredConfig);

  assert.equal(accountReads, 1);
  assert.deepEqual(output, ["Open [private-path]\\sessions", "Open [private-path]\\file.txt", "Open [private-path]\\Other\\file.txt"]);
});
