/**
 * Layer 2: XXE (XML External Entity) Detector
 * Detects XML parsing without entity expansion protection
 *
 * OWASP A02:2021 - Cryptographic Failures (was A04 in 2017)
 * CWE-611: Improper Restriction of XML External Entity Reference
 */

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.55

interface XXEOptions {
  parsed?: ParsedFile
}

interface XXEPattern {
  /** Regex to detect XML parser usage */
  pattern: RegExp
  /** Programming language */
  lang: 'node' | 'python' | 'java' | 'php'
  /** Library name for reporting */
  lib: string
  /** Regex for safe configuration that suppresses the finding */
  safe?: RegExp
  /** Default severity (can be overridden) */
  severity?: VulnerabilitySeverity
  /** File extensions this applies to */
  extensions?: string[]
}

const XXE_PATTERNS: XXEPattern[] = [
  // ==================== Node.js ====================
  {
    pattern: /xml2js\.parseString|new\s+xml2js\.Parser/,
    lang: 'node',
    lib: 'xml2js',
    safe: /explicitCharkey|xmlMode/,
    severity: 'medium', // xml2js v0.5+ doesn't expand entities by default
    extensions: ['.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx'],
  },
  {
    pattern: /libxmljs\.parseXml/,
    lang: 'node',
    lib: 'libxmljs',
    safe: /noent\s*:\s*false/,
    severity: 'high',
    extensions: ['.js', '.ts', '.mjs', '.cjs'],
  },
  {
    pattern: /new\s+DOMParser\(\)\.parseFromString/,
    lang: 'node',
    lib: 'DOMParser',
    severity: 'low', // Browsers block XXE by default
    extensions: ['.js', '.ts', '.jsx', '.tsx'],
  },
  {
    pattern: /(?:xmldom|@xmldom\/xmldom)/,
    lang: 'node',
    lib: 'xmldom',
    safe: /entityExpansionEnabled\s*:\s*false/,
    severity: 'medium',
    extensions: ['.js', '.ts', '.mjs', '.cjs'],
  },
  {
    pattern: /(?:fast-xml-parser|XMLParser)/,
    lang: 'node',
    lib: 'fast-xml-parser',
    safe: /processEntities\s*:\s*false/,
    severity: 'medium',
    extensions: ['.js', '.ts', '.mjs', '.cjs'],
  },

  // ==================== Python ====================
  {
    pattern: /xml\.etree\.ElementTree\.parse|ET\.parse|etree\.parse/,
    lang: 'python',
    lib: 'xml.etree.ElementTree',
    safe: /defusedxml/,
    severity: 'high',
    extensions: ['.py'],
  },
  {
    pattern: /lxml\.etree\.parse|etree\.fromstring/,
    lang: 'python',
    lib: 'lxml.etree',
    safe: /resolve_entities\s*=\s*False|defusedxml/,
    severity: 'high',
    extensions: ['.py'],
  },
  {
    pattern: /xml\.sax\.parse|xml\.dom\.minidom\.parse/,
    lang: 'python',
    lib: 'xml.sax/minidom',
    safe: /defusedxml/,
    severity: 'high',
    extensions: ['.py'],
  },
  {
    pattern: /xml\.dom\.pulldom/,
    lang: 'python',
    lib: 'xml.dom.pulldom',
    safe: /defusedxml/,
    severity: 'high',
    extensions: ['.py'],
  },

  // ==================== Java ====================
  {
    pattern: /DocumentBuilderFactory\.newInstance/,
    lang: 'java',
    lib: 'DocumentBuilderFactory',
    safe: /disallow-doctype-decl|FEATURE_SECURE_PROCESSING/,
    severity: 'high',
    extensions: ['.java'],
  },
  {
    pattern: /SAXParserFactory\.newInstance/,
    lang: 'java',
    lib: 'SAXParserFactory',
    safe: /disallow-doctype-decl|external-general-entities/,
    severity: 'high',
    extensions: ['.java'],
  },
  {
    pattern: /XMLInputFactory\.newInstance/,
    lang: 'java',
    lib: 'XMLInputFactory',
    safe: /IS_SUPPORTING_EXTERNAL_ENTITIES|SUPPORT_DTD/,
    severity: 'high',
    extensions: ['.java'],
  },
  {
    pattern: /TransformerFactory\.newInstance/,
    lang: 'java',
    lib: 'TransformerFactory',
    safe: /ACCESS_EXTERNAL_DTD|ACCESS_EXTERNAL_STYLESHEET/,
    severity: 'high',
    extensions: ['.java'],
  },

  // ==================== PHP ====================
  {
    pattern: /simplexml_load_string|simplexml_load_file/,
    lang: 'php',
    lib: 'simplexml',
    safe: /libxml_disable_entity_loader\s*\(\s*true\s*\)|LIBXML_NOENT/,
    severity: 'high',
    extensions: ['.php'],
  },
  {
    pattern: /DOMDocument.*(?:loadXML|load)\b/,
    lang: 'php',
    lib: 'DOMDocument',
    safe: /libxml_disable_entity_loader\s*\(\s*true\s*\)/,
    severity: 'high',
    extensions: ['.php'],
  },
  {
    pattern: /XMLReader.*open/,
    lang: 'php',
    lib: 'XMLReader',
    safe: /setParserProperty.*LOADDTD.*false/,
    severity: 'high',
    extensions: ['.php'],
  },
]

/**
 * Check if the file extension matches the expected language
 */
function matchesLanguageExtension(filePath: string, extensions?: string[]): boolean {
  if (!extensions) return true
  return extensions.some(ext => filePath.endsWith(ext))
}

/**
 * Check surrounding context for safe configuration
 */
function hasSafeConfig(
  content: string,
  lines: string[],
  lineIndex: number,
  safePattern: RegExp | undefined,
  lang: string
): boolean {
  if (!safePattern) return false

  // For Python: check entire file for defusedxml import
  if (lang === 'python' && /defusedxml/.test(safePattern.source)) {
    if (/import\s+defusedxml|from\s+defusedxml/.test(content)) {
      return true
    }
  }

  // Check surrounding context (±20 lines for Java setFeature, ±10 for others)
  const windowSize = lang === 'java' ? 20 : 10
  const start = Math.max(0, lineIndex - windowSize)
  const end = Math.min(lines.length, lineIndex + windowSize + 1)
  const context = lines.slice(start, end).join('\n')

  return safePattern.test(context)
}

/**
 * Detect XXE patterns in code
 */
export function detectXXEPatterns(
  content: string,
  filePath: string,
  options?: XXEOptions
): Vulnerability[] {
  const vulnerabilities: Vulnerability[] = []

  if (isScannerOrFixtureFile(filePath)) return vulnerabilities

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

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

    for (const xxePattern of XXE_PATTERNS) {
      // Check language extension match
      if (!matchesLanguageExtension(filePath, xxePattern.extensions)) continue

      if (!xxePattern.pattern.test(line)) continue

      // Check for safe configuration
      if (hasSafeConfig(content, lines, i, xxePattern.safe, xxePattern.lang)) continue

      let severity: VulnerabilitySeverity = xxePattern.severity || 'high'

      // Downgrade for test files
      if (isTestFile) {
        severity = 'info'
      }

      // DOMParser in .tsx client files is inherently safe
      if (xxePattern.lib === 'DOMParser' && (filePath.endsWith('.tsx') || filePath.endsWith('.jsx'))) {
        severity = 'info'
      }

      vulnerabilities.push({
        id: `xxe-${filePath}-${i + 1}-${xxePattern.lib}`,
        filePath,
        lineNumber: i + 1,
        lineContent: line.trim(),
        severity,
        category: 'xxe',
        title: `XXE risk: ${xxePattern.lib} XML parser without entity protection`,
        description:
          `${xxePattern.lib} XML parser is used without disabling external entity processing. An attacker could supply malicious XML to read server files, perform SSRF, or cause denial-of-service.`,
        suggestedFix: getSuggestedFix(xxePattern.lang, xxePattern.lib),
        confidence: severity === 'high' ? 'high' : 'medium',
        baseConfidence: BASE_CONFIDENCE,
        layer: 2,
        source: 'structural' as const,
        requiresAIValidation: severity !== 'high',
      })

      break // One finding per line
    }
  }

  return vulnerabilities
}

function getSuggestedFix(lang: string, lib: string): string {
  switch (lang) {
    case 'python':
      return 'Use defusedxml instead of the standard xml library: pip install defusedxml && from defusedxml.ElementTree import parse'
    case 'java':
      return 'Disable external entities: factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)'
    case 'php':
      return 'Disable entity loading: libxml_disable_entity_loader(true) before parsing XML'
    case 'node':
      if (lib === 'fast-xml-parser') {
        return 'Disable entity processing: new XMLParser({ processEntities: false })'
      }
      if (lib === 'xml2js') {
        return 'Consider upgrading to xml2js v0.5+ which has safer defaults, or use fast-xml-parser with processEntities: false'
      }
      return 'Disable external entity processing in the XML parser configuration'
    default:
      return 'Disable external entity processing in the XML parser configuration'
  }
}
