import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import {
  extractManifestYaml,
  parseManifest,
  validateAllManifests,
} from '../../lib/parsers/manifest-yaml.mjs';

const STEP_BODY_GOOD = `### Step 1: Add validator

- Files: lib/foo.mjs
- Verify: \`npm test\` → expected: pass
- Checkpoint: \`git commit -m "feat(lib): foo"\`
- Manifest:
  \`\`\`yaml
  manifest:
    expected_paths:
      - lib/foo.mjs
    min_file_count: 1
    commit_message_pattern: "^feat\\\\(lib\\\\):"
    bash_syntax_check: []
    forbidden_paths: []
    must_contain: []
  \`\`\`
`;

const STEP_BODY_NO_MANIFEST = `### Step 1: oops

no manifest here
`;

const STEP_BODY_INVALID_REGEX = `### Step 1: bad regex

- Manifest:
  \`\`\`yaml
  manifest:
    expected_paths:
      - x
    min_file_count: 1
    commit_message_pattern: "[unclosed"
    bash_syntax_check: []
    forbidden_paths: []
    must_contain: []
  \`\`\`
`;

test('extractManifestYaml — finds fenced manifest block', () => {
  const yaml = extractManifestYaml(STEP_BODY_GOOD);
  assert.ok(yaml);
  assert.match(yaml, /expected_paths/);
});

test('extractManifestYaml — null when missing', () => {
  assert.equal(extractManifestYaml(STEP_BODY_NO_MANIFEST), null);
});

test('parseManifest — happy path produces all required keys', () => {
  const r = parseManifest(STEP_BODY_GOOD);
  assert.equal(r.valid, true, JSON.stringify(r.errors));
  assert.deepEqual(r.parsed.expected_paths, ['lib/foo.mjs']);
  assert.equal(r.parsed.min_file_count, 1);
  assert.match(r.parsed.commit_message_pattern, /^\^feat/);
});

test('parseManifest — missing manifest produces MANIFEST_MISSING', () => {
  const r = parseManifest(STEP_BODY_NO_MANIFEST);
  assert.equal(r.valid, false);
  assert.ok(r.errors.find(e => e.code === 'MANIFEST_MISSING'));
});

test('parseManifest — invalid regex caught', () => {
  const r = parseManifest(STEP_BODY_INVALID_REGEX);
  assert.equal(r.valid, false);
  assert.ok(r.errors.find(e => e.code === 'MANIFEST_PATTERN_INVALID'));
});

test('parseManifest — missing required key flagged', () => {
  const noCount = `### Step 1
- Manifest:
  \`\`\`yaml
  manifest:
    expected_paths:
      - x
    commit_message_pattern: "^x:"
    bash_syntax_check: []
    forbidden_paths: []
    must_contain: []
  \`\`\`
`;
  const r = parseManifest(noCount);
  assert.equal(r.valid, false);
  assert.ok(r.errors.find(e => e.code === 'MANIFEST_MISSING_KEY' && /min_file_count/.test(e.message)));
});

test('parseManifest — commit_message_pattern compiles via new RegExp', () => {
  const r = parseManifest(STEP_BODY_GOOD);
  const re = new RegExp(r.parsed.commit_message_pattern);
  assert.ok(re.test('feat(lib): added foo'));
  assert.ok(!re.test('chore: not it'));
});

test('parseManifest — must_contain list-of-dicts (real-world template form)', () => {
  const body = `### Step 1: Real
- Manifest:
  \`\`\`yaml
  manifest:
    expected_paths:
      - a.json
      - b.md
    min_file_count: 2
    commit_message_pattern: "^chore:"
    bash_syntax_check: []
    forbidden_paths:
      - CHANGELOG.md
    must_contain:
      - path: a.json
        pattern: '"version": "2\\.3\\.0"'
      - path: b.md
        pattern: "version-blue"
  \`\`\`
`;
  const r = parseManifest(body);
  assert.equal(r.valid, true, JSON.stringify(r.errors));
  assert.equal(r.parsed.must_contain.length, 2);
  assert.equal(r.parsed.must_contain[0].path, 'a.json');
  assert.equal(r.parsed.must_contain[1].path, 'b.md');
  assert.equal(r.parsed.forbidden_paths[0], 'CHANGELOG.md');
});

test('validateAllManifests — aggregates per-step issues', () => {
  const steps = [
    { n: 1, body: STEP_BODY_GOOD },
    { n: 2, body: STEP_BODY_NO_MANIFEST },
  ];
  const r = validateAllManifests(steps);
  assert.equal(r.valid, false);
  assert.ok(r.errors.find(e => /Step 2/.test(e.message)));
});
