/**
 * Inline Suppression Comment Parser
 * Parses oculum-ignore comments from source code
 */

import type { InlineSuppression } from './types'
import type { VulnerabilityCategory } from '../../shared/types'

/**
 * Valid suppression comment patterns:
 *
 * Single-line suppressions:
 *   // oculum-ignore-next-line: reason here
 *   // oculum-ignore-next-line [rule-id]: reason here
 *   code // oculum-ignore: reason here
 *   code // oculum-ignore [rule-id]: reason here
 *
 * Block suppressions:
 *   /* oculum-ignore-block: reason here * /
 *   ... code ...
 *   /* oculum-ignore-block-end * /
 *
 * Also supports:
 *   # oculum-ignore-next-line: reason (Python, Ruby, YAML, etc.)
 *   -- oculum-ignore-next-line: reason (SQL)
 */

// Regex patterns for parsing comments
const PATTERNS = {
  // // oculum-ignore-next-line: reason
  // // oculum-ignore-next-line [rule-id]: reason
  nextLine: /(?:\/\/|#|--)\s*oculum-ignore-next-line(?:\s*\[([^\]]+)\])?\s*:\s*(.+)/i,

  // code // oculum-ignore: reason
  // code // oculum-ignore [rule-id]: reason
  sameLine: /(?:\/\/|#|--)\s*oculum-ignore(?:\s*\[([^\]]+)\])?\s*:\s*(.+)/i,

  // /* oculum-ignore-block: reason */
  // /* oculum-ignore-block [rule-id]: reason */
  blockStart: /\/\*\s*oculum-ignore-block(?:\s*\[([^\]]+)\])?\s*:\s*([^*]+)\*\//i,

  // /* oculum-ignore-block-end */
  blockEnd: /\/\*\s*oculum-ignore-block-end\s*\*\//i,
}

/**
 * Parse a single line for suppression comments
 */
function parseLineForSuppression(
  line: string,
  lineNumber: number
): InlineSuppression | null {
  // Check for next-line suppression (must be the only thing on the line, aside from whitespace)
  const trimmed = line.trim()

  // Next-line pattern (comment at start of line)
  if (
    trimmed.startsWith('//') ||
    trimmed.startsWith('#') ||
    trimmed.startsWith('--')
  ) {
    const nextLineMatch = trimmed.match(PATTERNS.nextLine)
    if (nextLineMatch) {
      return {
        lineNumber,
        type: 'next-line',
        ruleId: nextLineMatch[1] as VulnerabilityCategory | undefined,
        reason: nextLineMatch[2].trim(),
        commentText: trimmed,
      }
    }
  }

  // Same-line pattern (comment at end of line with code before)
  // Only check if there's actual code before the comment
  const sameLineMatch = line.match(PATTERNS.sameLine)
  if (sameLineMatch) {
    // Find where the comment starts
    const commentIndex = line.search(/(?:\/\/|#|--)\s*oculum-ignore/i)
    const beforeComment = line.substring(0, commentIndex).trim()

    // Only treat as same-line if there's code before the comment
    // (not just whitespace or if it's at the start)
    if (beforeComment.length > 0) {
      return {
        lineNumber,
        type: 'same-line',
        ruleId: sameLineMatch[1] as VulnerabilityCategory | undefined,
        reason: sameLineMatch[2].trim(),
        commentText: line.substring(commentIndex).trim(),
      }
    }
  }

  // Block start pattern
  const blockStartMatch = line.match(PATTERNS.blockStart)
  if (blockStartMatch) {
    return {
      lineNumber,
      type: 'block-start',
      ruleId: blockStartMatch[1] as VulnerabilityCategory | undefined,
      reason: blockStartMatch[2].trim(),
      commentText: blockStartMatch[0],
    }
  }

  // Block end pattern
  const blockEndMatch = line.match(PATTERNS.blockEnd)
  if (blockEndMatch) {
    return {
      lineNumber,
      type: 'block-end',
      reason: '', // Block end has no reason
      commentText: blockEndMatch[0],
    }
  }

  return null
}

/**
 * Parse all inline suppressions from file content
 *
 * Returns a map of line numbers to their effective suppressions.
 * A line can be suppressed by:
 * 1. A same-line comment (oculum-ignore)
 * 2. A next-line comment on the previous line (oculum-ignore-next-line)
 * 3. Being within a block suppression (oculum-ignore-block ... oculum-ignore-block-end)
 */
export function parseInlineSuppressions(content: string): Map<number, InlineSuppression> {
  const lines = content.split('\n')
  const suppressions = new Map<number, InlineSuppression>()

  // Track block suppression state
  let currentBlock: { startLine: number; reason: string; ruleId?: VulnerabilityCategory } | null = null

  for (let i = 0; i < lines.length; i++) {
    const lineNumber = i + 1 // 1-indexed
    const line = lines[i]

    const suppression = parseLineForSuppression(line, lineNumber)

    if (suppression) {
      switch (suppression.type) {
        case 'next-line':
          // Apply to the next line
          if (i + 1 < lines.length) {
            suppressions.set(lineNumber + 1, {
              ...suppression,
              lineNumber: lineNumber + 1,
            })
          }
          break

        case 'same-line':
          // Apply to this line
          suppressions.set(lineNumber, suppression)
          break

        case 'block-start':
          // Start tracking block suppression
          currentBlock = {
            startLine: lineNumber,
            reason: suppression.reason,
            ruleId: suppression.ruleId,
          }
          break

        case 'block-end':
          // End block suppression
          currentBlock = null
          break
      }
    }

    // If we're in a block suppression, add suppression for this line
    // (unless it's the block start/end line itself)
    if (currentBlock && suppression?.type !== 'block-start' && suppression?.type !== 'block-end') {
      // Don't override explicit same-line suppressions
      if (!suppressions.has(lineNumber)) {
        suppressions.set(lineNumber, {
          lineNumber,
          type: 'block-start', // Mark as from block
          reason: currentBlock.reason,
          ruleId: currentBlock.ruleId,
          commentText: `Block suppression from line ${currentBlock.startLine}`,
        })
      }
    }
  }

  return suppressions
}

/**
 * Check if a specific line is suppressed
 */
export function isLineSuppressed(
  suppressions: Map<number, InlineSuppression>,
  lineNumber: number,
  category?: VulnerabilityCategory
): InlineSuppression | null {
  const suppression = suppressions.get(lineNumber)

  if (!suppression) {
    return null
  }

  // If suppression specifies a rule and category doesn't match, not suppressed
  if (suppression.ruleId && category && suppression.ruleId !== category) {
    return null
  }

  return suppression
}

/**
 * Extract all unique suppression reasons from a file
 * Useful for auditing suppressed findings
 */
export function extractSuppressionReasons(content: string): Array<{
  reason: string
  ruleId?: VulnerabilityCategory
  lineNumber: number
  type: InlineSuppression['type']
}> {
  const suppressions = parseInlineSuppressions(content)
  const seen = new Set<string>()
  const results: Array<{
    reason: string
    ruleId?: VulnerabilityCategory
    lineNumber: number
    type: InlineSuppression['type']
  }> = []

  for (const [lineNumber, suppression] of suppressions) {
    // Only include unique comment sources (not duplicates from block propagation)
    const key = `${suppression.commentText}`
    if (!seen.has(key) && suppression.type !== 'block-end') {
      seen.add(key)
      results.push({
        reason: suppression.reason,
        ruleId: suppression.ruleId,
        lineNumber,
        type: suppression.type,
      })
    }
  }

  return results
}

/**
 * Generate a suppression comment for a given line
 */
export function generateSuppressionComment(
  reason: string,
  options: {
    type?: 'next-line' | 'same-line'
    ruleId?: VulnerabilityCategory
    commentStyle?: '//' | '#' | '--'
  } = {}
): string {
  const { type = 'next-line', ruleId, commentStyle = '//' } = options

  const ruleSpec = ruleId ? ` [${ruleId}]` : ''

  if (type === 'next-line') {
    return `${commentStyle} oculum-ignore-next-line${ruleSpec}: ${reason}`
  }

  return `${commentStyle} oculum-ignore${ruleSpec}: ${reason}`
}
