const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { spawnSync } = require('child_process');

let passed = 0;
let failed = 0;

const repoRoot = path.resolve(__dirname, '..', '..');
const cliPath = path.join(
  repoRoot,
  'skills',
  'continuous-learning-v2',
  'scripts',
  'instinct-cli.py'
);

function detectPython3() {
  for (const bin of ['python3', 'python']) {
    const r = spawnSync(bin, ['--version'], { encoding: 'utf8' });
    if (r.status === 0 && /Python 3/.test(r.stdout + r.stderr)) return bin;
  }
  return null;
}

const PYTHON3 = detectPython3();
if (!PYTHON3) {
  console.log('\n=== Testing instinct-cli.py evolve generation ===\n');
  console.log('  - skipped: Python 3 not found in PATH');
  console.log('\nPassed: 0');
  console.log('Failed: 0');
  process.exit(0);
}

function test(name, fn) {
  try {
    fn();
    console.log(`  ✓ ${name}`);
    passed += 1;
  } catch (error) {
    console.log(`  ✗ ${name}`);
    console.log(`    Error: ${error.message}`);
    failed += 1;
  }
}

function createTempDir() {
  return fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-instinct-cli-evolve-'));
}

function cleanupDir(dir) {
  fs.rmSync(dir, { recursive: true, force: true });
}

function writeInstinct(root, id, trigger, confidence = 0.8, domain = 'workflow') {
  const dir = path.join(root, 'instincts', 'personal');
  fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(
    path.join(dir, `${id}.yaml`),
    [
      '---',
      `id: ${id}`,
      `trigger: "${trigger}"`,
      `confidence: ${confidence}`,
      `domain: ${domain}`,
      '---',
      '',
      `## Action`,
      '',
      `Action for ${id}.`,
      '',
    ].join('\n')
  );
}

// CLV2_NO_PROJECT pins the run to global scope, so seeded instincts live in
// <root>/instincts/personal and generated files land in <root>/evolved.
function runCli(root, args) {
  return spawnSync(PYTHON3, [cliPath, ...args], {
    cwd: repoRoot,
    encoding: 'utf8',
    env: {
      ...process.env,
      CLV2_HOMUNCULUS_DIR: root,
      CLV2_NO_PROJECT: '1',
      HOME: path.join(root, 'home'),
      USERPROFILE: path.join(root, 'home'),
      CLAUDE_PROJECT_DIR: '',
    },
  });
}

function generatedCommands(root) {
  const dir = path.join(root, 'evolved', 'commands');
  if (!fs.existsSync(dir)) return [];
  return fs.readdirSync(dir).sort();
}

// Eight unrelated workflow triggers: no two share enough keywords to cluster,
// so each one is its own command candidate.
const EIGHT_TRIGGERS = [
  ['run-tests', 'when running tests'],
  ['build-images', 'when building images'],
  ['deploy-services', 'when deploying services'],
  ['profile-memory', 'when profiling memory'],
  ['rotate-secrets', 'when rotating secrets'],
  ['tag-releases', 'when tagging releases'],
  ['prune-caches', 'when pruning caches'],
  ['review-requests', 'when reviewing pull requests'],
];

function seedEight(root) {
  for (const [id, trigger] of EIGHT_TRIGGERS) {
    writeInstinct(root, id, trigger);
  }
}

console.log('\n=== Testing instinct-cli.py evolve generation ===\n');

test('generated command names are cut on a word boundary', () => {
  const root = createTempDir();
  try {
    writeInstinct(root, 'archaeology', 'when investigating complex systems');
    writeInstinct(root, 'codebases', 'when learning about complex codebases');
    writeInstinct(root, 'large-text', 'when analyzing large text files');

    const result = runCli(root, ['evolve', '--generate']);
    assert.strictEqual(result.status, 0, result.stderr);

    const names = generatedCommands(root);
    // A hard slice used to yield investigating-comple.md and
    // learning-about-compl.md, which read as typos.
    assert.deepStrictEqual(names, [
      'analyzing-large-text.md',
      'investigating.md',
      'learning-about.md',
    ]);
  } finally {
    cleanupDir(root);
  }
});

test('a cut landing on a separator keeps the whole word', () => {
  const root = createTempDir();
  try {
    writeInstinct(root, 'a', 'when analyzing large text files');
    writeInstinct(root, 'b', 'when running tests');
    writeInstinct(root, 'c', 'when building images');

    assert.strictEqual(runCli(root, ['evolve', '--generate']).status, 0);
    // "analyzing-large-text" is exactly the slug limit and ends on a word, so
    // nothing further may be dropped.
    assert.ok(generatedCommands(root).includes('analyzing-large-text.md'));
  } finally {
    cleanupDir(root);
  }
});

test('every command candidate is generated, not just the first five', () => {
  const root = createTempDir();
  try {
    seedEight(root);

    const result = runCli(root, ['evolve', '--generate']);
    assert.strictEqual(result.status, 0, result.stderr);
    assert.strictEqual(
      generatedCommands(root).length,
      EIGHT_TRIGGERS.length,
      'a fixed cap silently dropped candidates'
    );
  } finally {
    cleanupDir(root);
  }
});

test('--limit caps generation and reports what it skipped', () => {
  const root = createTempDir();
  try {
    seedEight(root);

    const result = runCli(root, ['evolve', '--generate', '--limit', '3']);
    assert.strictEqual(result.status, 0, result.stderr);
    assert.strictEqual(generatedCommands(root).length, 3);
    assert.match(result.stdout, /writing 3 of 8 command candidates/);
    assert.match(result.stdout, /5 skipped/);
  } finally {
    cleanupDir(root);
  }
});

test('colliding slugs produce distinct files instead of overwriting', () => {
  const root = createTempDir();
  try {
    // Both triggers trim to "investigating".
    writeInstinct(root, 'first', 'when investigating complex systems');
    writeInstinct(root, 'second', 'when investigating extraordinarily convoluted pipelines');
    writeInstinct(root, 'third', 'when running tests');

    assert.strictEqual(runCli(root, ['evolve', '--generate']).status, 0);

    const names = generatedCommands(root);
    assert.ok(names.includes('investigating.md'), `missing base name in ${names}`);
    assert.ok(names.includes('investigating-2.md'), `missing deduped name in ${names}`);
    assert.strictEqual(new Set(names).size, names.length);
  } finally {
    cleanupDir(root);
  }
});

test('preview states how many candidates it left out', () => {
  const root = createTempDir();
  try {
    seedEight(root);

    const result = runCli(root, ['evolve']);
    assert.strictEqual(result.status, 0, result.stderr);
    assert.match(result.stdout, /COMMAND CANDIDATES \(8\)/);
    assert.match(result.stdout, /and 3 more command candidates not shown/);
  } finally {
    cleanupDir(root);
  }
});

test('preview names match the files --generate writes', () => {
  const root = createTempDir();
  try {
    writeInstinct(root, 'first', 'when investigating complex systems');
    writeInstinct(root, 'second', 'when investigating extraordinarily convoluted pipelines');
    writeInstinct(root, 'third', 'when running tests');

    const preview = runCli(root, ['evolve']);
    assert.strictEqual(preview.status, 0, preview.stderr);
    assert.match(preview.stdout, /\/investigating\b/);
    assert.match(preview.stdout, /\/investigating-2\b/);

    assert.strictEqual(runCli(root, ['evolve', '--generate']).status, 0);
    const names = generatedCommands(root);
    assert.ok(names.includes('investigating.md'));
    assert.ok(names.includes('investigating-2.md'));
  } finally {
    cleanupDir(root);
  }
});

console.log(`\nPassed: ${passed}`);
console.log(`Failed: ${failed}`);

process.exit(failed > 0 ? 1 : 0);
