// tests/validators/profile-validator.test.mjs
// SC #1, #2, #3: profile-validator validates lib/profiles/{economy,balanced,premium}.yaml
// (innebygde profiler) plus rejects invalid models and invalid enum types.

import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import {
  validateProfile,
  validateProfileContent,
  PROFILE_REQUIRED_FIELDS,
  PROFILE_REQUIRED_PHASES,
} from '../../lib/validators/profile-validator.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = join(__dirname, '..', '..');

// SC #1: alle 3 innebygde profiler grønne

for (const profileName of ['economy', 'balanced', 'premium']) {
  test(`SC #1: lib/profiles/${profileName}.yaml validates clean`, () => {
    const r = validateProfile(join(REPO_ROOT, 'lib', 'profiles', `${profileName}.yaml`));
    assert.equal(r.valid, true,
      `expected valid, got errors: ${JSON.stringify(r.errors, null, 2)}`);
    assert.equal(r.errors.length, 0);
    // Spot-check: all 6 phases present
    const phases = r.parsed.frontmatter.phase_models.map(p => p.phase);
    for (const required of PROFILE_REQUIRED_PHASES) {
      assert.ok(phases.includes(required), `${profileName} missing phase: ${required}`);
    }
  });
}

// SC #2: PROFILE_INVALID_MODEL fired when phase_models[N].model not in allowlist

test('SC #2: profile-invalid-model.yaml rejected with PROFILE_INVALID_MODEL at phase_models[2].model', () => {
  const r = validateProfile(join(REPO_ROOT, 'tests', 'fixtures', 'profile-invalid-model.yaml'));
  assert.equal(r.valid, false);
  const found = r.errors.find(e => e.code === 'PROFILE_INVALID_MODEL');
  assert.ok(found, `expected PROFILE_INVALID_MODEL, got: ${JSON.stringify(r.errors)}`);
  assert.equal(found.location, 'phase_models[2].model',
    `expected location phase_models[2].model, got ${found.location}`);
  assert.match(found.message, /gpt-4/);
});

// SC #3: PROFILE_INVALID_ENUM for wrong-type values

test('SC #3: profile-invalid-enum.yaml rejected with PROFILE_INVALID_ENUM (external_research_enabled is string)', () => {
  const r = validateProfile(join(REPO_ROOT, 'tests', 'fixtures', 'profile-invalid-enum.yaml'));
  assert.equal(r.valid, false);
  const found = r.errors.find(e => e.code === 'PROFILE_INVALID_ENUM' && /external_research_enabled/.test(e.message));
  assert.ok(found, `expected PROFILE_INVALID_ENUM for external_research_enabled, got: ${JSON.stringify(r.errors)}`);
  assert.match(found.message, /boolean/);
});

// VOYAGE_ALLOW_HAIKU env-var allows haiku model

test('VOYAGE_ALLOW_HAIKU=1 allows haiku in phase_models', () => {
  const haikuProfile = `---
profile_version: "1.0"
name: haiku-allowed
phase_models:
  - phase: brief
    model: haiku
  - phase: research
    model: sonnet
  - phase: plan
    model: opus
  - phase: execute
    model: sonnet
  - phase: review
    model: opus
  - phase: continue
    model: sonnet
parallel_agents_min: 2
parallel_agents_max: 4
external_research_enabled: false
brief_reviewer_iter_cap: 1
---
`;
  // Default env: haiku rejected
  const denied = validateProfileContent(haikuProfile, { env: { /* no VOYAGE_ALLOW_HAIKU */ } });
  assert.equal(denied.valid, false);
  const haikuErr = denied.errors.find(e => e.code === 'PROFILE_INVALID_MODEL' && /haiku/.test(e.message));
  assert.ok(haikuErr, `expected haiku rejection: ${JSON.stringify(denied.errors)}`);
  assert.match(haikuErr.message, /VOYAGE_ALLOW_HAIKU/);

  // With opt-in: haiku accepted
  const allowed = validateProfileContent(haikuProfile, { env: { VOYAGE_ALLOW_HAIKU: '1' } });
  assert.equal(allowed.valid, true,
    `expected valid with VOYAGE_ALLOW_HAIKU=1, got: ${JSON.stringify(allowed.errors)}`);
});

// Required fields presence

test('PROFILE_MISSING_FIELD when name absent', () => {
  const r = validateProfileContent(`---
profile_version: "1.0"
phase_models:
  - phase: brief
    model: sonnet
  - phase: research
    model: sonnet
  - phase: plan
    model: opus
  - phase: execute
    model: sonnet
  - phase: review
    model: opus
  - phase: continue
    model: sonnet
parallel_agents_min: 2
parallel_agents_max: 4
external_research_enabled: false
brief_reviewer_iter_cap: 1
---
`);
  assert.equal(r.valid, false);
  const found = r.errors.find(e => e.code === 'PROFILE_MISSING_FIELD' && /name/.test(e.message));
  assert.ok(found, `expected PROFILE_MISSING_FIELD for name, got: ${JSON.stringify(r.errors)}`);
});

// PROFILE_NOT_FOUND for missing file

test('PROFILE_NOT_FOUND for non-existent file', () => {
  const r = validateProfile('/tmp/does-not-exist-profile-xyz.yaml');
  assert.equal(r.valid, false);
  assert.ok(r.errors.find(e => e.code === 'PROFILE_NOT_FOUND'));
});

// REQUIRED_FIELDS frontmatter contract drift-pin

test('PROFILE_REQUIRED_FIELDS export drift-pin', () => {
  assert.deepEqual(
    [...PROFILE_REQUIRED_FIELDS].sort(),
    ['brief_reviewer_iter_cap', 'external_research_enabled', 'name',
     'parallel_agents_max', 'parallel_agents_min', 'phase_models'].sort(),
    'PROFILE_REQUIRED_FIELDS contract drift — pin contract',
  );
});

test('PROFILE_REQUIRED_PHASES export drift-pin', () => {
  assert.deepEqual(
    [...PROFILE_REQUIRED_PHASES].sort(),
    ['brief', 'research', 'plan', 'execute', 'review', 'continue'].sort(),
    'PROFILE_REQUIRED_PHASES contract drift — pin contract',
  );
});
