// tests/lib/profile-application.test.mjs
// SC #5-#9 + backward-compat edge-case for lib/profiles/resolver.mjs.

import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import {
  loadProfile,
  resolveProfile,
  resolveTrekcontinueProfile,
  validateProfileFile,
  findProfilePath,
} from '../../lib/profiles/resolver.mjs';

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

// SC #5: loadProfile returns matrix-match for all 6 phase_models

test('SC #5: loadProfile("economy") returns flattened phase_models with all 6 phases', () => {
  const p = loadProfile('economy');
  assert.equal(p.name, 'economy');
  assert.equal(p.phase_models.brief, 'sonnet');
  assert.equal(p.phase_models.research, 'sonnet');
  assert.equal(p.phase_models.plan, 'sonnet');
  assert.equal(p.phase_models.execute, 'sonnet');
  assert.equal(p.phase_models.review, 'sonnet');
  assert.equal(p.phase_models.continue, 'sonnet');
  assert.equal(p.parallel_agents_min, 2);
  assert.equal(p.parallel_agents_max, 3);
  assert.equal(p.external_research_enabled, false);
  assert.equal(p.brief_reviewer_iter_cap, 1);
});

test('SC #5: loadProfile("balanced") returns mixed phase_models', () => {
  const p = loadProfile('balanced');
  assert.equal(p.phase_models.plan, 'opus');
  assert.equal(p.phase_models.review, 'opus');
  assert.equal(p.phase_models.brief, 'sonnet');
  assert.equal(p.phase_models.execute, 'sonnet');
});

test('SC #5: loadProfile("premium") returns all-opus', () => {
  const p = loadProfile('premium');
  for (const phase of ['brief', 'research', 'plan', 'execute', 'review', 'continue']) {
    assert.equal(p.phase_models[phase], 'opus', `premium ${phase} should be opus`);
  }
});

test('SC #5: loadProfile throws PROFILE_NOT_FOUND for unknown profile', () => {
  try {
    loadProfile('does-not-exist-xyz');
    assert.fail('expected throw');
  } catch (e) {
    assert.equal(e.cause, 'PROFILE_NOT_FOUND');
    assert.match(e.message, /not found/);
    assert.ok(Array.isArray(e.attempted), 'should expose attempted paths');
  }
});

// SC #6: env-var fallback flag > env > default

test('SC #6: resolveProfile flag > env > default', () => {
  // flag wins
  const r1 = resolveProfile({ flags: { '--profile': 'balanced' } }, { VOYAGE_PROFILE: 'economy' });
  assert.equal(r1.profile, 'balanced');
  assert.equal(r1.profile_source, 'flag');

  // env wins when no flag
  const r2 = resolveProfile({ flags: {} }, { VOYAGE_PROFILE: 'economy' });
  assert.equal(r2.profile, 'economy');
  assert.equal(r2.profile_source, 'env');

  // default when neither
  const r3 = resolveProfile({ flags: {} }, {});
  assert.equal(r3.profile, 'premium');
  assert.equal(r3.profile_source, 'default');
});

// SC #7: performance — loadProfile 1000 iter < 50ms average (allowing some headroom)

test('SC #7: loadProfile 1000-iter performance < 50ms average', () => {
  const iterations = 1000;
  const start = performance.now();
  for (let i = 0; i < iterations; i++) {
    loadProfile('economy');
  }
  const elapsed = performance.now() - start;
  const avgMs = elapsed / iterations;
  assert.ok(avgMs < 50, `loadProfile too slow: ${avgMs.toFixed(3)}ms average over ${iterations} iter`);
});

// SC #8: custom.yaml from repo-root trumps ~/.claude/

test('SC #8: custom profile from <cwd>/voyage-profiles/<name>.yaml takes precedence over ~/.claude/', () => {
  const tmpRepo = mkdtempSync(join(tmpdir(), 'voyage-resolver-repo-'));
  const tmpHome = mkdtempSync(join(tmpdir(), 'voyage-resolver-home-'));
  try {
    // Place custom profile in repo and home — repo should win
    mkdirSync(join(tmpRepo, 'voyage-profiles'), { recursive: true });
    mkdirSync(join(tmpHome, '.claude', 'voyage-profiles'), { recursive: true });

    writeFileSync(join(tmpRepo, 'voyage-profiles', 'mycustom.yaml'),
      `---
profile_version: "1.0"
name: mycustom-repo
phase_models:
  - phase: brief
    model: sonnet
  - phase: research
    model: sonnet
  - phase: plan
    model: sonnet
  - phase: execute
    model: sonnet
  - phase: review
    model: sonnet
  - phase: continue
    model: sonnet
parallel_agents_min: 1
parallel_agents_max: 2
external_research_enabled: false
brief_reviewer_iter_cap: 1
---
`);
    writeFileSync(join(tmpHome, '.claude', 'voyage-profiles', 'mycustom.yaml'),
      `---
profile_version: "1.0"
name: mycustom-home
phase_models:
  - phase: brief
    model: opus
  - phase: research
    model: opus
  - phase: plan
    model: opus
  - phase: execute
    model: opus
  - phase: review
    model: opus
  - phase: continue
    model: opus
parallel_agents_min: 1
parallel_agents_max: 2
external_research_enabled: true
brief_reviewer_iter_cap: 3
---
`);

    const found = findProfilePath('mycustom', { cwd: tmpRepo, home: tmpHome });
    assert.ok(found.path, `expected to find mycustom; attempted: ${found.attempted.join(', ')}`);
    assert.ok(found.path.startsWith(tmpRepo),
      `expected repo-rot win (path under ${tmpRepo}), got: ${found.path}`);

    const p = loadProfile('mycustom', { cwd: tmpRepo, home: tmpHome });
    assert.equal(p.name, 'mycustom-repo', 'repo profile should win');
    assert.equal(p.phase_models.brief, 'sonnet');
  } finally {
    rmSync(tmpRepo, { recursive: true, force: true });
    rmSync(tmpHome, { recursive: true, force: true });
  }
});

test('SC #8: missing profile error message includes both attempted paths', () => {
  const tmpRepo = mkdtempSync(join(tmpdir(), 'voyage-resolver-empty-'));
  const tmpHome = mkdtempSync(join(tmpdir(), 'voyage-resolver-emptyhome-'));
  try {
    try {
      loadProfile('not-a-real-profile', { cwd: tmpRepo, home: tmpHome });
      assert.fail('expected throw');
    } catch (e) {
      assert.equal(e.cause, 'PROFILE_NOT_FOUND');
      // Both attempted paths should be in the error message for diagnostic clarity
      const msg = e.message;
      assert.match(msg, /voyage-profiles\/not-a-real-profile\.yaml/);
      assert.match(msg, /\.claude\/voyage-profiles\/not-a-real-profile\.yaml/);
    }
  } finally {
    rmSync(tmpRepo, { recursive: true, force: true });
    rmSync(tmpHome, { recursive: true, force: true });
  }
});

// SC #9: resolveTrekcontinueProfile inheritance from plan-frontmatter

test('SC #9: resolveTrekcontinueProfile inherits from plan-frontmatter (profile: balanced)', () => {
  const planPath = join(REPO_ROOT, 'tests', 'fixtures', 'plan-with-profile.md');
  const r = resolveTrekcontinueProfile(planPath, { flags: {} });
  assert.equal(r.profile, 'balanced');
  assert.equal(r.profile_source, 'inheritance');
});

test('SC #9: resolveTrekcontinueProfile flag overrides plan-frontmatter (advisory)', () => {
  const planPath = join(REPO_ROOT, 'tests', 'fixtures', 'plan-with-profile.md');
  const advisories = [];
  const fakeConsole = { error: (m) => advisories.push(m) };
  const r = resolveTrekcontinueProfile(planPath,
    { flags: { '--profile': 'economy' } },
    { console: fakeConsole });
  assert.equal(r.profile, 'economy');
  assert.equal(r.profile_source, 'flag');
  assert.equal(advisories.length, 1, 'expected one advisory message');
  assert.match(advisories[0], /balanced.*economy/);
  assert.match(advisories[0], /\[voyage\]/);
});

// Backward-compat edge-case: v4.0-style plan WITHOUT profile field

test('Backward-compat: resolveTrekcontinueProfile on v4.0 plan without profile field returns default premium', () => {
  const planPath = join(REPO_ROOT, 'tests', 'fixtures', 'plan-without-profile.md');
  const r = resolveTrekcontinueProfile(planPath, { flags: {} });
  assert.equal(r.profile, 'premium');
  assert.equal(r.profile_source, 'default');
});

test('Backward-compat: resolveTrekcontinueProfile with non-existent plan path returns default premium', () => {
  const r = resolveTrekcontinueProfile('/tmp/does-not-exist-plan-xyz.md', { flags: {} });
  assert.equal(r.profile, 'premium');
  assert.equal(r.profile_source, 'default');
});

// validateProfileFile re-export sanity

test('validateProfileFile re-exports validateProfile (locked-interface compat)', () => {
  const r = validateProfileFile(join(REPO_ROOT, 'lib', 'profiles', 'economy.yaml'));
  assert.equal(r.valid, true);
});
