/**
 * Sanitiser Detection — Identifies sanitisation functions in code.
 *
 * Two-pass approach:
 * 1. Scan imports to know which sanitisation libraries are available
 * 2. Scan for sanitiser calls (context-sensitive)
 */

import type { SanitiserInfo, SanitiserTarget } from './taint-types'

// ============================================================================
// Known Sanitiser Libraries
// ============================================================================

interface KnownSanitiser {
  /** Import patterns that indicate the library is available */
  importPatterns: RegExp[]
  /** Function call patterns and what they sanitise */
  callPatterns: { pattern: RegExp; sanitises: SanitiserTarget; name: string }[]
  /** Library identifier */
  library: string
}

const KNOWN_SANITISERS: KnownSanitiser[] = [
  {
    library: 'dompurify',
    importPatterns: [/\bDOMPurify\b/, /\bdompurify\b/, /\bisomorphic-dompurify\b/],
    callPatterns: [
      { pattern: /DOMPurify\.sanitize\s*\(/, sanitises: 'html', name: 'DOMPurify.sanitize' },
      { pattern: /\bsanitize\s*\(/, sanitises: 'html', name: 'sanitize' },
    ],
  },
  {
    library: 'validator',
    importPatterns: [/\bvalidator\b/],
    callPatterns: [
      { pattern: /validator\.escape\s*\(/, sanitises: 'html', name: 'validator.escape' },
    ],
  },
  {
    library: 'escape-html',
    importPatterns: [/\bescape-html\b/, /\bescapeHtml\b/],
    callPatterns: [
      { pattern: /\bescapeHtml\s*\(/, sanitises: 'html', name: 'escapeHtml' },
      { pattern: /\bescapeHTML\s*\(/, sanitises: 'html', name: 'escapeHTML' },
    ],
  },
  {
    library: 'xss',
    importPatterns: [/\bxss\b/],
    callPatterns: [
      { pattern: /\bxss\s*\(/, sanitises: 'html', name: 'xss' },
      { pattern: /\bfilterXSS\s*\(/, sanitises: 'html', name: 'filterXSS' },
    ],
  },
  {
    library: 'zod',
    importPatterns: [/\bzod\b/, /\bfrom\s+['"]zod['"]/],
    callPatterns: [
      { pattern: /\.parse\s*\(/, sanitises: 'all', name: 'zod.parse' },
      { pattern: /\.safeParse\s*\(/, sanitises: 'all', name: 'zod.safeParse' },
    ],
  },
  {
    library: 'joi',
    importPatterns: [/\bjoi\b/, /\bfrom\s+['"]joi['"]/],
    callPatterns: [
      { pattern: /\.validate\s*\(/, sanitises: 'all', name: 'joi.validate' },
    ],
  },
  {
    library: 'yup',
    importPatterns: [/\byup\b/, /\bfrom\s+['"]yup['"]/],
    callPatterns: [
      { pattern: /\.validate\s*\(/, sanitises: 'all', name: 'yup.validate' },
      { pattern: /\.validateSync\s*\(/, sanitises: 'all', name: 'yup.validateSync' },
    ],
  },
  {
    library: 'shell-escape',
    importPatterns: [/\bshell-escape\b/, /\bshellescape\b/],
    callPatterns: [
      { pattern: /\bshellescape\s*\(/, sanitises: 'command', name: 'shellescape' },
    ],
  },
]

// ============================================================================
// Import-Independent Sanitisers (always available)
// ============================================================================

interface UniversalSanitiser {
  pattern: RegExp
  sanitises: SanitiserTarget
  name: string
  detection: 'known_library' | 'name_heuristic'
}

const UNIVERSAL_SANITISERS: UniversalSanitiser[] = [
  // Type coercion (always sanitises)
  { pattern: /\bNumber\s*\(/, sanitises: 'all', name: 'Number', detection: 'known_library' },
  { pattern: /\bparseInt\s*\(/, sanitises: 'all', name: 'parseInt', detection: 'known_library' },
  { pattern: /\bparseFloat\s*\(/, sanitises: 'all', name: 'parseFloat', detection: 'known_library' },
  { pattern: /\bBoolean\s*\(/, sanitises: 'all', name: 'Boolean', detection: 'known_library' },
  { pattern: /\bString\s*\(/, sanitises: 'all', name: 'String', detection: 'known_library' },

  // Path sanitisation
  { pattern: /\bpath\.normalize\s*\(/, sanitises: 'path', name: 'path.normalize', detection: 'known_library' },
  { pattern: /\bpath\.basename\s*\(/, sanitises: 'path', name: 'path.basename', detection: 'known_library' },

  // Python
  { pattern: /\bshlex\.quote\s*\(/, sanitises: 'command', name: 'shlex.quote', detection: 'known_library' },
  { pattern: /\bhtml\.escape\s*\(/, sanitises: 'html', name: 'html.escape', detection: 'known_library' },
  { pattern: /\bmarkup_safe\.escape\s*\(/, sanitises: 'html', name: 'markupsafe.escape', detection: 'known_library' },

  // Name heuristic — functions with "sanitize", "escape", "encode" in name
  { pattern: /\bsanitize\w*\s*\(/, sanitises: 'all', name: 'sanitize*', detection: 'name_heuristic' },
  { pattern: /\bescape\w*\s*\(/, sanitises: 'html', name: 'escape*', detection: 'name_heuristic' },
  { pattern: /\bencode(?:URI|URIComponent)\s*\(/, sanitises: 'url', name: 'encodeURI*', detection: 'known_library' },
]

// ============================================================================
// Parameterised Query Detection
// ============================================================================

const PARAMETERISED_QUERY_PATTERNS: RegExp[] = [
  // Placeholder-based: .query('SELECT ... WHERE id = ?', [value])
  /\.query\s*\(\s*['"`][^'"`]*\?\s*[^'"`]*['"`]\s*,\s*\[/,
  // $1-style: .query('SELECT ... WHERE id = $1', [value])
  /\.query\s*\(\s*['"`][^'"`]*\$\d+\s*[^'"`]*['"`]\s*,\s*\[/,
  // Sequelize replacements: .query('...', { replacements: ... })
  /\.query\s*\([^)]*\breplacements\s*:/,
  // Knex parameterised: .where('id', value) or .where({ id: value })
  /\.where\s*\(\s*['"`]\w+['"`]\s*,/,
  /\.where\s*\(\s*\{/,
]

// ============================================================================
// Main Detection Functions
// ============================================================================

/**
 * Detect sanitisation functions available in a file.
 *
 * Pass 1: Scan imports to determine which libraries are available.
 * Pass 2: Find sanitiser call sites.
 */
export function detectSanitisers(content: string, _filePath: string): SanitiserInfo[] {
  const lines = content.split('\n')
  const sanitisers: SanitiserInfo[] = []
  const seen = new Set<string>()

  // Pass 1: Determine which libraries are imported (only check import/require lines)
  const availableLibraries = new Set<string>()
  for (const line of lines) {
    const trimmed = line.trim()
    if (!trimmed.startsWith('import') && !trimmed.includes('require(') && !trimmed.startsWith('from')) {
      continue
    }
    for (const lib of KNOWN_SANITISERS) {
      if (lib.importPatterns.some(p => p.test(line))) {
        availableLibraries.add(lib.library)
      }
    }
  }

  // Pass 2: Find sanitiser calls
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]
    const lineNumber = i + 1
    const trimmed = line.trim()

    // Skip comments
    if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
      continue
    }

    // Check known library sanitisers (only if library is imported)
    for (const lib of KNOWN_SANITISERS) {
      if (!availableLibraries.has(lib.library)) continue

      for (const cp of lib.callPatterns) {
        if (cp.pattern.test(line)) {
          const key = `${lineNumber}:${cp.name}`
          if (seen.has(key)) continue
          seen.add(key)

          sanitisers.push({
            name: cp.name,
            line: lineNumber,
            sanitises: cp.sanitises,
            detection: 'known_library',
            library: lib.library,
          })
        }
      }
    }

    // Check universal sanitisers (always available)
    for (const us of UNIVERSAL_SANITISERS) {
      if (us.pattern.test(line)) {
        const key = `${lineNumber}:${us.name}`
        if (seen.has(key)) continue
        seen.add(key)

        sanitisers.push({
          name: us.name,
          line: lineNumber,
          sanitises: us.sanitises,
          detection: us.detection,
        })
      }
    }

    // Check parameterised queries
    for (const pqp of PARAMETERISED_QUERY_PATTERNS) {
      if (pqp.test(line)) {
        const key = `${lineNumber}:parameterised_query`
        if (seen.has(key)) continue
        seen.add(key)

        sanitisers.push({
          name: 'parameterised_query',
          line: lineNumber,
          sanitises: 'sql',
          detection: 'known_library',
        })
      }
    }
  }

  return sanitisers
}

/**
 * Check if a specific line contains a sanitiser call.
 *
 * Used during taint propagation to determine if a variable is sanitised
 * at a particular assignment.
 */
export function isSanitiserCall(
  line: string,
  sanitisers: SanitiserInfo[]
): { sanitised: boolean; method?: string; target?: SanitiserTarget } {
  // Check against known sanitisers found in the file
  for (const s of sanitisers) {
    // Check both by exact name match and by pattern
    if (line.includes(s.name.replace('*', ''))) {
      return { sanitised: true, method: s.name, target: s.sanitises }
    }
  }

  // Check universal sanitisers directly
  for (const us of UNIVERSAL_SANITISERS) {
    if (us.pattern.test(line)) {
      return { sanitised: true, method: us.name, target: us.sanitises }
    }
  }

  // Check parameterised queries
  for (const pqp of PARAMETERISED_QUERY_PATTERNS) {
    if (pqp.test(line)) {
      return { sanitised: true, method: 'parameterised_query', target: 'sql' }
    }
  }

  return { sanitised: false }
}
