import assert from "node:assert/strict";
import test from "node:test";
import { mkdir, rm, symlink } from "node:fs/promises";
import path from "node:path";
import { JSDOM } from "jsdom";
import createDOMPurify from "dompurify";

import { WORKSPACE_ROOT } from "../src/config.ts";
import { mockEventSequence } from "../src/agent/mock.ts";
import { redactSecrets, validatePassword } from "../src/security.ts";
import { isDeniedProjectName, validateWorkspacePath } from "../src/projects.ts";

test("strong password validation", () => {
  assert.ok(validatePassword("short").length >= 4);
  assert.deepEqual(validatePassword("A-strong-password7"), []);
});

test("workspace traversal, denied folders, and symlink escapes are rejected", async () => {
  await assert.rejects(validateWorkspacePath("../mail"), /PROJECT_INVALID|ENOENT/);
  const fixture = path.join(WORKSPACE_ROOT, `ux7-workspace-security-${process.pid}`);
  const nested = path.join(fixture, "nested-project");
  const denied = path.join(fixture, "node_modules");
  const link = path.join(fixture, "escape-link");
  await rm(fixture, { recursive: true, force: true });
  await mkdir(nested, { recursive: true }); await mkdir(denied);
  await symlink("/tmp", link);
  try {
    assert.equal(await validateWorkspacePath(fixture, false), fixture);
    assert.equal(await validateWorkspacePath(nested, true), nested);
    await assert.rejects(validateWorkspacePath(denied, true), /PROJECT_INVALID/);
    await assert.rejects(validateWorkspacePath(link, true), /PROJECT_INVALID/);
    assert.equal(isDeniedProjectName("vendor"), true); assert.equal(isDeniedProjectName(".private"), true); assert.equal(isDeniedProjectName("site"), false);
  } finally { await rm(fixture, { recursive: true, force: true }); }
});

test("mock adapter emits the required deterministic workflow order", () => {
  const types = mockEventSequence("Improve the application").map((event) => event.type);
  assert.deepEqual(types.slice(0, 8), ["queued", "started", "searching", "reading", "thinking", "editing", "running_command", "testing"]);
  assert.equal(types.at(-1), "completed");
  assert.ok(types.includes("partial"));
});

test("secret redaction removes common credentials", () => {
  const output = redactSecrets("token=supersecretvalue api_key=abc123456789 sk-abcdefghijklmnop");
  assert.doesNotMatch(output, /supersecretvalue|abc123456789|sk-abcdefghijklmnop/);
});

test("Markdown sanitization strips executable content", () => {
  const window = new JSDOM("").window;
  const purify = createDOMPurify(window as any);
  const clean = purify.sanitize('<img src=x onerror="alert(1)"><script>alert(2)</script><a href="javascript:alert(3)">x</a>');
  assert.doesNotMatch(clean, /onerror|script|javascript:/i);
  window.close();
});
