import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { test } from 'node:test';
import { fileURLToPath } from 'node:url';

const SCRIPT = join(fileURLToPath(new URL('.', import.meta.url)), 'list-error-codes.mjs');

function makeFixtureRepo() {
  const root = mkdtempSync(join(tmpdir(), 'list-error-codes-'));
  const git = (...args) => execFileSync('git', args, { cwd: root, stdio: 'pipe' });
  git('init', '-q');
  git('config', 'user.email', 'test@example.invalid');
  git('config', 'user.name', 'test');

  const srcDir = join(root, 'packages', 'pkg-a', 'src');
  mkdirSync(srcDir, { recursive: true });
  writeFileSync(
    join(srcDir, 'errors.ts'),
    `export const a = 'CONFIG.FILE_NOT_FOUND';
export const b = 'MIGRATION.CHECK_DANGLING_REF';
export const notACode = 'NOT_A_NAMESPACE.SUBCODE';
export const lowercase = 'config.file_not_found';
`,
  );
  writeFileSync(join(srcDir, 'errors.test.ts'), `export const t = 'DRIVER.NOT_CONNECTED';\n`);
  const testDir = join(root, 'packages', 'pkg-a', 'test');
  mkdirSync(testDir, { recursive: true });
  writeFileSync(join(testDir, 'x.ts'), `export const t = 'BUDGET.ROWS_EXCEEDED';\n`);
  git('add', '.');
  git('commit', '-q', '-m', 'fixture');
  return root;
}

function run(args) {
  try {
    const stdout = execFileSync('node', [SCRIPT, ...args], { encoding: 'utf-8', stdio: 'pipe' });
    return { code: 0, stdout, stderr: '' };
  } catch (err) {
    return { code: err.status, stdout: err.stdout ?? '', stderr: err.stderr ?? '' };
  }
}

test('extracts closed-namespace literals from production src only', () => {
  const root = makeFixtureRepo();
  try {
    const { code, stdout } = run(['--root', root]);
    assert.equal(code, 0);
    const entries = JSON.parse(stdout);
    assert.deepEqual(
      entries.map((e) => e.code),
      ['CONFIG.FILE_NOT_FOUND', 'MIGRATION.CHECK_DANGLING_REF'],
    );
    assert.deepEqual(entries[0].files, ['packages/pkg-a/src/errors.ts']);
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});

test('markdown mode groups by namespace with one heading per code', () => {
  const root = makeFixtureRepo();
  try {
    const { code, stdout } = run(['--root', root, '--format', 'markdown']);
    assert.equal(code, 0);
    assert.match(stdout, /## CONFIG\n\n### CONFIG\.FILE_NOT_FOUND/);
    assert.match(stdout, /## MIGRATION\n\n### MIGRATION\.CHECK_DANGLING_REF/);
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});

test('verify passes when the page lists every code', () => {
  const root = makeFixtureRepo();
  const page = join(root, 'page.md');
  try {
    writeFileSync(page, 'CONFIG.FILE_NOT_FOUND and MIGRATION.CHECK_DANGLING_REF\n');
    const { code, stdout } = run(['--root', root, '--verify', page]);
    assert.equal(code, 0);
    assert.match(stdout, /lists all 2 known codes/);
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});

test('verify fails listing the missing codes', () => {
  const root = makeFixtureRepo();
  const page = join(root, 'page.md');
  try {
    writeFileSync(page, 'CONFIG.FILE_NOT_FOUND only\n');
    const { code, stderr } = run(['--root', root, '--verify', page]);
    assert.equal(code, 1);
    assert.match(stderr, /missing 1 of 2 known codes/);
    assert.match(stderr, /MIGRATION\.CHECK_DANGLING_REF/);
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});