import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { finding, scannerResult, envelope, resetCounter } from '../../scanners/lib/output.mjs';

describe('resetCounter', () => {
  it('resets finding ID counter', () => {
    resetCounter();
    const f1 = finding({ scanner: 'TST', severity: 'info', title: 'a', description: 'b' });
    assert.strictEqual(f1.id, 'CA-TST-001');
    resetCounter();
    const f2 = finding({ scanner: 'TST', severity: 'info', title: 'c', description: 'd' });
    assert.strictEqual(f2.id, 'CA-TST-001');
  });
});

describe('finding', () => {
  beforeEach(() => { resetCounter(); });

  it('generates correct ID format', () => {
    const f = finding({ scanner: 'CML', severity: 'high', title: 't', description: 'd' });
    assert.match(f.id, /^CA-CML-\d{3}$/);
  });

  it('auto-increments IDs', () => {
    const f1 = finding({ scanner: 'CML', severity: 'info', title: 'a', description: 'b' });
    const f2 = finding({ scanner: 'CML', severity: 'info', title: 'c', description: 'd' });
    assert.strictEqual(f1.id, 'CA-CML-001');
    assert.strictEqual(f2.id, 'CA-CML-002');
  });

  it('includes all required fields', () => {
    const f = finding({
      scanner: 'SET',
      severity: 'critical',
      title: 'Test',
      description: 'Desc',
      file: '/foo.json',
      line: 10,
      evidence: 'x=1',
      category: 'Structure',
      recommendation: 'Fix it',
      autoFixable: true,
    });
    assert.strictEqual(f.scanner, 'SET');
    assert.strictEqual(f.severity, 'critical');
    assert.strictEqual(f.title, 'Test');
    assert.strictEqual(f.description, 'Desc');
    assert.strictEqual(f.file, '/foo.json');
    assert.strictEqual(f.line, 10);
    assert.strictEqual(f.evidence, 'x=1');
    assert.strictEqual(f.category, 'Structure');
    assert.strictEqual(f.recommendation, 'Fix it');
    assert.strictEqual(f.autoFixable, true);
  });

  it('defaults nullable fields to null', () => {
    const f = finding({ scanner: 'TST', severity: 'info', title: 't', description: 'd' });
    assert.strictEqual(f.file, null);
    assert.strictEqual(f.line, null);
    assert.strictEqual(f.evidence, null);
    assert.strictEqual(f.category, null);
    assert.strictEqual(f.recommendation, null);
    assert.strictEqual(f.autoFixable, false);
  });
});

describe('scannerResult', () => {
  beforeEach(() => { resetCounter(); });

  it('counts severity correctly', () => {
    const findings = [
      finding({ scanner: 'TST', severity: 'critical', title: 'a', description: 'b' }),
      finding({ scanner: 'TST', severity: 'high', title: 'c', description: 'd' }),
      finding({ scanner: 'TST', severity: 'high', title: 'e', description: 'f' }),
      finding({ scanner: 'TST', severity: 'info', title: 'g', description: 'h' }),
    ];
    const r = scannerResult('TST', 'ok', findings, 5, 100);
    assert.strictEqual(r.counts.critical, 1);
    assert.strictEqual(r.counts.high, 2);
    assert.strictEqual(r.counts.medium, 0);
    assert.strictEqual(r.counts.low, 0);
    assert.strictEqual(r.counts.info, 1);
  });

  it('includes error message when provided', () => {
    const r = scannerResult('TST', 'error', [], 0, 50, 'boom');
    assert.strictEqual(r.error, 'boom');
  });

  it('omits error when not provided', () => {
    const r = scannerResult('TST', 'ok', [], 3, 100);
    assert.strictEqual(r.error, undefined);
  });

  it('returns correct structure', () => {
    const r = scannerResult('CML', 'ok', [], 2, 42);
    assert.strictEqual(r.scanner, 'CML');
    assert.strictEqual(r.status, 'ok');
    assert.strictEqual(r.files_scanned, 2);
    assert.strictEqual(r.duration_ms, 42);
    assert.deepStrictEqual(r.findings, []);
  });
});

describe('envelope', () => {
  beforeEach(() => { resetCounter(); });

  it('aggregates across scanners', () => {
    const r1 = scannerResult('A', 'ok', [
      finding({ scanner: 'A', severity: 'high', title: 'x', description: 'y' }),
    ], 1, 10);
    resetCounter();
    const r2 = scannerResult('B', 'ok', [
      finding({ scanner: 'B', severity: 'critical', title: 'a', description: 'b' }),
      finding({ scanner: 'B', severity: 'low', title: 'c', description: 'd' }),
    ], 2, 20);

    const env = envelope('/target', [r1, r2], 50);
    assert.strictEqual(env.aggregate.total_findings, 3);
    assert.strictEqual(env.aggregate.counts.critical, 1);
    assert.strictEqual(env.aggregate.counts.high, 1);
    assert.strictEqual(env.aggregate.counts.low, 1);
    assert.strictEqual(env.aggregate.scanners_ok, 2);
  });

  it('counts scanner statuses', () => {
    const r1 = scannerResult('A', 'ok', [], 1, 10);
    const r2 = scannerResult('B', 'skipped', [], 0, 5);
    const r3 = scannerResult('C', 'error', [], 0, 3, 'fail');

    const env = envelope('/t', [r1, r2, r3], 30);
    assert.strictEqual(env.aggregate.scanners_ok, 1);
    assert.strictEqual(env.aggregate.scanners_skipped, 1);
    assert.strictEqual(env.aggregate.scanners_error, 1);
  });

  it('includes meta with version and tool', () => {
    const env = envelope('/t', [], 0);
    assert.strictEqual(env.meta.version, '2.2.0');
    assert.strictEqual(env.meta.tool, 'config-audit');
    assert.strictEqual(env.meta.target, '/t');
    assert.ok(env.meta.timestamp);
  });

  it('calculates verdict correctly', () => {
    const env = envelope('/t', [], 0);
    assert.strictEqual(env.aggregate.verdict, 'PASS');
  });
});
