/**
 * Layer 2: Data Exposure Detection
 * Identifies sensitive data in logs vs API responses with appropriate severity
 * Separates "logging concerns" from "response exposure" which have different risk profiles
 */

import type { Vulnerability, VulnerabilitySeverity } from '../../shared/types'
import type { ParsedFile } from '../../shared/parsed-file'
import { isComment, isTestOrMockFile, isScannerOrFixtureFile } from '../../parse/file-classifier'

const BASE_CONFIDENCE = 0.40

interface DataExposurePattern {
  name: string
  pattern: RegExp
  sink: 'log' | 'response' | 'both'
  severity: VulnerabilitySeverity
  description: string
  suggestedFix: string
}

const DATA_EXPOSURE_PATTERNS: DataExposurePattern[] = [
  // ============================================================================
  // LOG SINKS - DISABLED
  // Server logs are never exposed to users. After analysis of real codebases,
  // 100% of console.error/warn findings were false positives.
  // Logging is a standard debugging practice, not a security vulnerability.
  // ============================================================================

  // NOTE: The following patterns have been REMOVED to eliminate false positives:
  // - 'Logging user ID' - user IDs in logs are standard practice
  // - 'Logging error objects' - console.error(err) is correct error handling
  // - 'Logging request body' - server logs are not exposed to clients
  // - 'JSON.stringify error to log' - serializing errors for logs is fine

  // ============================================================================
  // RESPONSE SINKS (higher severity - exposed to clients)
  // These are actual information disclosure risks
  // ============================================================================

  // Error stack traces in responses - CRITICAL
  {
    name: 'Stack trace in response',
    pattern: /res\.(json|send|status\(\d+\)\.json)\s*\([^)]*\.stack|NextResponse\.json\s*\([^)]*\.stack/gi,
    sink: 'response',
    severity: 'high',
    description: 'Stack trace exposed in API response. Reveals internal code paths and file structure to clients.',
    suggestedFix: 'Never return stack traces to clients. Log server-side, return generic error message.',
  },
  {
    name: 'Full error object in response',
    pattern: /res\.(json|send)\s*\(\s*(err|error|e)\s*\)|NextResponse\.json\s*\(\s*(err|error|e)\s*\)/gi,
    sink: 'response',
    severity: 'high',
    description: 'Entire error object returned to client. May expose stack traces, internal paths, and sensitive details.',
    suggestedFix: 'Return only { error: message } or a structured error response. Never return raw error objects.',
  },
  {
    name: 'Error object spread in response',
    pattern: /res\.(json|send)\s*\(\s*\{[^}]*\.\.\.\s*(err|error|e)[^}]*\}|NextResponse\.json\s*\(\s*\{[^}]*\.\.\.\s*(err|error|e)/gi,
    sink: 'response',
    severity: 'high',
    description: 'Error object spread into response. May expose stack traces and internal details.',
    suggestedFix: 'Pick only safe properties: { error: err.message, code: err.code }',
  },
  {
    name: 'Detailed error in response',
    pattern: /res\.(json|send|status\(\d+\)\.json)\s*\(\s*\{[^}]*(details|internal|debug|trace|stack)/gi,
    sink: 'response',
    severity: 'medium',
    description: 'Detailed/internal error information in response may leak implementation details.',
    suggestedFix: 'Remove detailed/internal/debug/trace information from client-facing error responses.',
  },
  
  // Error message in response - SAFE PATTERN (info only)
  {
    name: 'Error message in response (safe pattern)',
    pattern: /res\.(json|send)\s*\([^)]*error:\s*(error|err|e)\.message|NextResponse\.json\s*\([^)]*error:\s*(error|err|e)\.message/gi,
    sink: 'response',
    severity: 'info',
    description: 'Error message returned to client. Generally safe - this is the recommended error response pattern.',
    suggestedFix: 'Verify error messages don\'t contain sensitive data. Consider using generic messages for auth errors.',
  },
  {
    name: 'Error message with toString',
    pattern: /res\.(json|send)\s*\([^)]*error:\s*(error|err|e)\.toString|NextResponse\.json\s*\([^)]*error:\s*(error|err|e)\.toString/gi,
    sink: 'response',
    severity: 'low',
    description: 'Error.toString() returned to client. May include error name and message - verify no sensitive data.',
    suggestedFix: 'Prefer error.message over error.toString() for cleaner output.',
  },
  {
    name: 'String error message in response',
    pattern: /res\.(json|send|status\(\d+\)\.json)\s*\(\s*\{\s*(error|message):\s*['"`][^'"`]+['"`]\s*\}/gi,
    sink: 'response',
    severity: 'info',
    description: 'Static/string error message returned to client. This is the safest error response pattern.',
    suggestedFix: 'No action needed - static error messages are safe.',
  },

  // ============================================================================
  // BOTH SINKS (depends on context)
  // ============================================================================

  // Sensitive data exposure patterns
  {
    name: 'Exposing user object',
    pattern: /res\.(json|send)\s*\([^)]*user\s*\)|NextResponse\.json\s*\([^)]*user\s*\)/gi,
    sink: 'response',
    severity: 'low',
    description: 'User object returned in response. Ensure password hashes and sensitive fields are excluded.',
    suggestedFix: 'Select only necessary user fields. Never expose password hashes, tokens, or internal IDs.',
  },
]

/**
 * Check if file path indicates low-risk logging context
 */
function isLowRiskLoggingFile(filePath: string): boolean {
  // Test files
  if (isTestOrMockFile(filePath)) {
    return true
  }

  // Scripts, tools, CLI utilities
  if (/\/(scripts?|tools?|cli|bin)\//i.test(filePath)) {
    return true
  }

  // Internal services/utilities (not API-facing)
  if (/\/(services?|lib|utils?|helpers?)\//i.test(filePath) &&
      !/\/(api|routes?)\//i.test(filePath)) {
    return true
  }

  // Component files (client-side, not API)
  if (/\/(components?|pages?|views?)\//i.test(filePath) &&
      !/route\.(ts|js)$/i.test(filePath)) {
    return true
  }

  return false
}

/**
 * Detect sensitive data exposure in logs and API responses
 */
export function detectDataExposure(
  content: string,
  filePath: string,
  options?: { parsed?: ParsedFile }
): Vulnerability[] {
  const vulnerabilities: Vulnerability[] = []

  // Skip scanner/fixture files to avoid self-detection
  if (isScannerOrFixtureFile(filePath)) return vulnerabilities

  const lines = options?.parsed?.lines ?? content.split('\n')
  const isTestFile = isTestOrMockFile(filePath)
  const isLowRiskFile = isLowRiskLoggingFile(filePath)

  // Determine if this is likely an API route file
  const isApiFile = /\/(api|routes?|handlers?|controllers?)\//i.test(filePath) ||
                    /route\.(ts|js)$/i.test(filePath)

  // Track log findings for aggregation
  const logFindings: { lineNumber: number; lineContent: string; name: string }[] = []

  lines.forEach((line, index) => {
    // Skip comments
    if (isComment(line)) return

    for (const pattern of DATA_EXPOSURE_PATTERNS) {
      const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)

      if (regex.test(line)) {
        let severity = pattern.severity
        let description = pattern.description

        // Adjust severity based on context
        if (isTestFile) {
          severity = 'info'
          description = `${description} (in test file)`
        }

        // Log sinks get special handling for aggregation
        if (pattern.sink === 'log') {
          // In low-risk files, just aggregate without reporting individual findings
          if (isLowRiskFile && severity === 'info') {
            logFindings.push({ lineNumber: index + 1, lineContent: line.trim(), name: pattern.name })
            break
          }

          // Log sinks in non-API files are lower priority
          if (!isApiFile) {
            if (severity === 'low') severity = 'info'
          }

          // Track for aggregation if info severity
          if (severity === 'info') {
            logFindings.push({ lineNumber: index + 1, lineContent: line.trim(), name: pattern.name })
            break
          }
        }

        // Response sinks in API files are higher priority
        if (pattern.sink === 'response' && isApiFile) {
          // Keep original severity - these are more critical in API routes
        }

        vulnerabilities.push({
          id: `data-exposure-${filePath}-${index + 1}-${pattern.name}`,
          filePath,
          lineNumber: index + 1,
          lineContent: line.trim(),
          severity,
          category: 'data_exposure',
          title: pattern.name,
          description,
          suggestedFix: pattern.suggestedFix,
          confidence: isTestFile ? 'low' : 'medium',
          baseConfidence: BASE_CONFIDENCE,
          layer: 2,
        source: 'structural' as const,
        })
        break // Only one finding per line
      }
    }
  })

  // Aggregate info-level log findings if there are many
  if (logFindings.length >= 3) {
    const lineNumbers = logFindings.map(f => f.lineNumber).slice(0, 5)
    const moreText = logFindings.length > 5 ? `... (${logFindings.length} total)` : ''

    // Group by pattern name
    const patternCounts = new Map<string, number>()
    for (const finding of logFindings) {
      patternCounts.set(finding.name, (patternCounts.get(finding.name) || 0) + 1)
    }
    const patternSummary = Array.from(patternCounts.entries())
      .map(([name, count]) => `${count}x ${name}`)
      .join(', ')

    vulnerabilities.push({
      id: `data-exposure-aggregated-${filePath}`,
      filePath,
      lineNumber: logFindings[0].lineNumber,
      lineContent: `${logFindings.length} instances across this file`,
      severity: 'info',
      category: 'data_exposure',
      title: `Logging patterns (${logFindings.length} instances)`,
      description: `${patternSummary}. Review for sensitive data exposure.\n\nFound ${logFindings.length} occurrences at lines: ${lineNumbers.join(', ')}${moreText}`,
      suggestedFix: 'Ensure logs have appropriate access controls and do not contain sensitive user data.',
      confidence: 'low',
      baseConfidence: BASE_CONFIDENCE,
      layer: 2,
        source: 'structural' as const,
    })
  } else if (logFindings.length > 0) {
    // Report individually for small counts
    for (const finding of logFindings) {
      const pattern = DATA_EXPOSURE_PATTERNS.find(p => p.name === finding.name)
      if (pattern) {
        vulnerabilities.push({
          id: `data-exposure-${filePath}-${finding.lineNumber}-${finding.name}`,
          filePath,
          lineNumber: finding.lineNumber,
          lineContent: finding.lineContent,
          severity: 'info',
          category: 'data_exposure',
          title: pattern.name,
          description: pattern.description,
          suggestedFix: pattern.suggestedFix,
          confidence: 'low',
          baseConfidence: BASE_CONFIDENCE,
          layer: 2,
        source: 'structural' as const,
        })
      }
    }
  }

  return vulnerabilities
}

/**
 * Check if error handling follows safe patterns
 * Returns true if the error handling appears safe
 */
export function isSafeErrorHandling(lineContent: string, surroundingLines: string[]): boolean {
  // Safe patterns: only returning error.message, using generic messages
  const safePatterns = [
    /error:\s*['"`].*['"`]/,  // Generic string message
    /error:\s*error\.message/,  // Only message property
    /error:\s*err\.message/,
    /message:\s*(error|err)\.message/,
    /status\(\d+\)\.json\(\s*\{\s*error:/,  // Status code + error object (common pattern)
  ]

  return safePatterns.some(p => p.test(lineContent))
}
