/**
 * Incremental Scan Mode
 * Optimized scanning for PR workflows - only scan changed files and surface relevant findings
 */

import type { ScanFile, Vulnerability, ScanResult, ScanModeConfig } from '../../shared/types'
import { runLayer1Scan } from '../../detect/secrets'
import { runLayer2Scan } from '../../detect/structural'
import {
  parseDiff,
  parseChangedFileList,
  filterToChangedLines,
  getChangedFilePaths,
  type FileDiff,
} from '../../shared/diff-parser'
import { detectGlobalAuthMiddleware } from '../../model/middleware-detector'

/**
 * Options for incremental scanning
 */
export interface IncrementalScanOptions {
  /** Git diff output (if available) */
  diffContent?: string
  /** List of changed file paths (alternative to diff) */
  changedFiles?: string[]
  /** Whether to only show findings on exactly changed lines (strict) or nearby (default) */
  strictLineMatching?: boolean
  /** Context window for "near changed line" (default: 5) */
  contextWindow?: number
  /** Whether to mark findings as "introduced in this PR" */
  markAsIntroduced?: boolean
  /** Previous findings to compare against (for suppressing pre-existing issues) */
  previousFindings?: Vulnerability[]
}

/**
 * Result of incremental scan with additional PR-specific metadata
 */
export interface IncrementalScanResult {
  /** All findings (filtered to changed lines) */
  findings: Vulnerability[]
  /** Findings that are new in this PR */
  introduced: Vulnerability[]
  /** Findings that existed before (if previousFindings provided) */
  preExisting: Vulnerability[]
  /** Number of files scanned */
  filesScanned: number
  /** Number of files that were changed */
  filesChanged: number
  /** Parsed diff information */
  diffs: Map<string, FileDiff>
  /** Scan duration in ms */
  duration: number
}

/**
 * Run an incremental scan optimized for PR workflows
 *
 * This scans:
 * 1. All changed files (added + modified)
 * 2. Files that import changed files (for context)
 * 3. Middleware files (for auth context)
 *
 * And only surfaces findings on/near changed lines.
 */
export async function runIncrementalScan(
  allFiles: ScanFile[],
  options: IncrementalScanOptions
): Promise<IncrementalScanResult> {
  const startTime = Date.now()

  const {
    diffContent,
    changedFiles,
    strictLineMatching = false,
    contextWindow = 5,
    markAsIntroduced = true,
    previousFindings = [],
  } = options

  // Parse diff or file list to get changed files
  let diffs: Map<string, FileDiff>

  if (diffContent) {
    diffs = parseDiff(diffContent, contextWindow)
  } else if (changedFiles && changedFiles.length > 0) {
    diffs = parseChangedFileList(changedFiles)
  } else {
    // No diff info - scan everything but don't filter
    console.log('[Incremental] No diff info provided, scanning all files')
    diffs = new Map()
  }

  const changedPaths = getChangedFilePaths(diffs)
  console.log(`[Incremental] Changed files: ${changedPaths.length}`)

  // Build file index for import resolution
  const fileIndex = new Map<string, ScanFile>()
  for (const file of allFiles) {
    fileIndex.set(file.path, file)
  }

  // Determine which files to scan
  const filesToScan: ScanFile[] = []
  const scannedPaths = new Set<string>()

  // 1. Add all changed files
  for (const path of changedPaths) {
    const file = fileIndex.get(path)
    if (file && !scannedPaths.has(path)) {
      filesToScan.push(file)
      scannedPaths.add(path)
    }
  }

  // 2. Add files that import changed files (for context)
  // This helps detect issues where changes break dependencies
  const importers = findImporters(allFiles, changedPaths)
  for (const path of importers) {
    if (!scannedPaths.has(path)) {
      const file = fileIndex.get(path)
      if (file) {
        filesToScan.push(file)
        scannedPaths.add(path)
      }
    }
  }

  // 3. Always include middleware files for auth context
  const middlewareFile = allFiles.find(f =>
    f.path.includes('middleware.ts') ||
    f.path.includes('middleware.js')
  )
  if (middlewareFile && !scannedPaths.has(middlewareFile.path)) {
    filesToScan.push(middlewareFile)
    scannedPaths.add(middlewareFile.path)
  }

  console.log(`[Incremental] Scanning ${filesToScan.length} files (${changedPaths.length} changed + ${importers.size} importers)`)

  // Detect auth middleware from ALL files (for context)
  const middlewareConfig = detectGlobalAuthMiddleware(allFiles)

  // Run Layer 1 + Layer 2 on selected files
  const layer1Result = await runLayer1Scan(filesToScan)
  const layer2Result = await runLayer2Scan(filesToScan, { middlewareConfig })

  let allFindings = [...layer1Result.vulnerabilities, ...layer2Result.vulnerabilities]

  // Filter to only findings on/near changed lines
  if (diffs.size > 0) {
    const beforeFilter = allFindings.length
    allFindings = filterToChangedLines(allFindings, diffs, { strictMode: strictLineMatching })
    console.log(`[Incremental] Filtered findings: ${beforeFilter} → ${allFindings.length} (on/near changed lines)`)
  }

  // Mark findings as introduced and separate pre-existing
  const introduced: Vulnerability[] = []
  const preExisting: Vulnerability[] = []

  if (previousFindings.length > 0) {
    // Create fingerprints for previous findings
    const previousFingerprints = new Set(
      previousFindings.map(f => `${f.filePath}:${f.lineNumber}:${f.category}`)
    )

    for (const finding of allFindings) {
      const fingerprint = `${finding.filePath}:${finding.lineNumber}:${finding.category}`

      if (previousFingerprints.has(fingerprint)) {
        preExisting.push(finding)
      } else {
        if (markAsIntroduced) {
          finding.validationNotes = (finding.validationNotes || '') + ' [Introduced in this PR]'
        }
        introduced.push(finding)
      }
    }
  } else {
    // No previous findings - all are "introduced"
    introduced.push(...allFindings)
  }

  const duration = Date.now() - startTime
  console.log(`[Incremental] Scan completed in ${duration}ms: ${introduced.length} new, ${preExisting.length} pre-existing`)

  return {
    findings: allFindings,
    introduced,
    preExisting,
    filesScanned: filesToScan.length,
    filesChanged: changedPaths.length,
    diffs,
    duration,
  }
}

/**
 * Find files that import any of the changed files
 */
function findImporters(allFiles: ScanFile[], changedPaths: string[]): Set<string> {
  const importers = new Set<string>()

  // Create patterns to match imports
  const importPatterns = changedPaths.map(path => {
    // Remove extension for import matching
    const withoutExt = path.replace(/\.[^/.]+$/, '')
    // Get just the filename without path for relative imports
    const filename = withoutExt.split('/').pop() || ''
    return { fullPath: withoutExt, filename }
  })

  for (const file of allFiles) {
    // Skip if this file is already in changed paths
    if (changedPaths.includes(file.path)) continue

    // Check if this file imports any changed file
    for (const { fullPath, filename } of importPatterns) {
      // Match various import patterns
      const importRegex = new RegExp(
        `(?:import|require)\\s*(?:\\([^)]*|[^;]*from\\s*)['"]` +
        `(?:\\.{0,2}/)?(?:${escapeRegex(fullPath)}|[^'"]*/${escapeRegex(filename)})['"]`,
        'i'
      )

      if (importRegex.test(file.content)) {
        importers.add(file.path)
        break
      }
    }
  }

  return importers
}

/**
 * Escape special regex characters
 */
function escapeRegex(str: string): string {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

/**
 * Create a PR-optimized scan config
 */
export function createPRScanConfig(
  changedFiles: string[],
  options: Partial<ScanModeConfig> = {}
): ScanModeConfig {
  return {
    mode: 'incremental',
    changedFiles,
    skipAIValidation: false,  // Use AI for validation
    skipLayer3: true,          // Skip deep analysis for speed
    maxAIValidationFiles: 20,
    maxLayer3Files: 0,
    scanDepth: 'local',        // Fast feedback for PRs
    ...options,
  }
}

/**
 * Format incremental scan result for PR comment
 */
export function formatIncrementalForPR(result: IncrementalScanResult): {
  summary: string
  hasNewIssues: boolean
  blockingIssues: number
} {
  const blocking = result.introduced.filter(
    f => f.severity === 'critical' || f.severity === 'high'
  )

  let summary: string

  if (result.introduced.length === 0) {
    summary = `✅ No new security issues introduced in this PR`
  } else if (blocking.length > 0) {
    summary = `🚨 ${blocking.length} blocking issue${blocking.length === 1 ? '' : 's'} introduced`
  } else {
    summary = `⚠️ ${result.introduced.length} new issue${result.introduced.length === 1 ? '' : 's'} to review`
  }

  if (result.preExisting.length > 0) {
    summary += ` (${result.preExisting.length} pre-existing)`
  }

  return {
    summary,
    hasNewIssues: result.introduced.length > 0,
    blockingIssues: blocking.length,
  }
}
