/**
 * Layer 2: Framework-Specific Security Checks
 * Detects security issues specific to popular frameworks (Next.js, Express, React, etc.)
 */

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

const BASE_CONFIDENCE = 0.20

interface FrameworkPattern {
  name: string
  pattern: RegExp
  severity: VulnerabilitySeverity
  description: string
  suggestedFix: string
  framework: string
}

const FRAMEWORK_PATTERNS: FrameworkPattern[] = [
  // ==================== Next.js ====================
  {
    name: 'Next.js API route without auth',
    pattern: /export\s+(default\s+)?(async\s+)?function\s+handler\s*\(/gi,
    severity: 'medium',
    description: 'Next.js API route handler - ensure authentication is implemented',
    suggestedFix: 'Add authentication check at the start of the handler',
    framework: 'nextjs',
  },
  {
    name: 'Next.js getServerSideProps without auth',
    pattern: /export\s+(async\s+)?function\s+getServerSideProps/gi,
    severity: 'low',
    description: 'Server-side props function - verify auth for protected pages',
    suggestedFix: 'Check authentication and redirect if needed',
    framework: 'nextjs',
  },
  {
    name: 'Next.js exposed server action',
    pattern: /['"]use server['"]\s*;?\s*\n\s*export\s+(async\s+)?function/gi,
    severity: 'medium',
    description: 'Server action is publicly accessible - ensure proper validation',
    suggestedFix: 'Add authentication and input validation to server actions',
    framework: 'nextjs',
  },
  {
    name: 'Next.js revalidate with user input',
    pattern: /revalidatePath\s*\(\s*(req|request|params|searchParams)/gi,
    severity: 'high',
    description: 'revalidatePath with user input could allow cache poisoning',
    suggestedFix: 'Validate and sanitize paths before revalidation',
    framework: 'nextjs',
  },
  {
    name: 'Next.js redirect with user input',
    pattern: /redirect\s*\(\s*(req|request|params|searchParams|url)/gi,
    severity: 'high',
    description: 'Redirect with user input can lead to open redirect vulnerability',
    suggestedFix: 'Validate redirect URLs against an allowlist',
    framework: 'nextjs',
  },
  {
    name: 'Next.js unstable_cache without tags',
    pattern: /unstable_cache\s*\([^)]*\)(?!.*tags)/gi,
    severity: 'low',
    description: 'unstable_cache without tags makes invalidation difficult',
    suggestedFix: 'Add cache tags for proper cache invalidation',
    framework: 'nextjs',
  },
  
  // ==================== Express ====================
  // NOTE: "Express without helmet" moved to security-headers.ts (OWASP Workstream 1)
  {
    name: 'Express CORS allow all',
    pattern: /cors\s*\(\s*\{[^}]*origin\s*:\s*['"]\*['"]/gi,
    severity: 'high',
    description: 'CORS configured to allow all origins',
    suggestedFix: 'Restrict CORS to specific trusted origins',
    framework: 'express',
  },
  {
    name: 'Express body parser limit',
    pattern: /bodyParser\.(json|urlencoded)\s*\(\s*\)(?!.*limit)/gi,
    severity: 'low',
    description: 'Body parser without size limit can lead to DoS',
    suggestedFix: 'Add limit option: bodyParser.json({ limit: "100kb" })',
    framework: 'express',
  },
  {
    name: 'Express trust proxy',
    pattern: /app\.set\s*\(\s*['"]trust proxy['"]\s*,\s*true\s*\)/gi,
    severity: 'medium',
    description: 'Trust proxy enabled - ensure this is intentional',
    suggestedFix: 'Only enable trust proxy behind a known reverse proxy',
    framework: 'express',
  },
  {
    name: 'Express error handler exposes stack',
    pattern: /res\.(json|send)\s*\(\s*\{[^}]*stack|err\.stack/gi,
    severity: 'high',
    description: 'Error stack trace may be exposed to clients',
    suggestedFix: 'Only expose stack traces in development mode',
    framework: 'express',
  },
  
  // ==================== React ====================
  {
    name: 'React useEffect with async',
    pattern: /useEffect\s*\(\s*async\s*\(/gi,
    severity: 'low',
    description: 'useEffect with async function can cause memory leaks',
    suggestedFix: 'Define async function inside useEffect and call it',
    framework: 'react',
  },
  {
    name: 'React state with sensitive data',
    pattern: /useState\s*\(\s*['"]?(password|secret|token|apiKey|api_key)/gi,
    severity: 'medium',
    description: 'Sensitive data in React state may be exposed in dev tools',
    suggestedFix: 'Avoid storing sensitive data in client-side state',
    framework: 'react',
  },
  {
    name: 'React href javascript:',
    pattern: /href\s*=\s*[{'"]*javascript:/gi,
    severity: 'high',
    description: 'javascript: URLs can lead to XSS vulnerabilities',
    suggestedFix: 'Use onClick handlers instead of javascript: URLs',
    framework: 'react',
  },
  {
    name: 'React uncontrolled input with defaultValue',
    pattern: /defaultValue\s*=\s*\{[^}]*(password|secret|token)/gi,
    severity: 'medium',
    description: 'Sensitive data in defaultValue may persist in DOM',
    suggestedFix: 'Use controlled inputs for sensitive data',
    framework: 'react',
  },
  
  // ==================== Vue ====================
  {
    name: 'Vue v-html directive',
    pattern: /v-html\s*=\s*['"]/gi,
    severity: 'high',
    description: 'v-html can lead to XSS if content is not sanitized',
    suggestedFix: 'Sanitize HTML content or use v-text for plain text',
    framework: 'vue',
  },
  {
    name: 'Vue computed with side effects',
    pattern: /computed\s*:\s*\{[^}]*\b(fetch|axios|http|api)\b/gi,
    severity: 'low',
    description: 'Computed properties with side effects can cause issues',
    suggestedFix: 'Move API calls to methods or lifecycle hooks',
    framework: 'vue',
  },
  
  // ==================== Django ====================
  {
    name: 'Django DEBUG in production',
    pattern: /DEBUG\s*=\s*True/gi,
    severity: 'critical',
    description: 'Django DEBUG mode should be disabled in production',
    suggestedFix: 'Set DEBUG = False in production settings',
    framework: 'django',
  },
  {
    name: 'Django ALLOWED_HOSTS wildcard',
    pattern: /ALLOWED_HOSTS\s*=\s*\[\s*['"]\*['"]/gi,
    severity: 'high',
    description: 'ALLOWED_HOSTS with wildcard allows any host',
    suggestedFix: 'Specify exact allowed hostnames',
    framework: 'django',
  },
  {
    name: 'Django SECRET_KEY hardcoded',
    pattern: /SECRET_KEY\s*=\s*['"][^'"]+['"]/gi,
    severity: 'critical',
    description: 'Django SECRET_KEY should not be hardcoded',
    suggestedFix: 'Load SECRET_KEY from environment variable',
    framework: 'django',
  },
  
  // ==================== Flask ====================
  {
    name: 'Flask debug mode',
    pattern: /app\.run\s*\([^)]*debug\s*=\s*True/gi,
    severity: 'critical',
    description: 'Flask debug mode should be disabled in production',
    suggestedFix: 'Set debug=False in production',
    framework: 'flask',
  },
  {
    name: 'Flask secret key hardcoded',
    pattern: /app\.secret_key\s*=\s*['"][^'"]+['"]/gi,
    severity: 'critical',
    description: 'Flask secret_key should not be hardcoded',
    suggestedFix: 'Load secret_key from environment variable',
    framework: 'flask',
  },
  
  // ==================== Supabase ====================
  {
    name: 'Supabase service role key exposed',
    pattern: /supabaseServiceRole|service_role|SUPABASE_SERVICE_ROLE/gi,
    severity: 'critical',
    description: 'Supabase service role key should never be exposed to client',
    suggestedFix: 'Only use service role key in server-side code',
    framework: 'supabase',
  },
  {
    name: 'Supabase RLS bypass',
    pattern: /\.rpc\s*\(\s*['"][^'"]+['"]\s*,\s*\{[^}]*\}\s*,\s*\{[^}]*count/gi,
    severity: 'medium',
    description: 'RPC call may bypass Row Level Security',
    suggestedFix: 'Ensure RPC functions have proper security checks',
    framework: 'supabase',
  },
]

// Detect framework from file content and path
function detectFramework(content: string, filePath: string): string[] {
  const frameworks: string[] = []
  
  // Next.js
  if (content.includes('next/') || content.includes('from "next"') || 
      filePath.includes('/app/') || filePath.includes('/pages/')) {
    frameworks.push('nextjs')
  }
  
  // Express
  if (content.includes('express()') || content.includes('from "express"') ||
      content.includes("require('express')")) {
    frameworks.push('express')
  }
  
  // React
  if (content.includes('from "react"') || content.includes("from 'react'") ||
      content.includes('useState') || content.includes('useEffect')) {
    frameworks.push('react')
  }
  
  // Vue
  if (content.includes('from "vue"') || content.includes('Vue.component') ||
      filePath.endsWith('.vue')) {
    frameworks.push('vue')
  }
  
  // Django
  if (content.includes('from django') || content.includes('import django') ||
      filePath.includes('settings.py')) {
    frameworks.push('django')
  }
  
  // Flask
  if (content.includes('from flask') || content.includes('Flask(__name__)')) {
    frameworks.push('flask')
  }
  
  // Supabase
  if (content.includes('supabase') || content.includes('@supabase/')) {
    frameworks.push('supabase')
  }
  
  return frameworks
}

export function detectFrameworkIssues(
  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 detectedFrameworks = detectFramework(content, filePath)
  const isTestFile = isTestOrMockFile(filePath)

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

    for (const pattern of FRAMEWORK_PATTERNS) {
      // Only check patterns for detected frameworks (or check all if none detected)
      if (detectedFrameworks.length > 0 && !detectedFrameworks.includes(pattern.framework)) {
        continue
      }

      const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)

      if (regex.test(line)) {
        // Special handling for Supabase service role key
        if (pattern.name === 'Supabase service role key exposed') {
          // Check if this is a proper Supabase client setup pattern
          // Expected safe pattern:
          // - Variable named supabaseServiceRoleKey (or similar)
          // - Loaded from process.env.SUPABASE_SERVICE_ROLE_KEY
          // - Used in server-only file (lib/supabase/server.ts, /api/, etc.)

          const isServerFile = isServerOnlyFile(filePath)
          const usesEnvVar = isEnvVarReference(line)

          // Check if this line is just a variable declaration from env var
          // e.g., const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!
          const isEnvVarDeclaration = /(?:const|let|var)\s+\w*(?:service.*role|supabase.*key)\w*\s*=\s*process\.env\./i.test(line)

          // Check if this is a createClient call with variable reference (not hardcoded)
          // e.g., createClient(supabaseUrl, supabaseServiceRoleKey, {...})
          const isCreateClientWithVar = /createClient\s*\(\s*\w+\s*,\s*\w*(?:service.*role|key)\w*\s*[,)]/i.test(line)

          // If server file + env var pattern OR server file + createClient with variable reference, it's safe
          if (isServerFile) {
            if (isEnvVarDeclaration || usesEnvVar) {
              // Safe: declaring/loading from env var in server file
              continue
            }
            if (isCreateClientWithVar && !/"[a-zA-Z0-9._-]{20,}"/.test(line)) {
              // Safe: using variable (not hardcoded string) in createClient
              // Check surrounding content for env var loading
              const fileHasEnvVarLoading = content.includes('process.env.SUPABASE_SERVICE_ROLE_KEY')
              if (fileHasEnvVarLoading) {
                continue
              }
            }
          }

          const serviceRoleContext = getServiceRoleKeyContext(line, filePath)

          // Double-check: Server-only file using env var = safe, skip entirely
          if (serviceRoleContext === 'safe_server') {
            continue
          }

          // Determine severity and whether AI validation is needed
          let adjustedSeverity = pattern.severity
          let adjustedDescription = pattern.description
          let requiresAIValidation = false
          let adjustedConfidence: 'high' | 'medium' | 'low' = 'medium'

          if (serviceRoleContext === 'client_exposure') {
            // Client exposure = critical
            adjustedSeverity = 'critical'
            adjustedDescription = 'Supabase service role key may be exposed to client - this bypasses RLS'
            requiresAIValidation = true
            adjustedConfidence = 'high'
          } else {
            // needs_review
            if (isEnvVarReference(line)) {
              // Using env var in non-server file - needs review
              adjustedSeverity = 'medium'
              adjustedDescription = 'Supabase service role key usage - verify this is server-only code'
              requiresAIValidation = true
            } else if (isServerOnlyFile(filePath)) {
              // Hardcoded in server file - bad practice but not critical
              adjustedSeverity = 'high'
              adjustedDescription = 'Supabase service role key should use environment variable'
              requiresAIValidation = true
            } else {
              // Unknown context - flag for review
              adjustedSeverity = 'high'
              requiresAIValidation = true
            }
          }

          // Downgrade test file severity
          if (isTestFile) {
            adjustedSeverity = 'low'
            adjustedConfidence = 'low'
            adjustedDescription = `${adjustedDescription} (in test file)`
          }

          vulnerabilities.push({
            id: `framework-${filePath}-${index + 1}-${pattern.name}`,
            filePath,
            lineNumber: index + 1,
            lineContent: line.trim(),
            severity: adjustedSeverity,
            category: 'insecure_config',
            title: `[${pattern.framework}] ${pattern.name}`,
            description: adjustedDescription,
            suggestedFix: pattern.suggestedFix,
            confidence: adjustedConfidence,
            baseConfidence: BASE_CONFIDENCE,
            layer: 2,
        source: 'structural' as const,
            requiresAIValidation,
          })
          break
        }

        // Standard handling for other patterns
        let severity = pattern.severity
        let confidence: 'high' | 'medium' | 'low' = 'medium'

        // Downgrade test file severity
        if (isTestFile) {
          if (severity === 'critical') {
            severity = 'medium'
          } else if (severity === 'high') {
            severity = 'low'
          } else {
            severity = 'info'
          }
          confidence = 'low'
        }

        vulnerabilities.push({
          id: `framework-${filePath}-${index + 1}-${pattern.name}`,
          filePath,
          lineNumber: index + 1,
          lineContent: line.trim(),
          severity,
          category: 'insecure_config',
          title: `[${pattern.framework}] ${pattern.name}`,
          description: isTestFile ? `${pattern.description} (in test file)` : pattern.description,
          suggestedFix: pattern.suggestedFix,
          confidence,
          baseConfidence: BASE_CONFIDENCE,
          layer: 2,
        source: 'structural' as const,
        })
        break // Only report once per line
      }
    }
  })

  return vulnerabilities
}
