/**
 * Layer 1: Weak Cryptography Detection
 * Detects usage of deprecated or weak cryptographic algorithms
 */

import type { Vulnerability } from '../../shared/types'
import type { ParsedFile } from '../../shared/parsed-file'

// Base confidence for weak cryptography findings
const BASE_CONFIDENCE = 0.45

// Weak/deprecated cryptographic patterns
const WEAK_CRYPTO_PATTERNS = [
  // Weak hash algorithms
  {
    pattern: /\bMD5\s*\(/gi,
    name: 'MD5 Hash Usage',
    severity: 'high' as const,
    description: 'MD5 is cryptographically broken and should not be used for security purposes',
    fix: 'Use SHA-256 or SHA-3 for hashing. For passwords, use bcrypt, scrypt, or Argon2.',
    contextCheck: (line: string) => {
      // Only flag as high if used in security context (passwords, tokens, etc.)
      // Checksum/etag/cache usage is acceptable for non-security purposes
      const securityContexts = [/password/i, /token/i, /secret/i, /credential/i, /auth/i, /session/i, /key/i, /verify.*user/i, /sign/i]
      const checksumContexts = [
        /checksum/i,
        /hash.*file/i,
        /file.*hash/i,
        /etag/i,
        /content.*hash/i,
        /integrity/i,
        /digest/i,
        /fingerprint/i,
        /cache.*key/i,
        /dedup/i,
        /uniqueId/i,
        /contentId/i,
        /gravatar/i,        // Gravatar uses MD5 for email hashing
        /avatar/i,          // Avatar URLs often use MD5
        /blob/i,            // Blob hashing
        /asset/i,           // Asset fingerprinting
        /sprite/i,          // Sprite sheet hashing
        /bundle/i,          // Bundle hashing
        /chunk/i,           // Webpack chunk hashing
        /manifest/i,        // Manifest hashing
      ]

      const isSecurityContext = securityContexts.some(p => p.test(line))
      const isChecksumContext = checksumContexts.some(p => p.test(line))

      // If it's clearly a checksum context, don't flag
      if (isChecksumContext && !isSecurityContext) {
        return false
      }
      return true
    },
  },
  {
    pattern: /createHash\s*\(\s*['"]md5['"]\s*\)/gi,
    name: 'MD5 Hash Creation',
    severity: 'high' as const,
    description: 'MD5 is cryptographically broken and should not be used for security purposes',
    fix: 'Use createHash(\'sha256\') or createHash(\'sha3-256\') instead.',
    contextCheck: (line: string, _match: RegExpMatchArray, funcName?: string) => {
      // Check function name and line context for checksum indicators
      const checksumIndicators = [
        /checksum/i,
        /etag/i,
        /content.*hash/i,
        /file.*hash/i,
        /integrity/i,
        /digest/i,
        /fingerprint/i,
        /verify.*file/i,
        /cache/i,
        /dedup/i,
        /gravatar/i,
        /avatar/i,
        /asset/i,
        /bundle/i,
        /chunk/i,
        /manifest/i,
        /sprite/i,
      ]
      const securityIndicators = [/password/i, /token/i, /secret/i, /credential/i, /auth/i, /session/i, /verify.*user/i, /sign/i]

      const lineAndFunc = funcName ? `${funcName} ${line}` : line

      const isChecksum = checksumIndicators.some(p => p.test(lineAndFunc))
      const isSecurity = securityIndicators.some(p => p.test(lineAndFunc))

      // Checksum use without security context = acceptable, don't flag
      if (isChecksum && !isSecurity) {
        return false
      }
      return true
    },
  },
  {
    pattern: /\bSHA1\s*\(/gi,
    name: 'SHA1 Hash Usage',
    severity: 'medium' as const,
    description: 'SHA1 is deprecated and vulnerable to collision attacks',
    fix: 'Use SHA-256 or SHA-3 for hashing.',
  },
  {
    pattern: /createHash\s*\(\s*['"]sha1['"]\s*\)/gi,
    name: 'SHA1 Hash Creation',
    severity: 'medium' as const,
    description: 'SHA1 is deprecated and vulnerable to collision attacks',
    fix: 'Use createHash(\'sha256\') or createHash(\'sha3-256\') instead.',
  },

  // Weak encryption algorithms
  {
    pattern: /\bDES\b(?!ede3|3)/gi,
    name: 'DES Encryption',
    severity: 'high' as const,
    description: 'DES is obsolete and easily broken. Use AES instead.',
    fix: 'Use AES-256-GCM for symmetric encryption.',
    contextCheck: (line: string) => {
      // DES must appear in crypto-related context, not natural language
      // Common French/Spanish word "des" should not trigger this
      const cryptoContexts = [
        /cipher/i,
        /encrypt/i,
        /decrypt/i,
        /crypto/i,
        /algorithm/i,
        /createCipher/i,
        /\bDES\s*[.(]/i,        // DES function call like DES() or DES.encrypt()
        /['"]DES['"]/i,         // DES as string literal
        /DES[-_]/i,             // DES-CBC, DES_KEY, etc.
        /[-_]DES/i,             // 3DES, etc.
      ]
      return cryptoContexts.some(ctx => ctx.test(line))
    },
  },
  {
    pattern: /createCipher(?:iv)?\s*\(\s*['"]des['"]/gi,
    name: 'DES Cipher Creation',
    severity: 'high' as const,
    description: 'DES is obsolete and easily broken',
    fix: 'Use createCipheriv(\'aes-256-gcm\', ...) instead.',
  },
  {
    pattern: /\bRC4\b/gi,
    name: 'RC4 Encryption',
    severity: 'high' as const,
    description: 'RC4 is broken and should never be used',
    fix: 'Use AES-256-GCM for symmetric encryption.',
  },
  {
    pattern: /createCipher(?:iv)?\s*\(\s*['"]rc4['"]/gi,
    name: 'RC4 Cipher Creation',
    severity: 'high' as const,
    description: 'RC4 is broken and should never be used',
    fix: 'Use createCipheriv(\'aes-256-gcm\', ...) instead.',
  },
  {
    pattern: /\bBlowfish\b/gi,
    name: 'Blowfish Encryption',
    severity: 'medium' as const,
    description: 'Blowfish has a small block size and is not recommended for new applications',
    fix: 'Use AES-256-GCM for symmetric encryption.',
  },

  // Insecure random number generation
  {
    pattern: /Math\.random\s*\(\s*\)/g,
    name: 'Math.random() for Security',
    severity: 'high' as const,
    description: 'Math.random() is not cryptographically secure and should not be used for security purposes',
    fix: 'Use crypto.randomBytes() or crypto.getRandomValues() for cryptographic operations.',
    contextCheck: (line: string) => {
      // Exclude load balancing/array selection patterns (not security-critical)
      // Pattern: Math.random() * array.length or Math.random() * count
      const loadBalancingPatterns = [
        /Math\.random\s*\(\s*\)\s*\*\s*\w+\.length/i,        // * array.length
        /Math\.random\s*\(\s*\)\s*\*\s*\w+\.keyLen/i,        // * store.keyLen (API key manager)
        /Math\.random\s*\(\s*\)\s*\*\s*\w+\.size/i,          // * collection.size
        /Math\.random\s*\(\s*\)\s*\*\s*\d+\s*\)/,            // * literal number in expression
        /Math\.floor\s*\(\s*Math\.random\s*\(\s*\)\s*\*\s*\w+\.length/i,  // Math.floor(Math.random() * arr.length)
        /Math\.floor\s*\(\s*Math\.random\s*\(\s*\)\s*\*\s*\w+\.keyLen/i,  // Math.floor(Math.random() * store.keyLen)
        /\[\s*Math\.floor\s*\(\s*Math\.random/i,             // array[Math.floor(Math.random...)]
        /index\s*=\s*Math\.floor\s*\(\s*Math\.random/i,      // index = Math.floor(Math.random...)
        /pick|select|choose|shuffle|sample/i,                 // Load balancing keywords
      ]

      if (loadBalancingPatterns.some(p => p.test(line))) {
        return false  // Don't flag - this is load balancing, not security
      }

      // Exclude fallback patterns where crypto is the primary method
      // Pattern: crypto.randomUUID() ?? Math.random() or crypto.getRandomValues() || Math.random()
      const fallbackPatterns = [
        /crypto\??\.\s*randomUUID\s*\(\s*\)\s*\?\?/i,     // crypto.randomUUID() ??
        /crypto\??\.\s*getRandomValues\s*\([^)]*\)\s*\?\?/i, // crypto.getRandomValues() ??
        /globalThis\.crypto\??\.\s*randomUUID/i,          // globalThis.crypto.randomUUID
        /window\.crypto\??\.\s*randomUUID/i,              // window.crypto.randomUUID
        /\?\.\s*randomUUID\s*\(\s*\)\s*\?\?/i,            // ?.randomUUID() ??
        /randomUUID\s*\(\s*\)\s*\?\?.*Math\.random/i,     // randomUUID() ?? ...Math.random
      ]

      if (fallbackPatterns.some(p => p.test(line))) {
        return false  // Don't flag - Math.random is only a fallback for missing crypto API
      }

      // Only flag if it looks like it's being used for security
      const securityContexts = [
        /token/i, /secret/i, /key/i, /password/i, /salt/i,
        /nonce/i, /iv/i, /random.*id/i, /uuid/i, /session/i,
      ]
      return securityContexts.some(ctx => ctx.test(line))
    },
  },

  // Weak key derivation
  {
    pattern: /pbkdf2.*iterations?\s*[=:]\s*(\d+)/gi,
    name: 'Weak PBKDF2 Iterations',
    severity: 'medium' as const,
    description: 'PBKDF2 with low iteration count is vulnerable to brute force attacks',
    fix: 'Use at least 100,000 iterations for PBKDF2, or switch to Argon2.',
    contextCheck: (line: string, match: RegExpMatchArray) => {
      const iterations = parseInt(match[1], 10)
      return iterations < 10000
    },
  },

  // Weak bcrypt rounds
  {
    pattern: /bcrypt\.hash\s*\([^,]+,\s*(\d+)/gi,
    name: 'Weak bcrypt Rounds',
    severity: 'medium' as const,
    description: 'bcrypt with low cost factor is vulnerable to brute force attacks',
    fix: 'Use at least 10 rounds for bcrypt (12 recommended).',
    contextCheck: (line: string, match: RegExpMatchArray) => {
      const rounds = parseInt(match[1], 10)
      return rounds < 10
    },
  },

  // ECB mode (insecure)
  {
    pattern: /['"]aes-\d+-ecb['"]/gi,
    name: 'AES ECB Mode',
    severity: 'high' as const,
    description: 'ECB mode is insecure as it does not provide semantic security',
    fix: 'Use AES-GCM or AES-CBC with proper IV handling.',
  },

  // Deprecated createCipher (no IV)
  {
    pattern: /createCipher\s*\(/g,
    name: 'Deprecated createCipher',
    severity: 'high' as const,
    description: 'createCipher is deprecated and does not use an IV, making it insecure',
    fix: 'Use createCipheriv() with a random IV instead.',
  },

  // Hardcoded encryption keys
  {
    pattern: /(?:encryption|cipher|aes)[_-]?key\s*[=:]\s*['"][a-zA-Z0-9+/=]{16,}['"]/gi,
    name: 'Hardcoded Encryption Key',
    severity: 'critical' as const,
    description: 'Encryption key is hardcoded in source code',
    fix: 'Store encryption keys in environment variables or a secure key management system.',
  },

  // Hardcoded IVs
  {
    pattern: /\biv\s*[=:]\s*['"][a-zA-Z0-9+/=]{16,}['"]/gi,
    name: 'Hardcoded IV',
    severity: 'high' as const,
    description: 'Initialization vector (IV) should be random for each encryption operation',
    fix: 'Generate a random IV using crypto.randomBytes() for each encryption.',
  },
]

// Check if line is a comment
function isComment(lineContent: string): boolean {
  const trimmed = lineContent.trim()
  return (
    trimmed.startsWith('//') ||
    trimmed.startsWith('#') ||
    trimmed.startsWith('*') ||
    trimmed.startsWith('/*')
  )
}

// Check if this is a Python file
function isPythonFile(filePath: string): boolean {
  return /\.py$/i.test(filePath)
}

/**
 * Check if Python's usedforsecurity=False flag is present
 * Python 3.9+ allows explicitly marking hash usage as non-security
 * Example: hashlib.md5(data, usedforsecurity=False)
 */
function hasUsedForSecurityFalse(line: string): boolean {
  return /usedforsecurity\s*=\s*False/i.test(line)
}

// Check if line is a pattern definition (regex or string literal in detector code)
function isPatternDefinition(lineContent: string): boolean {
  const trimmed = lineContent.trim()
  // Pattern definitions typically look like:
  // pattern: /regex/
  // name: 'string'
  // description: 'string'
  // fix: 'string'
  return (
    trimmed.startsWith('pattern:') ||
    trimmed.startsWith('name:') ||
    trimmed.startsWith('description:') ||
    trimmed.startsWith('fix:') ||
    // Also check for object property assignments with these names
    /^\s*(pattern|name|description|fix|severity)\s*:/.test(lineContent)
  )
}

// Check if file is part of the scanner's own detection code
function isScannerDetectorFile(filePath: string): boolean {
  return (
    filePath.includes('scanner/src/layer1/') ||
    filePath.includes('scanner/src/layer2/') ||
    filePath.includes('scanner/src/layer3/') ||
    filePath.includes('/lib/scanner/layer1/') ||
    filePath.includes('/lib/scanner/layer2/') ||
    filePath.includes('/lib/scanner/layer3/')
  )
}

// Check if file is a test file or fixture
function isTestOrFixtureFile(filePath: string): boolean {
  const lowerPath = filePath.toLowerCase()
  return (
    lowerPath.includes('__tests__') ||
    lowerPath.includes('__mocks__') ||
    lowerPath.includes('/test/') ||
    lowerPath.includes('/tests/') ||
    lowerPath.includes('/fixtures/') ||
    lowerPath.includes('/fixture/') ||
    lowerPath.includes('.test.') ||
    lowerPath.includes('.spec.') ||
    lowerPath.includes('-test.') ||
    lowerPath.includes('-spec.') ||
    lowerPath.includes('benchmark')
  )
}

// Check if file is a locale/translation file
function isLocaleFile(filePath: string): boolean {
  const lowerPath = filePath.toLowerCase()
  return (
    lowerPath.includes('/locales/') ||
    lowerPath.includes('/locale/') ||
    lowerPath.includes('/translations/') ||
    lowerPath.includes('/i18n/') ||
    lowerPath.includes('/lang/') ||
    lowerPath.includes('/languages/') ||
    // Common locale file patterns
    /\/(en|fr|de|es|it|pt|ja|ko|zh|ru|ar|nl|pl|tr|vi|th|id|ms|fil|hi|bn|ta|te|ml|kn|gu|mr|pa|ur|sw|am|or|si|ne|km|my|lo|ka|mk|sr|hr|sl|sk|cs|hu|ro|bg|uk|el|he|fa|ps|ku|ug|yi|mn|bo|dz|dv)[-_][a-z]{2,}\.json$/i.test(lowerPath) ||
    /locale.*\.json$/i.test(lowerPath)
  )
}

/**
 * Find the enclosing function name for a given line
 */
function findEnclosingFunctionName(lines: string[], lineIndex: number): string | undefined {
  // Look backwards for function declaration
  for (let i = lineIndex; i >= 0 && i >= lineIndex - 20; i--) {
    const line = lines[i]
    // Match function declarations: function name(), const name = (), async function name()
    const funcMatch = line.match(/(?:function\s+|const\s+|let\s+|var\s+)(\w+)\s*(?:=\s*(?:async\s*)?\(|=\s*(?:async\s+)?function|\()/)
    if (funcMatch) {
      return funcMatch[1]
    }
    // Match method declarations: name() { or name: function()
    const methodMatch = line.match(/^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/)
    if (methodMatch) {
      return methodMatch[1]
    }
  }
  return undefined
}

export function detectWeakCrypto(
  content: string,
  filePath: string,
  options?: { parsed?: ParsedFile }
): Vulnerability[] {
  const vulnerabilities: Vulnerability[] = []
  const lines = options?.parsed?.lines ?? content.split('\n')
  
  // Skip scanner's own detector files to avoid self-detection
  if (isScannerDetectorFile(filePath)) {
    return vulnerabilities
  }
  
  // Skip test files and fixtures (intentional vulnerable code for testing)
  if (isTestOrFixtureFile(filePath)) {
    return vulnerabilities
  }

  // Skip locale/translation files (natural language text, not crypto code)
  if (isLocaleFile(filePath)) {
    return vulnerabilities
  }

  const isPython = isPythonFile(filePath)

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]

    // Skip comments
    if (isComment(line)) continue

    // Skip pattern definitions (detector rule definitions)
    if (isPatternDefinition(line)) continue

    // Skip Python code with usedforsecurity=False flag
    // Python 3.9+ allows explicitly marking non-security hash usage
    if (isPython && hasUsedForSecurityFalse(line)) {
      continue
    }

    for (const cryptoPattern of WEAK_CRYPTO_PATTERNS) {
      const { pattern, name, severity, description, fix, contextCheck } = cryptoPattern

      // Reset regex state
      const regex = new RegExp(pattern.source, pattern.flags)
      const match = regex.exec(line)

      if (match) {
        // Special handling for Math.random - check multi-line fallback patterns
        if (name === 'Math.random() for Security') {
          // Check previous 2 lines for crypto.randomUUID fallback pattern
          // Pattern: crypto?.randomUUID?.() ?? `...${Math.random()}...`
          const prevLines = lines.slice(Math.max(0, i - 2), i + 1).join(' ')
          const multiLineFallbackPatterns = [
            /crypto\??\.?\s*randomUUID\??\s*\(\s*\)\s*\?\?/i,         // crypto.randomUUID() ??
            /globalThis\.crypto\??\.?\s*randomUUID\??\.?\s*\(/i,      // globalThis.crypto?.randomUUID?.()
            /window\.crypto\??\.?\s*randomUUID\??\.?\s*\(/i,          // window.crypto?.randomUUID?.()
            /\?\.\s*randomUUID\??\.?\s*\(\s*\)\s*\?\?/i,              // ?.randomUUID?.() ??
            /crypto\??\.?\s*getRandomValues\s*\(/i,                    // crypto.getRandomValues()
            /randomUUID\??\.?\s*\(\s*\)\s*\?\?/i,                      // randomUUID?.() ?? (generic)
          ]
          if (multiLineFallbackPatterns.some(p => p.test(prevLines))) {
            continue  // Skip - Math.random is only a fallback for missing crypto API
          }
        }

        // If there's a context check, apply it
        // Pass function name for additional context
        const funcName = findEnclosingFunctionName(lines, i)
        if (contextCheck && !contextCheck(line, match, funcName)) {
          continue
        }

        vulnerabilities.push({
          id: `weak-crypto-${filePath}-${i + 1}-${name}`,
          filePath,
          lineNumber: i + 1,
          lineContent: line.trim(),
          severity,
          category: 'weak_crypto',
          title: name,
          description,
          suggestedFix: fix,
          confidence: 'high',
          baseConfidence: BASE_CONFIDENCE,
          layer: 1,
        source: 'secrets' as const,
        })
        break // Only report one crypto issue per line
      }
    }
  }

  return vulnerabilities
}
