// Slot-only four named mutants. Each child gets its own short TMPDIR and nice19.
// Never writes raw child output: canary mutations must not put secrets in receipts.
import { readFileSync, writeFileSync, rmSync } from 'node:fs';
import { execFileSync, spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { fileURLToPath } from 'node:url';
const root = fileURLToPath(new URL('../../../../', import.meta.url));
const page = 'packages/bus/src/proxy-page.mjs', serve = 'packages/webui/src/serve.mjs';
const cases = [
  { name: 'accounts-unfiltered-field', file: page, before: 'return { accountId, provider, displayName, status, priority };',
    after: 'return { ...row };', test: 'packages/bus/tests/proxy-page-accounts.test.mjs' },
  { name: 'callback-skips-state', file: page, before: "const state = url.searchParams.get('state'); this.#session(state, scope);",
    after: "const state = url.searchParams.get('state');", test: 'packages/bus/tests/proxy-page-state.test.mjs' },
  { name: 'proxy-route-accepts-granted-user', file: serve, before: "if (session?.user !== undefined) {",
    after: "if (session?.user !== undefined && path.startsWith('/api/proxy/')) return json(res, 200, { accepted: true });\n      if (session?.user !== undefined) {",
    test: 'packages/webui/tests/proxy-page.test.mjs' },
  { name: 'apikey-logged', file: page, before: 'await this.#client.putUpstreamGroups([...groups, group]);',
    after: 'process.stderr.write(args.key); await this.#client.putUpstreamGroups([...groups, group]);',
    test: 'packages/webui/tests/proxy-canary.test.mjs' },
];
function run(test) {
  const temp = execFileSync('mktemp', ['-d', '/mnt/storage/scratch/tmp/XXXXXX'], { encoding: 'utf8' }).trim();
  try {
    const command = `exec timeout -k 10 180 nice -n $((19 - $(nice))) bash -c 'printf "nice=%s\\n" "$(nice)"; exec node --test "$1"' bash "$1"`;
    const result = spawnSync('bash', ['-c', command, 'bash', root + test],
      { cwd: root, env: { ...process.env, TMPDIR: temp }, encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
    const out = (result.stdout ?? '') + (result.stderr ?? '');
    return { exit: result.status, nice: Number(out.match(/^nice=(\d+)/m)?.[1]),
      failures: Number(out.match(/(?:# fail|ℹ fail)\s+(\d+)/)?.[1] ?? -1),
      passes: Number(out.match(/(?:# pass|ℹ pass)\s+(\d+)/)?.[1] ?? -1),
      failingTests: [...out.matchAll(/^✖ (.+?) \([0-9.]+ms\)$/gm)].map(m => m[1]).filter((v, i, all) => all.indexOf(v) === i) };
  } finally { rmSync(temp, { recursive: true, force: true }); }
}
let failed = false;
for (const mutant of cases) {
  const file = root + mutant.file, original = readFileSync(file, 'utf8');
  if (original.split(mutant.before).length !== 2) throw Error('mutant anchor not unique: ' + mutant.name);
  let result;
  try {
    writeFileSync(file, original.replace(mutant.before, mutant.after));
    result = run(mutant.test);
  } finally { writeFileSync(file, original); }
  const killed = result.exit === 1 && result.failures > 0 && result.nice === 19;
  const restored = readFileSync(file, 'utf8') === original;
  console.log(JSON.stringify({ mutant: mutant.name, test: mutant.test, ...result, killed, restored,
    originalSha256: createHash('sha256').update(original).digest('hex') }));
  if (!killed || !restored) { failed = true; break; }
  const control = run(mutant.test);
  const positive = control.exit === 0 && control.failures === 0 && control.passes > 0 && control.nice === 19;
  console.log(JSON.stringify({ mutant: mutant.name, positiveControl: true, test: mutant.test, ...control, positive }));
  if (!positive) { failed = true; break; }
}
process.exitCode = failed ? 1 : 0;
