/**
 * Baseline Diff Computation Tests
 */

import { computeDiff, hasNewBlockingIssues, formatDiffSummary } from '../diff'
import type { Vulnerability } from '../../types'
import type { BaselineData } from '../types'

describe('Baseline Diff Computation', () => {
  // Helper to create a test vulnerability
  const createVuln = (
    filePath: string,
    lineNumber: number,
    category: string,
    severity: 'critical' | 'high' | 'medium' | 'low' | 'info' = 'medium',
    title: string = 'Test finding'
  ): Vulnerability => ({
    filePath,
    lineNumber,
    category: category as any,
    severity,
    title,
    description: 'Test description',
    confidence: 'high',
    layer: 1,
    lineContent: `Line ${lineNumber} content`,
  })

  // Helper to create a baseline
  const createBaseline = (findings: Array<{
    hash: string
    filePath: string
    lineNumber: number
    category: string
    severity: string
    title: string
  }>): BaselineData => ({
    version: 1,
    createdAt: '2024-01-15T10:00:00.000Z',
    findings: findings as any,
    stats: {
      total: findings.length,
      critical: findings.filter(f => f.severity === 'critical').length,
      high: findings.filter(f => f.severity === 'high').length,
      medium: findings.filter(f => f.severity === 'medium').length,
      low: findings.filter(f => f.severity === 'low').length,
      info: findings.filter(f => f.severity === 'info').length,
    },
  })

  describe('computeDiff', () => {
    it('should identify all findings as new when baseline is empty', () => {
      const current = [
        createVuln('src/a.ts', 10, 'hardcoded_secret', 'critical'),
        createVuln('src/b.ts', 20, 'sql_injection', 'high'),
      ]
      const baseline = createBaseline([])

      const diff = computeDiff(current, baseline)

      expect(diff.new).toHaveLength(2)
      expect(diff.fixed).toHaveLength(0)
      expect(diff.existing).toHaveLength(0)
      expect(diff.stats.newCount).toBe(2)
      expect(diff.stats.fixedCount).toBe(0)
      expect(diff.stats.existingCount).toBe(0)
    })

    it('should identify all findings as existing when current matches baseline', () => {
      const current = [
        createVuln('src/a.ts', 10, 'hardcoded_secret', 'critical', 'API key'),
      ]

      // Need to compute the hash that would match
      const { computeFindingHash } = require('../../../postprocess/suppression/hash')
      const hash = computeFindingHash(current[0])

      const baseline = createBaseline([
        {
          hash,
          filePath: 'src/a.ts',
          lineNumber: 10,
          category: 'hardcoded_secret',
          severity: 'critical',
          title: 'API key',
        },
      ])

      const diff = computeDiff(current, baseline)

      expect(diff.new).toHaveLength(0)
      expect(diff.fixed).toHaveLength(0)
      expect(diff.existing).toHaveLength(1)
      expect(diff.stats.existingCount).toBe(1)
    })

    it('should identify fixed findings when they exist in baseline but not current', () => {
      const current: Vulnerability[] = []
      const baseline = createBaseline([
        {
          hash: 'fixedhash1234567',
          filePath: 'src/fixed.ts',
          lineNumber: 5,
          category: 'hardcoded_secret',
          severity: 'high',
          title: 'Fixed finding',
        },
      ])

      const diff = computeDiff(current, baseline)

      expect(diff.new).toHaveLength(0)
      expect(diff.fixed).toHaveLength(1)
      expect(diff.fixed[0].title).toBe('Fixed finding')
      expect(diff.existing).toHaveLength(0)
      expect(diff.stats.fixedCount).toBe(1)
    })

    it('should handle mixed scenario with new, fixed, and existing findings', () => {
      // Create a finding that will be in both current and baseline
      const existingVuln = createVuln('src/existing.ts', 10, 'xss', 'medium', 'Existing XSS')
      const { computeFindingHash } = require('../../../postprocess/suppression/hash')
      const existingHash = computeFindingHash(existingVuln)

      // Current: one existing + one new
      const current = [
        existingVuln,
        createVuln('src/new.ts', 20, 'sql_injection', 'high', 'New SQL injection'),
      ]

      // Baseline: one existing + one fixed
      const baseline = createBaseline([
        {
          hash: existingHash,
          filePath: 'src/existing.ts',
          lineNumber: 10,
          category: 'xss',
          severity: 'medium',
          title: 'Existing XSS',
        },
        {
          hash: 'fixedhash9999999',
          filePath: 'src/old.ts',
          lineNumber: 30,
          category: 'hardcoded_secret',
          severity: 'critical',
          title: 'Fixed secret',
        },
      ])

      const diff = computeDiff(current, baseline)

      expect(diff.new).toHaveLength(1)
      expect(diff.new[0].title).toBe('New SQL injection')
      expect(diff.fixed).toHaveLength(1)
      expect(diff.fixed[0].title).toBe('Fixed secret')
      expect(diff.existing).toHaveLength(1)
      expect(diff.existing[0].title).toBe('Existing XSS')

      expect(diff.stats.newCount).toBe(1)
      expect(diff.stats.fixedCount).toBe(1)
      expect(diff.stats.existingCount).toBe(1)
    })

    it('should compute severity counts correctly', () => {
      const current = [
        createVuln('src/a.ts', 1, 'sql_injection', 'critical', 'Crit 1'),
        createVuln('src/b.ts', 2, 'xss', 'high', 'High 1'),
        createVuln('src/c.ts', 3, 'xss', 'high', 'High 2'),
        createVuln('src/d.ts', 4, 'data_exposure', 'medium', 'Med 1'),
      ]
      const baseline = createBaseline([])

      const diff = computeDiff(current, baseline)

      expect(diff.stats.newBySeverity.critical).toBe(1)
      expect(diff.stats.newBySeverity.high).toBe(2)
      expect(diff.stats.newBySeverity.medium).toBe(1)
      expect(diff.stats.newBySeverity.low).toBe(0)
      expect(diff.stats.newBySeverity.info).toBe(0)
    })
  })

  describe('hasNewBlockingIssues', () => {
    it('should return true when there are new critical findings', () => {
      const diff = computeDiff(
        [createVuln('src/a.ts', 1, 'hardcoded_secret', 'critical')],
        createBaseline([])
      )

      expect(hasNewBlockingIssues(diff)).toBe(true)
    })

    it('should return true when there are new high findings', () => {
      const diff = computeDiff(
        [createVuln('src/a.ts', 1, 'sql_injection', 'high')],
        createBaseline([])
      )

      expect(hasNewBlockingIssues(diff)).toBe(true)
    })

    it('should return false when there are only new medium/low/info findings', () => {
      const diff = computeDiff(
        [
          createVuln('src/a.ts', 1, 'xss', 'medium'),
          createVuln('src/b.ts', 2, 'data_exposure', 'low'),
          createVuln('src/c.ts', 3, 'ai_pattern', 'info'),
        ],
        createBaseline([])
      )

      expect(hasNewBlockingIssues(diff)).toBe(false)
    })

    it('should return false when there are no new findings', () => {
      const diff = computeDiff([], createBaseline([]))

      expect(hasNewBlockingIssues(diff)).toBe(false)
    })
  })

  describe('formatDiffSummary', () => {
    it('should format a summary with all categories', () => {
      const diff = computeDiff(
        [
          createVuln('src/a.ts', 1, 'hardcoded_secret', 'critical'),
          createVuln('src/b.ts', 2, 'xss', 'high'),
        ],
        createBaseline([
          {
            hash: 'fixedhash1234567',
            filePath: 'src/fixed.ts',
            lineNumber: 5,
            category: 'sql_injection',
            severity: 'high',
            title: 'Fixed finding',
          },
        ])
      )

      const summary = formatDiffSummary(diff)

      expect(summary).toContain('2 new')
      expect(summary).toContain('1 fixed')
    })

    it('should omit zero counts', () => {
      const diff = computeDiff(
        [createVuln('src/a.ts', 1, 'hardcoded_secret', 'critical')],
        createBaseline([])
      )

      const summary = formatDiffSummary(diff)

      expect(summary).toContain('1 new')
      expect(summary).not.toContain('fixed')
      expect(summary).not.toContain('existing')
    })
  })
})
