/**
 * Baseline Diff Computation
 * Computes the difference between current findings and a baseline
 */

import type { Vulnerability, SeverityCounts, VulnerabilitySeverity } from '../types'
import type { BaselineData, BaselineFinding, DiffResult } from './types'
import { computeFindingHash } from '../../postprocess/suppression/hash'

/**
 * Compute severity counts from baseline findings
 */
function computeBaselineSeverityCounts(findings: BaselineFinding[]): SeverityCounts {
  const counts: SeverityCounts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 }

  for (const finding of findings) {
    const severity = finding.severity as VulnerabilitySeverity
    if (severity in counts) {
      counts[severity]++
    }
  }

  return counts
}

/**
 * Compute severity counts from vulnerabilities
 */
function computeVulnerabilitySeverityCounts(vulnerabilities: Vulnerability[]): SeverityCounts {
  const counts: SeverityCounts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 }

  for (const vuln of vulnerabilities) {
    if (vuln.severity in counts) {
      counts[vuln.severity]++
    }
  }

  return counts
}

/**
 * Compute the diff between current scan findings and a baseline
 *
 * Uses finding hashes for comparison, which are computed from:
 * - Normalized file path
 * - Normalized line content
 * - Category
 *
 * This means findings are considered the same even if:
 * - Line numbers changed (code moved)
 * - Minor whitespace changes occurred
 *
 * @param currentFindings - Vulnerabilities from the current scan
 * @param baseline - The baseline to compare against
 * @returns DiffResult with new, fixed, and existing findings
 */
export function computeDiff(
  currentFindings: Vulnerability[],
  baseline: BaselineData
): DiffResult {
  // Build hash set from baseline for O(1) lookup
  const baselineHashes = new Set(baseline.findings.map(f => f.hash))

  // Build hash map from current findings
  const currentHashMap = new Map<string, Vulnerability>()
  for (const finding of currentFindings) {
    const hash = computeFindingHash(finding)
    currentHashMap.set(hash, finding)
  }

  // Compute new findings (in current, not in baseline)
  const newFindings: Vulnerability[] = []
  for (const finding of currentFindings) {
    const hash = computeFindingHash(finding)
    if (!baselineHashes.has(hash)) {
      newFindings.push(finding)
    }
  }

  // Compute fixed findings (in baseline, not in current)
  const fixedFindings: BaselineFinding[] = []
  for (const baselineFinding of baseline.findings) {
    if (!currentHashMap.has(baselineFinding.hash)) {
      fixedFindings.push(baselineFinding)
    }
  }

  // Compute existing findings (in both)
  const existingFindings: Vulnerability[] = []
  for (const finding of currentFindings) {
    const hash = computeFindingHash(finding)
    if (baselineHashes.has(hash)) {
      existingFindings.push(finding)
    }
  }

  return {
    new: newFindings,
    fixed: fixedFindings,
    existing: existingFindings,
    stats: {
      newCount: newFindings.length,
      fixedCount: fixedFindings.length,
      existingCount: existingFindings.length,
      newBySeverity: computeVulnerabilitySeverityCounts(newFindings),
      fixedBySeverity: computeBaselineSeverityCounts(fixedFindings),
    },
  }
}

/**
 * Check if a diff has any new blocking issues (critical or high severity)
 */
export function hasNewBlockingIssues(diff: DiffResult): boolean {
  return diff.stats.newBySeverity.critical > 0 || diff.stats.newBySeverity.high > 0
}

/**
 * Format a summary string for the diff
 */
export function formatDiffSummary(diff: DiffResult): string {
  const parts: string[] = []

  if (diff.stats.newCount > 0) {
    parts.push(`${diff.stats.newCount} new`)
  }
  if (diff.stats.fixedCount > 0) {
    parts.push(`${diff.stats.fixedCount} fixed`)
  }
  if (diff.stats.existingCount > 0) {
    parts.push(`${diff.stats.existingCount} existing`)
  }

  return parts.join(', ')
}
