import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { join } from 'node:path';
import { writeFile, mkdir, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import {
  loadSuppressions,
  parseIgnoreFile,
  applySuppressions,
  formatSuppressionSummary,
} from '../../scanners/lib/suppression.mjs';

// --- Helpers ---

function makeFinding(id, scanner, severity = 'medium') {
  return {
    id,
    scanner,
    severity,
    title: `Finding ${id}`,
    description: `Description for ${id}`,
    file: null,
    line: null,
    evidence: null,
    category: null,
    recommendation: null,
    autoFixable: false,
  };
}

// ========================================
// parseIgnoreFile
// ========================================
describe('parseIgnoreFile', () => {
  it('parses exact finding IDs', () => {
    const result = parseIgnoreFile('CA-CML-001\nCA-SET-003');
    assert.equal(result.length, 2);
    assert.equal(result[0].pattern, 'CA-CML-001');
    assert.equal(result[1].pattern, 'CA-SET-003');
  });

  it('parses glob patterns', () => {
    const result = parseIgnoreFile('CA-GAP-*');
    assert.equal(result.length, 1);
    assert.equal(result[0].pattern, 'CA-GAP-*');
  });

  it('skips comments and empty lines', () => {
    const content = `# This is a comment
CA-CML-001

# Another comment

CA-SET-002`;
    const result = parseIgnoreFile(content);
    assert.equal(result.length, 2);
  });

  it('extracts inline comments', () => {
    const result = parseIgnoreFile('CA-HKV-003  # Known timeout in CI');
    assert.equal(result.length, 1);
    assert.equal(result[0].pattern, 'CA-HKV-003');
    assert.equal(result[0].comment, 'Known timeout in CI');
  });

  it('returns empty array for empty content', () => {
    const result = parseIgnoreFile('');
    assert.deepEqual(result, []);
  });

  it('returns empty array for comment-only content', () => {
    const result = parseIgnoreFile('# Just comments\n# Nothing else');
    assert.deepEqual(result, []);
  });
});

// ========================================
// applySuppressions
// ========================================
describe('applySuppressions', () => {
  it('filters exact match', () => {
    const findings = [
      makeFinding('CA-CML-001', 'CML'),
      makeFinding('CA-CML-002', 'CML'),
    ];
    const suppressions = [{ pattern: 'CA-CML-001', comment: '' }];
    const { active, suppressed } = applySuppressions(findings, suppressions);

    assert.equal(active.length, 1);
    assert.equal(active[0].id, 'CA-CML-002');
    assert.equal(suppressed.length, 1);
    assert.equal(suppressed[0].id, 'CA-CML-001');
  });

  it('filters glob pattern CA-SET-*', () => {
    const findings = [
      makeFinding('CA-SET-001', 'SET'),
      makeFinding('CA-SET-002', 'SET'),
      makeFinding('CA-CML-001', 'CML'),
    ];
    const suppressions = [{ pattern: 'CA-SET-*', comment: '' }];
    const { active, suppressed } = applySuppressions(findings, suppressions);

    assert.equal(active.length, 1);
    assert.equal(active[0].id, 'CA-CML-001');
    assert.equal(suppressed.length, 2);
  });

  it('returns all active when no suppressions', () => {
    const findings = [makeFinding('CA-CML-001', 'CML')];
    const { active, suppressed } = applySuppressions(findings, []);

    assert.equal(active.length, 1);
    assert.equal(suppressed.length, 0);
  });

  it('returns all active when suppressions is null', () => {
    const findings = [makeFinding('CA-CML-001', 'CML')];
    const { active, suppressed } = applySuppressions(findings, null);

    assert.equal(active.length, 1);
    assert.equal(suppressed.length, 0);
  });

  it('handles empty findings list', () => {
    const suppressions = [{ pattern: 'CA-CML-*', comment: '' }];
    const { active, suppressed } = applySuppressions([], suppressions);

    assert.equal(active.length, 0);
    assert.equal(suppressed.length, 0);
  });

  it('applies multiple suppression patterns', () => {
    const findings = [
      makeFinding('CA-CML-001', 'CML'),
      makeFinding('CA-SET-001', 'SET'),
      makeFinding('CA-GAP-001', 'GAP'),
    ];
    const suppressions = [
      { pattern: 'CA-CML-001', comment: '' },
      { pattern: 'CA-GAP-*', comment: '' },
    ];
    const { active, suppressed } = applySuppressions(findings, suppressions);

    assert.equal(active.length, 1);
    assert.equal(active[0].id, 'CA-SET-001');
    assert.equal(suppressed.length, 2);
  });
});

// ========================================
// formatSuppressionSummary
// ========================================
describe('formatSuppressionSummary', () => {
  it('formats correct count and groups', () => {
    const suppressed = [
      makeFinding('CA-GAP-001', 'GAP'),
      makeFinding('CA-GAP-002', 'GAP'),
      makeFinding('CA-HKV-003', 'HKV'),
    ];
    const summary = formatSuppressionSummary(suppressed);

    assert.ok(summary.includes('3 finding(s) suppressed'));
    assert.ok(summary.includes('CA-GAP-*'));
    assert.ok(summary.includes('CA-HKV-*'));
  });

  it('returns zero message for empty array', () => {
    assert.equal(formatSuppressionSummary([]), '0 findings suppressed');
  });

  it('returns zero message for null', () => {
    assert.equal(formatSuppressionSummary(null), '0 findings suppressed');
  });
});

// ========================================
// loadSuppressions
// ========================================
describe('loadSuppressions', () => {
  const tmpDir = join(tmpdir(), `config-audit-suppress-test-${Date.now()}`);

  it('returns empty when no .config-audit-ignore exists', async () => {
    const result = await loadSuppressions('/nonexistent/path');
    assert.deepEqual(result.suppressions, []);
    assert.equal(result.source, 'none');
  });

  it('loads from project directory', async () => {
    await mkdir(tmpDir, { recursive: true });
    await writeFile(join(tmpDir, '.config-audit-ignore'), 'CA-GAP-*\nCA-CML-001\n');

    const result = await loadSuppressions(tmpDir);
    assert.equal(result.suppressions.length, 2);
    assert.equal(result.source, 'project');

    await rm(tmpDir, { recursive: true, force: true });
  });
});
