/**
 * Confidence Scoring System
 *
 * Entry point for the confidence scoring system. Provides:
 * - scoreFindings() — score a batch of findings
 * - buildConfidenceLog() — structured logging for debugging/tuning
 * - buildAdjustmentContext() — build context for adjustment rules
 * - DEPTH_CONFIGS — threshold configs for each scan depth
 */

import type { Vulnerability, ScanDepth } from '../shared/types'
import type {
  ScoredFinding,
  ConfidenceLog,
  AdjustmentContext,
  DepthConfig,
} from './types'
import type { ContextEngineResult } from '../model/taint-types'
import type { CrossFileTaintPath } from '../model/cross-file-taint'
import { findClosestRoute } from '../model/route-discovery/utils'
import { computeConfidence } from './confidence'
import { ALL_ADJUSTMENT_RULES } from './adjustments'
import {
  isTestOrMockFile,
  isExampleFile,
  isScannerOrFixtureFile,
  isToolingDirectory,
  isServerOnlyFile,
  isClientBundledFile,
} from '../parse/file-classifier'

// ============================================================================
// Depth Configurations
// ============================================================================

/**
 * Score thresholds per scan depth.
 *
 * - local:    Only high-confidence findings surface. No AI.
 * - verified: High-confidence surface directly; medium goes to AI.
 * - deep:     High-confidence surface; nearly everything goes to AI.
 */
export const DEPTH_CONFIGS: Record<ScanDepth, DepthConfig> = {
  local: {
    surfaceThreshold: 0.70,
    validateThreshold: 1.0,   // Effectively no validation (nothing reaches 1.0)
    aiEnabled: false,
  },
  verified: {
    surfaceThreshold: 0.70,
    validateThreshold: 0.20,
    aiEnabled: true,
  },
  deep: {
    surfaceThreshold: 0.70,
    validateThreshold: 0.10,
    aiEnabled: true,
  },
}

// ============================================================================
// Context Builder
// ============================================================================

/**
 * Build adjustment context for a finding from file + project info.
 */
export function buildAdjustmentContext(
  finding: Vulnerability,
  ceResult: ContextEngineResult
): AdjustmentContext {
  // Look up taint data for this finding's file
  let taint: AdjustmentContext['taint'] = undefined
  const fileAnalysis = ceResult.fileTaintAnalyses?.get(finding.filePath)
  if (fileAnalysis) {
    // Check if any taint path has a sink at the finding's line number
    const matchingPaths = fileAnalysis.taintPaths.filter(
      tp => tp.sink.line === finding.lineNumber
    )
    if (matchingPaths.length > 0) {
      // Pick the highest-confidence unsanitised path, or any path
      const bestPath = matchingPaths.find(p => !p.sanitised) ?? matchingPaths[0]
      taint = {
        confirmed: true,
        sources: matchingPaths.map(p => p.source.sourceType),
        sinkType: bestPath.sink.sinkType,
        sanitised: bestPath.sanitised,
        confidence: bestPath.confidence,
      }
    } else {
      // Check cross-file taint paths (CE Phase 4) — sink-side match
      const crossFilePaths = findCrossFileTaintMatch(ceResult, finding)

      if (crossFilePaths.length > 0) {
        const bestPath = crossFilePaths.find(p => !p.sanitised) ?? crossFilePaths[0]
        taint = {
          confirmed: true,
          sources: crossFilePaths.map(p => p.source.sourceType),
          sinkType: bestPath.sink.sinkType,
          sanitised: bestPath.sanitised,
          confidence: bestPath.confidence,
        }
      } else {
        // File has taint analysis but no path to this finding's line
        taint = {
          confirmed: false,
          sources: [],
          sinkType: null,
          sanitised: false,
          confidence: null,
        }
      }
    }
  } else {
    // No intra-file taint analysis — still check cross-file taint paths (call-site match)
    const crossFilePaths = findCrossFileTaintMatch(ceResult, finding)
    if (crossFilePaths.length > 0) {
      const bestPath = crossFilePaths.find(p => !p.sanitised) ?? crossFilePaths[0]
      taint = {
        confirmed: true,
        sources: crossFilePaths.map(p => p.source.sourceType),
        sinkType: bestPath.sink.sinkType,
        sanitised: bestPath.sanitised,
        confidence: bestPath.confidence,
      }
    }
  }

  // Look up route data for this finding's file
  let route: AdjustmentContext['route'] = undefined
  const fileRoutes = ceResult.routeMap?.fileToRoutes.get(finding.filePath)
  if (fileRoutes && fileRoutes.length > 0) {
    // Find the closest route to the finding's line number
    const closestRoute = findClosestRoute(fileRoutes, finding.lineNumber)
    if (closestRoute) {
      route = {
        hasAuth: closestRoute.authMiddleware.length > 0,
        isPublic: closestRoute.isPublic,
        middleware: closestRoute.authMiddleware,
        methods: closestRoute.methods,
        hasRateLimiting: closestRoute.rateLimiting,
      }
    }
  }

  // Look up framework analysis for this finding's file
  let framework: AdjustmentContext['framework'] = undefined
  const fileFramework = ceResult.frameworkAnalyses?.get(finding.filePath)
  if (fileFramework) {
    const safeLinePatterns = fileFramework.safeLines.get(finding.lineNumber)
    const unsafeLinePatterns = fileFramework.unsafeLines.get(finding.lineNumber)
    const safeLine = safeLinePatterns?.[0]
    const unsafeLine = unsafeLinePatterns?.[0]
    if (safeLine || unsafeLine) {
      const activeFrameworks: string[] = []
      const pc2 = ceResult.projectContext
      if (pc2.frameworks.primary) activeFrameworks.push(pc2.frameworks.primary)
      if (pc2.frameworks.frontend && !activeFrameworks.includes(pc2.frameworks.frontend)) {
        activeFrameworks.push(pc2.frameworks.frontend)
      }
      framework = {
        safePatternMatch: safeLine ? { id: safeLine.id, reason: safeLine.reason } : null,
        unsafePatternMatch: unsafeLine ? { id: unsafeLine.id, reason: unsafeLine.reason } : null,
        activeFrameworks,
        orm: ceResult.projectContext.dataAccess.orm ?? null,
      }
    }
  }

  const pc = ceResult.projectContext
  return {
    file: {
      isTestFile: isTestOrMockFile(finding.filePath),
      isExampleFile: isExampleFile(finding.filePath),
      isDocFile: /\.(md|mdx|txt|rst)$/i.test(finding.filePath),
      isScannerCode: isScannerOrFixtureFile(finding.filePath),
      isToolingDir: isToolingDirectory(finding.filePath),
      isServerOnly: isServerOnlyFile(finding.filePath),
      isClientBundled: isClientBundledFile(finding.filePath),
    },
    project: {
      hasAuthMiddleware: pc.auth.hasGlobalMiddleware,
      hasTypeScript: pc.frameworks.usesTypeScript,
      orm: pc.dataAccess.orm ?? null,
      validationLib: pc.dataAccess.validationLibrary ?? null,
    },
    lineContent: finding.lineContent,
    filePath: finding.filePath,
    taint,
    route,
    framework,
  }
}

/**
 * Find cross-file taint paths matching a finding — both sink-side and call-site.
 * Mirrors the matching logic in request-builder.ts for AI annotations.
 */
function findCrossFileTaintMatch(
  ceResult: ContextEngineResult,
  finding: Vulnerability
): CrossFileTaintPath[] {
  return ceResult.crossFileTaintPaths?.filter(
    cfp => (cfp.sinkFile === finding.filePath && cfp.sink.line === finding.lineNumber) ||
           (cfp.sourceFile === finding.filePath && cfp.importLink.callLine === finding.lineNumber)
  ) ?? []
}

// ============================================================================
// Score Findings
// ============================================================================

/**
 * Score a batch of findings with confidence scoring.
 *
 * Each finding gets a ConfidenceComputation attached as `confidence_score`.
 * The routing field indicates whether the finding should be surfaced,
 * validated by AI, or suppressed.
 */
export interface ScoringDiagnostics {
  /** How many times each adjustment rule fired */
  ruleHitCounts: Record<string, number>
  /** Score distribution buckets */
  scoreDistribution: { '0.0-0.2': number; '0.2-0.4': number; '0.4-0.6': number; '0.6-0.8': number; '0.8-1.0': number }
  /** Average score per category */
  categoryAvgScores: Record<string, { avg: number; count: number }>
  /** Taint rule hits specifically */
  taintRuleHits: Record<string, number>
}

export function scoreFindings(
  findings: Vulnerability[],
  depth: ScanDepth,
  ceResult: ContextEngineResult
): { scored: ScoredFinding[]; diagnostics: ScoringDiagnostics } {
  const depthConfig = DEPTH_CONFIGS[depth]
  const ruleHitCounts: Record<string, number> = {}
  const taintRuleHits: Record<string, number> = {}
  const scoreDistribution = { '0.0-0.2': 0, '0.2-0.4': 0, '0.4-0.6': 0, '0.6-0.8': 0, '0.8-1.0': 0 }
  const categoryScoreSums: Record<string, { sum: number; count: number }> = {}

  const scored = findings.map(finding => {
    const context = buildAdjustmentContext(finding, ceResult)
    const computation = computeConfidence(finding, context, ALL_ADJUSTMENT_RULES, depthConfig)

    // Collect diagnostics
    for (const adj of computation.adjustments) {
      ruleHitCounts[adj.factor] = (ruleHitCounts[adj.factor] || 0) + 1
      if (adj.source === 'taint') {
        taintRuleHits[adj.factor] = (taintRuleHits[adj.factor] || 0) + 1
      }
    }

    // Score distribution
    const s = computation.score
    if (s < 0.2) scoreDistribution['0.0-0.2']++
    else if (s < 0.4) scoreDistribution['0.2-0.4']++
    else if (s < 0.6) scoreDistribution['0.4-0.6']++
    else if (s < 0.8) scoreDistribution['0.6-0.8']++
    else scoreDistribution['0.8-1.0']++

    // Per-category avg
    const cat = finding.category
    if (!categoryScoreSums[cat]) categoryScoreSums[cat] = { sum: 0, count: 0 }
    categoryScoreSums[cat].sum += computation.score
    categoryScoreSums[cat].count++

    return {
      ...finding,
      confidence_score: computation,
    }
  })

  const categoryAvgScores: Record<string, { avg: number; count: number }> = {}
  for (const [cat, data] of Object.entries(categoryScoreSums)) {
    categoryAvgScores[cat] = { avg: data.sum / data.count, count: data.count }
  }

  return {
    scored,
    diagnostics: { ruleHitCounts, scoreDistribution, categoryAvgScores, taintRuleHits },
  }
}

// ============================================================================
// Confidence Log
// ============================================================================

/**
 * Build structured confidence logs for a batch of scored findings.
 * Used for debugging, tuning, and comparing with tier-based routing.
 */
export function buildConfidenceLog(scored: ScoredFinding[]): ConfidenceLog[] {
  return scored.map(f => ({
    findingId: `${f.filePath}:${f.lineNumber}:${f.category}`,
    category: f.category,
    detectorGroup: f.source ?? 'unknown',
    base: f.confidence_score.base,
    adjustments: f.confidence_score.adjustments,
    final: f.confidence_score.score,
    routing: f.confidence_score.routing,
    filePath: f.filePath,
    severity: f.severity,
  }))
}

// Re-export types for consumer convenience
export type {
  ScoredFinding,
  ConfidenceComputation,
  ConfidenceAdjustment,
  ConfidenceLog,
  AdjustmentRule,
  AdjustmentContext,
  DepthConfig,
} from './types'
