import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { resetCounter } from '../../scanners/lib/output.mjs';
import { discoverConfigFiles } from '../../scanners/lib/file-discovery.mjs';
import { scan } from '../../scanners/settings-validator.mjs';

const __dirname = fileURLToPath(new URL('.', import.meta.url));
const FIXTURES = resolve(__dirname, '../fixtures');

describe('SET scanner — healthy project', () => {
  let result;
  beforeEach(async () => {
    resetCounter();
    const discovery = await discoverConfigFiles(resolve(FIXTURES, 'healthy-project'));
    result = await scan(resolve(FIXTURES, 'healthy-project'), discovery);
  });

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

  it('has scanner prefix SET', () => {
    assert.strictEqual(result.scanner, 'SET');
  });

  it('finds no critical issues', () => {
    const critical = result.findings.filter(f => f.severity === 'critical');
    assert.strictEqual(critical.length, 0);
  });

  it('all finding IDs match CA-SET-NNN', () => {
    for (const f of result.findings) {
      assert.match(f.id, /^CA-SET-\d{3}$/);
    }
  });
});

describe('SET scanner — broken project', () => {
  let result;
  beforeEach(async () => {
    resetCounter();
    const discovery = await discoverConfigFiles(resolve(FIXTURES, 'broken-project'));
    result = await scan(resolve(FIXTURES, 'broken-project'), discovery);
  });

  it('detects unknown settings key', () => {
    // CA-SET-001 in broken-project, evidence='unknownKey123'.
    const found = result.findings.some(f => f.scanner === 'SET' && /unknownKey123/.test(f.evidence || ''));
    assert.ok(found, 'Should detect unknownKey123');
  });

  it('detects deprecated key (includeCoAuthoredBy)', () => {
    // CA-SET-002 in broken-project, evidence='includeCoAuthoredBy: true'.
    const found = result.findings.some(f => f.scanner === 'SET' && /includeCoAuthoredBy/.test(f.evidence || ''));
    assert.ok(found, 'Should detect includeCoAuthoredBy');
  });

  it('detects type mismatch (alwaysThinkingEnabled as string)', () => {
    // CA-SET-003 in broken-project, evidence='alwaysThinkingEnabled: "yes" (string)'.
    const found = result.findings.some(f => f.scanner === 'SET' && /alwaysThinkingEnabled/.test(f.evidence || ''));
    assert.ok(found, 'Should detect boolean/string mismatch');
  });

  it('detects invalid effortLevel value', () => {
    // CA-SET-004 in broken-project, evidence='effortLevel: "turbo"'.
    const found = result.findings.some(f => f.scanner === 'SET' && /effortLevel:\s*"turbo"/.test(f.evidence || ''));
    assert.ok(found, 'Should detect effortLevel "turbo"');
  });

  it('detects hooks as array', () => {
    // CA-SET-006 in broken-project, evidence='"hooks": [...]'.
    const found = result.findings.some(f => f.scanner === 'SET' && /"hooks":\s*\[/.test(f.evidence || ''));
    assert.ok(found, 'Should detect hooks array format');
  });

  it('marks hooks-as-array as critical', () => {
    const f = result.findings.find(x => x.scanner === 'SET' && /"hooks":\s*\[/.test(x.evidence || ''));
    assert.strictEqual(f?.severity, 'critical');
  });
});

describe('SET scanner — additionalDirectories (v5 M6)', () => {
  it('does NOT flag additionalDirectories as unknown key', async () => {
    resetCounter();
    const path = resolve(FIXTURES, 'additional-dirs-ok');
    const discovery = await discoverConfigFiles(path);
    const result = await scan(path, discovery);
    // SET findings preserve evidence verbatim; an unknown-key finding for additionalDirectories
    // would carry "additionalDirectories" in evidence regardless of humanizer rewriting the title.
    const unknown = result.findings.find(f =>
      f.scanner === 'SET' && /additionalDirectories/.test(f.evidence || ''));
    assert.equal(unknown, undefined,
      'additionalDirectories should be in KNOWN_KEYS');
  });

  it('does NOT flag 2 entries as too many', async () => {
    resetCounter();
    const path = resolve(FIXTURES, 'additional-dirs-ok');
    const discovery = await discoverConfigFiles(path);
    const result = await scan(path, discovery);
    // The additionalDirectories threshold finding writes paths into evidence (e.g., "~/work/repo-a", ...).
    // additional-dirs-ok is below threshold, so no SET finding fires at all.
    const f = result.findings.find(x => x.scanner === 'SET');
    assert.equal(f, undefined,
      `expected no SET findings for 2 entries, got id=${f?.id}`);
  });

  it('flags > 2 entries as low finding', async () => {
    resetCounter();
    const path = resolve(FIXTURES, 'additional-dirs-many');
    const discovery = await discoverConfigFiles(path);
    const result = await scan(path, discovery);
    // CA-SET-001 in additional-dirs-many = the additionalDirectories threshold finding.
    const f = result.findings.find(x => x.scanner === 'SET' && x.id === 'CA-SET-001');
    assert.ok(f, `expected additionalDirectories threshold finding; got: ${result.findings.map(x => x.id).join(' | ')}`);
    assert.equal(f.severity, 'low', `expected low severity, got ${f.severity}`);
  });
});

describe('SET scanner — empty project', () => {
  let result;
  beforeEach(async () => {
    resetCounter();
    const discovery = await discoverConfigFiles(resolve(FIXTURES, 'empty-project'));
    result = await scan(resolve(FIXTURES, 'empty-project'), discovery);
  });

  it('returns skipped when no settings files', () => {
    assert.strictEqual(result.status, 'skipped');
  });

  it('has 0 findings', () => {
    assert.strictEqual(result.findings.length, 0);
  });
});
