/**
 * Schema Semantics Utility
 * Understands validation schema logic, particularly Zod/Yup/Joi patterns
 *
 * This addresses false positives where OR validation (either A or B required)
 * is flagged as "bypass" when individual fields are marked optional.
 *
 * Key insight: `.optional()` + `.refine()` = OR validation, not bypass
 */

export interface SchemaAnalysis {
  /** Whether the schema has cross-field refinement/validation */
  hasRefinement: boolean
  /** Type of refinement detected */
  refinementType?: 'or_logic' | 'super_refine' | 'custom_validation' | 'none'
  /** Number of optional fields in the schema */
  optionalFieldCount: number
  /** Whether this looks like legitimate OR validation */
  isOrValidation: boolean
}

/**
 * Patterns that indicate cross-field validation refinements
 */
const REFINEMENT_PATTERNS = [
  // Zod .refine() with OR logic
  /\.refine\s*\(\s*\([^)]*\)\s*=>\s*[^,]*\|\|/,
  /\.refine\s*\(\s*\(\s*\{\s*\w+\s*,/,  // Destructured refine
  /\.refine\s*\(\s*\(?data\)?\s*=>/,    // data => refine

  // Zod .superRefine()
  /\.superRefine\s*\(/,

  // Yup .test() with cross-field validation
  /\.test\s*\(\s*['"][^'"]+['"]\s*,\s*['"][^'"]+['"]\s*,\s*function/,
  /\.test\s*\(\s*['"][^'"]+['"]\s*,\s*['"][^'"]+['"]\s*,\s*\([^)]*\)\s*=>/,

  // Joi custom validation
  /\.custom\s*\(/,
  /Joi\.alternatives\s*\(\)/,
  /\.xor\s*\(/,
  /\.or\s*\(/,
]

/**
 * Patterns indicating OR logic in refinement callbacks
 */
const OR_LOGIC_PATTERNS = [
  // Direct OR in refine: (data) => data.a || data.b
  /=>\s*[^,;]*\|\|/,
  // Negated AND: !(a && b)
  /=>\s*!\s*\([^)]*&&/,
  // Ternary with OR
  /\?\s*[^:]*\|\|/,
]

/**
 * Find the start of a schema definition containing a given line
 */
function findSchemaStart(lines: string[], lineNumber: number): number {
  // Look backwards for schema definition patterns
  const schemaStartPatterns = [
    /(?:const|let|var)\s+\w+\s*=\s*z\.object\s*\(/,
    /(?:const|let|var)\s+\w+\s*=\s*Yup\.object\s*\(/,
    /(?:const|let|var)\s+\w+\s*=\s*Joi\.object\s*\(/,
    /export\s+(?:const|let|var)\s+\w+\s*=\s*z\.object\s*\(/,
    /z\.object\s*\(\s*\{/,
    /Yup\.object\s*\(\s*\{/,
    /Joi\.object\s*\(\s*\{/,
  ]

  for (let i = lineNumber; i >= 0; i--) {
    if (schemaStartPatterns.some(pattern => pattern.test(lines[i]))) {
      return i
    }
    // Don't look back more than 50 lines
    if (lineNumber - i > 50) break
  }

  return Math.max(0, lineNumber - 10)
}

/**
 * Find the end of a schema definition containing a given line
 */
function findSchemaEnd(lines: string[], schemaStart: number): number {
  let braceCount = 0
  let inSchema = false

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

    // Count braces/parens
    for (const char of line) {
      if (char === '(' || char === '{') {
        braceCount++
        inSchema = true
      } else if (char === ')' || char === '}') {
        braceCount--
      }
    }

    // Schema ends when we return to zero brace count after entering
    if (inSchema && braceCount === 0) {
      return i + 1
    }

    // Don't look forward more than 100 lines
    if (i - schemaStart > 100) break
  }

  return Math.min(lines.length, schemaStart + 50)
}

/**
 * Count .optional() calls in content
 */
function countOptionalFields(content: string): number {
  const matches = content.match(/\.optional\s*\(\s*\)/g)
  return matches ? matches.length : 0
}

/**
 * Analyze a validation schema for OR-logic refinement patterns
 *
 * @param content - The full file content
 * @param lineNumber - The 0-indexed line number where the pattern was found
 * @returns SchemaAnalysis with refinement information
 */
export function analyzeSchemaSemantics(
  content: string,
  lineNumber: number
): SchemaAnalysis {
  const lines = content.split('\n')

  // Find the schema boundaries
  const schemaStart = findSchemaStart(lines, lineNumber)
  const schemaEnd = findSchemaEnd(lines, schemaStart)

  // Extract schema content
  const schemaLines = lines.slice(schemaStart, schemaEnd)
  const schemaContent = schemaLines.join('\n')

  // Check for refinement patterns
  const hasRefine = REFINEMENT_PATTERNS.some(pattern => pattern.test(schemaContent))
  const hasSuperRefine = /\.superRefine\s*\(/.test(schemaContent)
  const hasOrLogic = OR_LOGIC_PATTERNS.some(pattern => pattern.test(schemaContent))

  // Count optional fields
  const optionalCount = countOptionalFields(schemaContent)

  // Determine if this is OR validation
  // OR validation pattern: multiple optional fields + refine with || logic
  const isOrValidation = optionalCount >= 2 && hasRefine && hasOrLogic

  // Determine refinement type
  let refinementType: SchemaAnalysis['refinementType'] = 'none'
  if (hasSuperRefine) {
    refinementType = 'super_refine'
  } else if (hasOrLogic && hasRefine) {
    refinementType = 'or_logic'
  } else if (hasRefine) {
    refinementType = 'custom_validation'
  }

  return {
    hasRefinement: hasRefine || hasSuperRefine,
    refinementType,
    optionalFieldCount: optionalCount,
    isOrValidation,
  }
}

/**
 * Check if a line with .optional() is part of legitimate OR validation
 *
 * @param content - The full file content
 * @param lineNumber - The 0-indexed line number where .optional() was found
 * @returns true if this is part of OR validation (not a bypass)
 */
export function isLegitimateOrValidation(
  content: string,
  lineNumber: number
): boolean {
  const analysis = analyzeSchemaSemantics(content, lineNumber)
  return analysis.isOrValidation || analysis.refinementType === 'super_refine'
}

/**
 * Check if a 2FA-related field with .optional() has proper OR validation
 * Specifically for 2FA patterns where either totpCode OR backupCode is required
 *
 * @param content - The full file content
 * @param lineNumber - The 0-indexed line number
 * @returns true if this is legitimate 2FA OR validation
 */
export function is2FAOrValidation(
  content: string,
  lineNumber: number
): boolean {
  const lines = content.split('\n')
  const line = lines[lineNumber] || ''

  // Check if this line is about 2FA fields
  const is2FAField = /\b(totp|otp|backupCode|recoveryCode|twoFactor|2fa|mfa)\b/i.test(line)
  if (!is2FAField) {
    return false
  }

  // Check for OR validation in the schema
  const analysis = analyzeSchemaSemantics(content, lineNumber)

  // If there's refinement with OR logic, this is legitimate
  if (analysis.isOrValidation) {
    return true
  }

  // Check for specific 2FA OR patterns in nearby content
  const schemaStart = findSchemaStart(lines, lineNumber)
  const schemaEnd = findSchemaEnd(lines, schemaStart)
  const schemaContent = lines.slice(schemaStart, schemaEnd).join('\n')

  // Patterns like: "Either TOTP or backup code required"
  const twoFAOrPatterns = [
    /either.*or.*required/i,
    /totp.*\|\|.*backup/i,
    /backup.*\|\|.*totp/i,
    /code.*\|\|.*code/i,
    /one\s+of.*required/i,
  ]

  return twoFAOrPatterns.some(pattern => pattern.test(schemaContent))
}
