/**
 * SuppressionManager Tests
 */

import { SuppressionManager } from '../manager'
import type { SuppressionConfig } from '../types'
import type { Vulnerability, ScanFile } from '../../types'

describe('SuppressionManager', () => {
  // Helper to create a mock vulnerability
  function createVulnerability(overrides: Partial<Vulnerability> = {}): Vulnerability {
    return {
      id: 'test-1',
      filePath: 'src/index.ts',
      lineNumber: 10,
      lineContent: 'const secret = "api_key_12345"',
      severity: 'high',
      category: 'hardcoded_secret',
      title: 'Hardcoded Secret',
      description: 'Found a hardcoded secret',
      confidence: 'high',
      layer: 1,
      ...overrides,
    }
  }

  // Helper to create a mock scan file
  function createScanFile(overrides: Partial<ScanFile> = {}): ScanFile {
    return {
      path: 'src/index.ts',
      content: 'const secret = "api_key_12345";',
      language: 'typescript',
      size: 100,
      ...overrides,
    }
  }

  describe('constructor', () => {
    it('should accept a pre-loaded config', () => {
      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          rules: [{
            category: 'hardcoded_secret',
            reason: 'test',
          }],
        },
      }

      const manager = new SuppressionManager({
        projectPath: '/fake/path',
        config,
      })

      expect(manager.hasSuppressions()).toBe(true)
      expect(manager.getAllSuppressions().rules).toHaveLength(1)
    })

    it('should return default config when path has no config file', () => {
      const manager = new SuppressionManager({
        projectPath: '/nonexistent/path',
      })

      expect(manager.hasSuppressions()).toBe(false)
      expect(manager.getConfigPath()).toBeUndefined()
    })
  })

  describe('isPathIgnored', () => {
    it('should match simple patterns', () => {
      const config: SuppressionConfig = {
        version: 1,
        ignore: ['**/*.test.ts', 'node_modules/**'],
      }

      const manager = new SuppressionManager({
        projectPath: '/fake',
        config,
      })

      expect(manager.isPathIgnored('src/utils.test.ts')).toBe(true)
      expect(manager.isPathIgnored('node_modules/lodash/index.js')).toBe(true)
      expect(manager.isPathIgnored('src/index.ts')).toBe(false)
    })

    it('should return false when no ignore patterns', () => {
      const manager = new SuppressionManager({
        projectPath: '/fake',
        config: { version: 1 },
      })

      expect(manager.isPathIgnored('anything.ts')).toBe(false)
    })
  })

  describe('isFindingSuppressed', () => {
    it('should suppress by inline comment', () => {
      const config: SuppressionConfig = { version: 1 }
      const manager = new SuppressionManager({ projectPath: '/fake', config })

      // Line 4 should be suppressed (line 3 has the comment)
      const finding = createVulnerability({ lineNumber: 4 })
      const fileContent = `const x = 1;
const y = 2;
// oculum-ignore-next-line: false positive
const secret = "api_key_12345";
`

      const result = manager.isFindingSuppressed(finding, fileContent)

      expect(result.suppressed).toBe(true)
      expect(result.match?.type).toBe('inline')
      expect(result.match?.reason).toBe('false positive')
    })

    it('should suppress by config finding hash', () => {
      const finding = createVulnerability()
      // First get the hash
      const tempManager = new SuppressionManager({
        projectPath: '/fake',
        config: { version: 1 },
      })
      const { hash } = tempManager.isFindingSuppressed(finding)

      // Now create a manager with that hash suppressed
      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          findings: [{
            hash,
            file: 'src/index.ts',
            reason: 'suppressed by hash',
          }],
        },
      }

      const manager = new SuppressionManager({ projectPath: '/fake', config })
      const result = manager.isFindingSuppressed(finding)

      expect(result.suppressed).toBe(true)
      expect(result.match?.type).toBe('config-finding')
      expect(result.match?.reason).toBe('suppressed by hash')
    })

    it('should suppress by config rule', () => {
      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          rules: [{
            category: 'hardcoded_secret',
            reason: 'all hardcoded secrets suppressed',
          }],
        },
      }

      const manager = new SuppressionManager({ projectPath: '/fake', config })
      const finding = createVulnerability()

      const result = manager.isFindingSuppressed(finding)

      expect(result.suppressed).toBe(true)
      expect(result.match?.type).toBe('config-rule')
      expect(result.match?.reason).toBe('all hardcoded secrets suppressed')
    })

    it('should respect rule path restrictions', () => {
      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          rules: [{
            category: 'hardcoded_secret',
            reason: 'only in tests',
            paths: ['**/*.test.ts'],
          }],
        },
      }

      const manager = new SuppressionManager({ projectPath: '/fake', config })

      const testFinding = createVulnerability({ filePath: 'src/utils.test.ts' })
      const srcFinding = createVulnerability({ filePath: 'src/index.ts' })

      expect(manager.isFindingSuppressed(testFinding).suppressed).toBe(true)
      expect(manager.isFindingSuppressed(srcFinding).suppressed).toBe(false)
    })

    it('should handle expired suppressions', () => {
      const finding = createVulnerability()
      const tempManager = new SuppressionManager({
        projectPath: '/fake',
        config: { version: 1 },
      })
      const { hash } = tempManager.isFindingSuppressed(finding)

      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          findings: [{
            hash,
            file: 'src/index.ts',
            reason: 'expired suppression',
            expires: '2020-01-01', // Already expired
          }],
        },
      }

      const manager = new SuppressionManager({ projectPath: '/fake', config })
      const result = manager.isFindingSuppressed(finding)

      expect(result.suppressed).toBe(false)
      expect(result.match?.expired).toBe(true)
    })

    it('should prioritize inline over config', () => {
      // Line 4 is the secret line, line 3 has the comment
      const finding = createVulnerability({ lineNumber: 4 })
      const tempManager = new SuppressionManager({
        projectPath: '/fake',
        config: { version: 1 },
      })
      const { hash } = tempManager.isFindingSuppressed(finding)

      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          findings: [{
            hash,
            file: 'src/index.ts',
            reason: 'config reason',
          }],
        },
      }

      const manager = new SuppressionManager({ projectPath: '/fake', config })
      const fileContent = `const x = 1;
const y = 2;
// oculum-ignore-next-line: inline reason
const secret = "api_key_12345";
`

      const result = manager.isFindingSuppressed(finding, fileContent)

      // Should be suppressed by inline (higher priority)
      expect(result.suppressed).toBe(true)
      expect(result.match?.type).toBe('inline')
      expect(result.match?.reason).toBe('inline reason')
    })
  })

  describe('applySuppressions', () => {
    it('should filter suppressed findings', () => {
      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          rules: [{
            category: 'hardcoded_secret',
            reason: 'suppress all secrets',
          }],
        },
      }

      const manager = new SuppressionManager({ projectPath: '/fake', config })

      const findings = [
        createVulnerability({ id: '1', category: 'hardcoded_secret' }),
        createVulnerability({ id: '2', category: 'high_entropy_string' }),
        createVulnerability({ id: '3', category: 'hardcoded_secret' }),
      ]

      const files = [createScanFile()]

      const result = manager.applySuppressions(findings, files)

      expect(result.findings).toHaveLength(1)
      expect(result.findings[0].category).toBe('high_entropy_string')

      expect(result.suppressed).toHaveLength(2)
      expect(result.stats.configRuleSuppressed).toBe(2)
    })

    it('should include suppression details in result', () => {
      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          rules: [{
            category: 'hardcoded_secret',
            reason: 'test reason',
          }],
        },
      }

      const manager = new SuppressionManager({ projectPath: '/fake', config })
      const finding = createVulnerability()
      const files = [createScanFile()]

      const result = manager.applySuppressions([finding], files)

      expect(result.suppressed).toHaveLength(1)
      expect(result.suppressed[0].suppression.reason).toBe('test reason')
      expect(result.suppressed[0].suppression.type).toBe('config-rule')
      expect(result.suppressed[0].vulnerability.filePath).toBe(finding.filePath)
    })

    it('should track expired suppressions count', () => {
      const finding = createVulnerability()
      const tempManager = new SuppressionManager({
        projectPath: '/fake',
        config: { version: 1 },
      })
      const { hash } = tempManager.isFindingSuppressed(finding)

      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          findings: [{
            hash,
            file: 'src/index.ts',
            reason: 'expired',
            expires: '2020-01-01',
          }],
        },
      }

      const manager = new SuppressionManager({ projectPath: '/fake', config })
      const files = [createScanFile()]

      const result = manager.applySuppressions([finding], files)

      expect(result.findings).toHaveLength(1) // Not suppressed because expired
      expect(result.expiredSuppressions).toBe(1)
      expect(result.stats.expired).toBe(1)
    })

    it('should correctly count by suppression type', () => {
      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          rules: [{
            category: 'hardcoded_secret',
            reason: 'rule suppression',
          }],
        },
      }

      const manager = new SuppressionManager({ projectPath: '/fake', config })

      // Create a finding that will be suppressed by inline (line 4 - after the comment on line 3)
      const inlineFinding = createVulnerability({
        id: '1',
        category: 'high_entropy_string',
        lineNumber: 4,
        lineContent: 'const entropy = "abcdef123456";',
      })

      // Create a finding that will be suppressed by rule (line 5)
      const ruleFinding = createVulnerability({
        id: '2',
        category: 'hardcoded_secret',
        lineNumber: 5,
        lineContent: 'const secret = "api_key";',
      })

      // Create a finding that won't be suppressed (line 6)
      const passedFinding = createVulnerability({
        id: '3',
        category: 'sql_injection',
        lineNumber: 6,
        lineContent: 'const query = "SELECT * FROM users";',
      })

      const findings = [inlineFinding, ruleFinding, passedFinding]

      const fileContent = `const x = 1;
const y = 2;
// oculum-ignore-next-line: inline reason
const entropy = "abcdef123456";
const secret = "api_key";
const query = "SELECT * FROM users";
`
      const files = [createScanFile({ content: fileContent })]

      const result = manager.applySuppressions(findings, files)

      expect(result.findings).toHaveLength(1)
      expect(result.stats.inlineSuppressed).toBe(1)
      expect(result.stats.configRuleSuppressed).toBe(1)
    })
  })

  describe('getSummary', () => {
    it('should return accurate summary', () => {
      const config: SuppressionConfig = {
        version: 1,
        suppressions: {
          rules: [
            { category: 'hardcoded_secret', reason: 'r1' },
            { category: 'high_entropy_string', reason: 'r2' },
          ],
          findings: [
            { hash: 'abc123def4567890', file: 'a.ts', reason: 'f1' },
          ],
        },
        ignore: ['*.test.ts', '*.spec.ts'],
      }

      const manager = new SuppressionManager({ projectPath: '/fake', config })
      const summary = manager.getSummary()

      expect(summary.ruleCount).toBe(2)
      expect(summary.findingCount).toBe(1)
      expect(summary.ignorePatternCount).toBe(2)
      expect(summary.hasErrors).toBe(false)
    })
  })
})
