import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { resolve, dirname, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import { isFixturePath, FIXTURE_DIR_NAMES, runAllScanners } from '../../scanners/scan-orchestrator.mjs';

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

// ========================================
// isFixturePath
// ========================================
describe('isFixturePath', () => {
  const target = '/repo';

  it('matches tests/ subdirectory relative to target', () => {
    assert.strictEqual(isFixturePath({ file: '/repo/tests/fixtures/CLAUDE.md' }, target), true);
  });

  it('matches examples/ subdirectory relative to target', () => {
    assert.strictEqual(isFixturePath({ file: '/repo/examples/demo/settings.json' }, target), true);
  });

  it('matches __tests__/ subdirectory', () => {
    assert.strictEqual(isFixturePath({ file: '/repo/__tests__/config/CLAUDE.md' }, target), true);
  });

  it('does not match production paths', () => {
    assert.strictEqual(isFixturePath({ file: '/repo/CLAUDE.md' }, target), false);
    assert.strictEqual(isFixturePath({ file: '/repo/plugins/config-audit/CLAUDE.md' }, target), false);
  });

  it('does not filter when target IS a fixture directory', () => {
    // If we're scanning tests/fixtures/broken-project directly, its files should NOT be filtered
    const fixtureTarget = '/repo/tests/fixtures/broken-project';
    assert.strictEqual(
      isFixturePath({ file: '/repo/tests/fixtures/broken-project/CLAUDE.md' }, fixtureTarget),
      false,
      'Files at the root of the scanned target should not be filtered'
    );
  });

  it('falls back to path field', () => {
    assert.strictEqual(isFixturePath({ path: '/repo/tests/broken/hooks.json' }, target), true);
  });

  it('falls back to location field', () => {
    assert.strictEqual(isFixturePath({ location: '/repo/examples/bad.md' }, target), true);
  });

  it('returns false when file is null (GAP findings)', () => {
    assert.strictEqual(isFixturePath({ file: null }, target), false);
  });

  it('returns false for empty finding (no file/path/location)', () => {
    assert.strictEqual(isFixturePath({}, target), false);
  });

  it('returns false when file is outside target path', () => {
    assert.strictEqual(isFixturePath({ file: '/other/tests/foo.md' }, target), false);
  });

  it('uses platform-native separator (path.sep)', () => {
    // On macOS/Linux sep='/', on Windows sep='\\'
    // This test verifies the function works with the native separator
    const nativePath = `${target}${sep}tests${sep}fixtures${sep}CLAUDE.md`;
    assert.strictEqual(isFixturePath({ file: nativePath }, target), true);
  });
});

// ========================================
// FIXTURE_DIR_NAMES
// ========================================
describe('FIXTURE_DIR_NAMES', () => {
  it('contains expected directory names', () => {
    assert.ok(FIXTURE_DIR_NAMES.includes('tests'));
    assert.ok(FIXTURE_DIR_NAMES.includes('examples'));
    assert.ok(FIXTURE_DIR_NAMES.includes('__tests__'));
  });
});

// ========================================
// runAllScanners — fixture filtering
// ========================================
describe('runAllScanners — fixture filtering', () => {
  it('excludes fixture findings by default when scanning plugin root', async () => {
    const env = await runAllScanners(PLUGIN_ROOT);
    // The plugin has test fixtures in tests/fixtures/ — those should be filtered out
    const allFindingFiles = env.scanners.flatMap(s => s.findings.map(f => f.file)).filter(Boolean);
    const fixtureInResults = allFindingFiles.filter(f => f.includes('/tests/'));
    assert.strictEqual(fixtureInResults.length, 0, 'No fixture findings should appear in scanner results');
  });

  it('stores excluded findings in env.fixture_findings', async () => {
    const env = await runAllScanners(PLUGIN_ROOT);
    // Plugin has intentionally broken fixtures — at least some findings should be excluded
    if (env.fixture_findings) {
      assert.ok(Array.isArray(env.fixture_findings));
      assert.ok(env.fixture_findings.length > 0, 'Expected fixture findings to be captured');
      // All fixture findings should have test/example paths
      for (const f of env.fixture_findings) {
        const p = f.file || f.path || f.location || '';
        assert.ok(
          p.includes('/tests/') || p.includes('/examples/'),
          `Fixture finding path should contain /tests/ or /examples/: ${p}`
        );
      }
    }
    // Note: if no fixture findings exist (unlikely but possible), test still passes
  });

  it('includes fixture findings when filterFixtures is false', async () => {
    const env = await runAllScanners(PLUGIN_ROOT, { filterFixtures: false });
    assert.strictEqual(env.fixture_findings, undefined, 'No fixture_findings field when filtering disabled');
    // Some findings should have test fixture paths
    const allFindingFiles = env.scanners.flatMap(s => s.findings.map(f => f.file)).filter(Boolean);
    const fixtureInResults = allFindingFiles.filter(f => f.includes('/tests/'));
    assert.ok(fixtureInResults.length > 0, 'Fixture findings should be present when filtering disabled');
  });

  it('does not filter GAP findings (file is null)', async () => {
    const env = await runAllScanners(PLUGIN_ROOT);
    const gapScanner = env.scanners.find(s => s.scanner === 'GAP');
    assert.ok(gapScanner, 'GAP scanner should be present');
    // GAP findings have file: null — they should never be filtered
    for (const f of gapScanner.findings) {
      assert.strictEqual(f.file, null, 'GAP findings have null file and should not be filtered');
    }
  });

  it('recalculates scanner counts after fixture filtering', async () => {
    const env = await runAllScanners(PLUGIN_ROOT);
    for (const scanner of env.scanners) {
      // Verify counts match actual findings
      const expected = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
      for (const f of scanner.findings) {
        if (expected[f.severity] !== undefined) expected[f.severity]++;
      }
      assert.deepStrictEqual(scanner.counts, expected,
        `Scanner ${scanner.scanner}: counts should match actual findings after filtering`);
    }
  });

  it('total_findings in aggregate excludes fixtures', async () => {
    const withFilter = await runAllScanners(PLUGIN_ROOT, { filterFixtures: true });
    const withoutFilter = await runAllScanners(PLUGIN_ROOT, { filterFixtures: false });
    // With filter should have fewer or equal findings
    assert.ok(
      withFilter.aggregate.total_findings <= withoutFilter.aggregate.total_findings,
      `Filtered total (${withFilter.aggregate.total_findings}) should be <= unfiltered (${withoutFilter.aggregate.total_findings})`
    );
  });

  it('fixture filtering and suppression are independent', async () => {
    // Both enabled (default)
    const both = await runAllScanners(PLUGIN_ROOT, { filterFixtures: true, suppress: true });
    // Only fixtures
    const fixturesOnly = await runAllScanners(PLUGIN_ROOT, { filterFixtures: true, suppress: false });
    // Only suppression
    const suppressOnly = await runAllScanners(PLUGIN_ROOT, { filterFixtures: false, suppress: true });

    // fixture_findings should be present in both fixture-filtered runs
    if (both.fixture_findings) {
      assert.ok(fixturesOnly.fixture_findings, 'fixture_findings should be present regardless of suppress flag');
    }
    // suppressed_findings should be present in both suppression-enabled runs (if any suppressions exist)
    if (both.suppressed_findings) {
      assert.ok(suppressOnly.suppressed_findings, 'suppressed_findings should be present regardless of filterFixtures flag');
    }
  });
});
