// reference-config.test.mjs — Tests for the reference configuration generator
// Tests against fixtures in tests/fixtures/posture-scan/ with:
//   - grade-a-project: already Grade A → no recommendations
//   - grade-f-project: dangerous flags, no hooks → full recommendations

import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { resolve, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { mkdirSync, writeFileSync, rmSync, readFileSync, existsSync } from 'node:fs';
import { resetCounter } from '../../scanners/lib/output.mjs';
import { generate } from '../../scanners/reference-config-generator.mjs';

const __dirname = fileURLToPath(new URL('.', import.meta.url));
const GRADE_A_FIXTURE = resolve(__dirname, '../fixtures/posture-scan/grade-a-project');
const GRADE_F_FIXTURE = resolve(__dirname, '../fixtures/posture-scan/grade-f-project');

// ---------------------------------------------------------------------------
// Grade A project — already well-configured, no changes needed
// ---------------------------------------------------------------------------

describe('reference-config-generator: grade-a-project', () => {
  let result;

  beforeEach(async () => {
    resetCounter();
    result = await generate(GRADE_A_FIXTURE);
  });

  it('returns status ok', () => {
    assert.equal(result.status, 'ok');
  });

  it('detects project type as standalone', () => {
    assert.equal(result.projectType, 'standalone');
  });

  it('includes posture grade', () => {
    assert.equal(result.posture.grade, 'A');
  });

  it('has zero or minimal recommendations', () => {
    // Grade A should need very few changes (maybe none)
    const actionable = result.recommendations.filter(r => r.action !== 'none');
    assert.ok(actionable.length <= 2, `Expected <= 2 actionable, got ${actionable.length}`);
  });

  it('does not recommend settings.json changes', () => {
    const settingsRec = result.recommendations.find(r => r.file === '.claude/settings.json' && r.action !== 'none');
    assert.equal(settingsRec, undefined, 'Should not recommend settings changes for Grade A');
  });

  it('applied is false by default (dry-run)', () => {
    assert.equal(result.applied, false);
  });
});

// ---------------------------------------------------------------------------
// Grade F project — needs everything
// ---------------------------------------------------------------------------

describe('reference-config-generator: grade-f-project', () => {
  let result;

  beforeEach(async () => {
    resetCounter();
    result = await generate(GRADE_F_FIXTURE);
  });

  it('returns status ok', () => {
    assert.equal(result.status, 'ok');
  });

  it('detects project type', () => {
    assert.ok(
      ['plugin', 'standalone', 'monorepo'].includes(result.projectType),
      `Expected valid project type, got ${result.projectType}`,
    );
  });

  it('includes posture grade F', () => {
    assert.equal(result.posture.grade, 'F');
  });

  it('recommends settings.json with deny-first', () => {
    const rec = result.recommendations.find(r => r.file === '.claude/settings.json');
    assert.ok(rec, 'Expected settings.json recommendation');
    assert.ok(rec.action === 'create' || rec.action === 'merge', `Expected create or merge, got ${rec.action}`);
    assert.ok(rec.content.includes('deny'), 'Settings should include deny-first');
  });

  it('recommends CLAUDE.md security section', () => {
    const rec = result.recommendations.find(r => r.file === 'CLAUDE.md');
    assert.ok(rec, 'Expected CLAUDE.md recommendation');
    assert.ok(rec.content.includes('Security Boundaries'), 'Should include security boundaries');
  });

  it('recommends .gitignore additions', () => {
    const rec = result.recommendations.find(r => r.file === '.gitignore');
    assert.ok(rec, 'Expected .gitignore recommendation');
    assert.ok(rec.content.includes('.env'), 'Should include .env');
  });

  it('has multiple recommendations', () => {
    const actionable = result.recommendations.filter(r => r.action !== 'none');
    assert.ok(actionable.length >= 3, `Expected >= 3 actionable, got ${actionable.length}`);
  });

  it('each recommendation has required fields', () => {
    for (const rec of result.recommendations) {
      assert.ok(rec.category, `Missing category in recommendation`);
      assert.ok(rec.file, `Missing file in recommendation`);
      assert.ok(rec.action, `Missing action in recommendation`);
      assert.ok(typeof rec.content === 'string', `Missing content in recommendation`);
    }
  });
});

// ---------------------------------------------------------------------------
// Apply mode — writes files to a temp directory
// ---------------------------------------------------------------------------

describe('reference-config-generator: --apply mode', () => {
  const tmpDir = resolve(__dirname, '../fixtures/posture-scan/tmp-apply-test');

  beforeEach(() => {
    // Create a bare project to apply to
    rmSync(tmpDir, { recursive: true, force: true });
    mkdirSync(tmpDir, { recursive: true });
    writeFileSync(join(tmpDir, 'CLAUDE.md'), '# Test Project\n\nA bare project.\n');
  });

  it('creates settings.json when applying', async () => {
    resetCounter();
    const result = await generate(tmpDir, { apply: true });
    assert.equal(result.applied, true);
    const settingsPath = join(tmpDir, '.claude', 'settings.json');
    assert.ok(existsSync(settingsPath), 'settings.json should exist after apply');
    const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
    assert.equal(settings.permissions.defaultPermissionLevel, 'deny');
  });

  it('appends security section to existing CLAUDE.md', async () => {
    resetCounter();
    await generate(tmpDir, { apply: true });
    const content = readFileSync(join(tmpDir, 'CLAUDE.md'), 'utf-8');
    assert.ok(content.includes('Security Boundaries'), 'CLAUDE.md should have security section');
    assert.ok(content.includes('# Test Project'), 'Original content should be preserved');
  });

  it('creates .gitignore with security patterns', async () => {
    resetCounter();
    await generate(tmpDir, { apply: true });
    const content = readFileSync(join(tmpDir, '.gitignore'), 'utf-8');
    assert.ok(content.includes('.env'), '.gitignore should include .env');
    assert.ok(content.includes('*.key'), '.gitignore should include *.key');
  });

  it('does not overwrite existing settings.json', async () => {
    // Create existing settings
    mkdirSync(join(tmpDir, '.claude'), { recursive: true });
    writeFileSync(join(tmpDir, '.claude', 'settings.json'), JSON.stringify({
      permissions: { defaultPermissionLevel: 'deny', allow: ['Read(*)'] },
      customKey: 'preserved',
    }, null, 2));

    resetCounter();
    const result = await generate(tmpDir, { apply: true });
    const settings = JSON.parse(readFileSync(join(tmpDir, '.claude', 'settings.json'), 'utf-8'));
    assert.equal(settings.customKey, 'preserved', 'Custom keys should be preserved');
  });

  it('reports backupPath when applying', async () => {
    resetCounter();
    const result = await generate(tmpDir, { apply: true });
    // backupPath is set when there were existing files to back up
    // For a bare project, it may or may not create backup
    assert.ok(typeof result.backupPath === 'string' || result.backupPath === null);
  });

  // Cleanup
  it('cleanup', () => {
    rmSync(tmpDir, { recursive: true, force: true });
  });
});

// ---------------------------------------------------------------------------
// Project type detection
// ---------------------------------------------------------------------------

describe('reference-config-generator: project type detection', () => {
  const tmpDir = resolve(__dirname, '../fixtures/posture-scan/tmp-type-test');

  it('detects plugin project', async () => {
    rmSync(tmpDir, { recursive: true, force: true });
    mkdirSync(join(tmpDir, '.claude-plugin'), { recursive: true });
    writeFileSync(join(tmpDir, '.claude-plugin', 'plugin.json'), '{"name":"test"}');
    resetCounter();
    const result = await generate(tmpDir);
    assert.equal(result.projectType, 'plugin');
    rmSync(tmpDir, { recursive: true, force: true });
  });

  it('detects monorepo', async () => {
    rmSync(tmpDir, { recursive: true, force: true });
    mkdirSync(tmpDir, { recursive: true });
    writeFileSync(join(tmpDir, 'package.json'), '{"workspaces":["packages/*"]}');
    resetCounter();
    const result = await generate(tmpDir);
    assert.equal(result.projectType, 'monorepo');
    rmSync(tmpDir, { recursive: true, force: true });
  });

  it('defaults to standalone', async () => {
    rmSync(tmpDir, { recursive: true, force: true });
    mkdirSync(tmpDir, { recursive: true });
    resetCounter();
    const result = await generate(tmpDir);
    assert.equal(result.projectType, 'standalone');
    rmSync(tmpDir, { recursive: true, force: true });
  });
});
