/**
 * Smoke tests for the migrate-config CLI. We invoke the bash wrapper as a
 * subprocess so the test exercises the same code path operators will use.
 *
 * The bash wrapper auto-picks dist/scripts/migrate-config.js if it exists,
 * else falls back to tsx — both are tested transparently because we don't
 * care which runtime ran, only that the output is correct.
 */
import { afterEach, describe, expect, it } from 'vitest';
import { spawnSync } from 'child_process';
import { copyFileSync, existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = join(__dirname, '..', '..'); // src/scripts → repo root
const SCRIPT = join(REPO_ROOT, 'scripts', 'migrate-config.sh');
const FIXTURES = join(__dirname, '..', '__fixtures__', 'config-migration');

function runCli(args: string[]): { stdout: string; stderr: string; status: number } {
  const r = spawnSync('bash', [SCRIPT, ...args], { encoding: 'utf-8', cwd: REPO_ROOT });
  return { stdout: r.stdout ?? '', stderr: r.stderr ?? '', status: r.status ?? -1 };
}

describe('migrate-config CLI', () => {
  let tempDir = '';

  afterEach(() => {
    if (tempDir) {
      rmSync(tempDir, { recursive: true, force: true });
      tempDir = '';
    }
  });

  it('--help exits 0 and prints usage', () => {
    const r = runCli(['--help']);
    expect(r.status).toBe(0);
    expect(r.stdout).toContain('Usage: migrate-config');
  });

  it('unknown flag exits 3', () => {
    const r = runCli(['--nope']);
    expect(r.status).toBe(3);
    expect(r.stderr).toMatch(/unknown flag/);
  });

  it('missing config file exits 1', () => {
    tempDir = mkdtempSync(join(tmpdir(), 'migrate-config-'));
    const r = runCli(['--config', join(tempDir, 'nope.yaml')]);
    expect(r.status).toBe(1);
    expect(r.stderr).toMatch(/config file not found/);
  });

  it('dry-run on v1 fixture prints v2 YAML to stdout and does not modify the file', () => {
    tempDir = mkdtempSync(join(tmpdir(), 'migrate-config-'));
    const target = join(tempDir, 'config.yaml');
    copyFileSync(join(FIXTURES, 'v1-single-ollama.yaml'), target);
    const before = readFileSync(target, 'utf-8');

    const r = runCli(['--dry-run', '--config', target]);
    expect(r.status).toBe(0);
    expect(r.stdout).toMatch(/config_version: 2/);
    expect(r.stdout).toMatch(/connection_type: direct/);
    // Dry run does NOT touch the source file.
    expect(readFileSync(target, 'utf-8')).toBe(before);
    expect(readdirSync(tempDir).filter(f => f.includes('.bak-'))).toHaveLength(0);
  });

  it('in-place rewrite produces v2 file and a timestamped backup', () => {
    tempDir = mkdtempSync(join(tmpdir(), 'migrate-config-'));
    const target = join(tempDir, 'config.yaml');
    copyFileSync(join(FIXTURES, 'v1-single-ollama.yaml'), target);

    const r = runCli(['--config', target]);
    expect(r.status).toBe(0);

    const migrated = readFileSync(target, 'utf-8');
    expect(migrated).toMatch(/^config_version: 2/m);
    expect(migrated).toMatch(/storage:/);

    // Exactly one backup file in the same directory.
    const backups = readdirSync(tempDir).filter(f => f.startsWith('config.yaml.bak-'));
    expect(backups).toHaveLength(1);

    // Backup matches the original input.
    const original = readFileSync(join(FIXTURES, 'v1-single-ollama.yaml'), 'utf-8');
    expect(readFileSync(join(tempDir, backups[0]!), 'utf-8')).toBe(original);
  });

  it('already-v2 file → no-op + exit 0', () => {
    tempDir = mkdtempSync(join(tmpdir(), 'migrate-config-'));
    const target = join(tempDir, 'config.yaml');
    writeFileSync(
      target,
      [
        'config_version: 2',
        'llm:',
        '  workers:',
        '    - id: w1',
        '      connection_type: direct',
        '      endpoint: http://localhost:11434/v1',
        '      model: qwen3:32b',
        '      roles: [auto]',
        '      max_concurrency: 1',
        '      enabled: true',
        '',
      ].join('\n'),
    );
    const before = readFileSync(target, 'utf-8');
    const r = runCli(['--config', target]);
    expect(r.status).toBe(0);
    expect(r.stdout).toMatch(/already up to date/);
    // No rewrite (no backup either).
    expect(readFileSync(target, 'utf-8')).toBe(before);
    expect(readdirSync(tempDir).filter(f => f.includes('.bak-'))).toHaveLength(0);
  });

  it('config_version: 99 exits 2 with the typo-guard message', () => {
    tempDir = mkdtempSync(join(tmpdir(), 'migrate-config-'));
    const target = join(tempDir, 'config.yaml');
    writeFileSync(target, 'config_version: 99\nprovider:\n  model: x\n');
    const r = runCli(['--config', target]);
    expect(r.status).toBe(2);
    expect(r.stderr).toMatch(/config_version=99/);
  });
});
