/**
 * Layer 2: RAG Data Safety Detection
 * Detects data exfiltration risks in Retrieval Augmented Generation systems
 *
 * Covers:
 * - M5.1: RAG data exfiltration (cross-tenant retrieval, raw context exposure)
 * - Unscoped vector store queries
 * - Raw retrieved context in responses
 * - Context logging risks
 */

import type { Vulnerability, VulnerabilitySeverity, VulnerabilityCategory } from '../../shared/types'
import type { ParsedFile } from '../../shared/parsed-file'
import {
  isComment,
  isTestOrMockFile,
  isDocumentationFile,
  isScannerOrFixtureFile,
  isExampleDirectory,
  isLibraryCode,
} from '../../parse/file-classifier'

const BASE_CONFIDENCE = 0.45

// ============================================================================
// Context Detection
// ============================================================================

/**
 * Check if file uses client-side fuzzy search libraries (not vector stores)
 * These are safe local search implementations, not cross-tenant data access risks
 */
function isClientSideFuzzySearch(content: string): boolean {
  const fuzzySearchPatterns = [
    // Fuse.js - client-side fuzzy search
    /import.*from\s+['"]fuse\.js['"]/i,
    /require\s*\(\s*['"]fuse\.js['"]\s*\)/i,
    /new\s+Fuse\s*\(/i,
    // Other client-side search libraries
    /import.*from\s+['"]flexsearch['"]/i,
    /import.*from\s+['"]lunr['"]/i,
    /import.*from\s+['"]minisearch['"]/i,
    /import.*from\s+['"]fuzzysort['"]/i,
    /import.*from\s+['"]match-sorter['"]/i,
  ]
  return fuzzySearchPatterns.some(p => p.test(content))
}

/**
 * Check if a line contains a generic query pattern that is NOT a vector store query
 * These are common web framework patterns that should not be flagged as RAG issues
 */
function isGenericQueryPattern(lineContent: string): boolean {
  const genericQueryPatterns = [
    // Express/Hono/Koa query params
    /req\.query\s*\(/i,
    /c\.req\.query\s*\(/i,
    /ctx\.query\s*\(/i,
    /request\.query\s*\(/i,
    // URL search params
    /searchParams\.get\s*\(/i,
    /url\.searchParams/i,
    /URLSearchParams/i,
    // Query string parsing
    /querystring\.parse/i,
    /qs\.parse/i,
    // Database query builders (not vector stores)
    /\.query\s*\(\s*['"`]SELECT/i,
    /\.query\s*\(\s*['"`]INSERT/i,
    /\.query\s*\(\s*['"`]UPDATE/i,
    /\.query\s*\(\s*['"`]DELETE/i,
    // GraphQL queries
    /graphql.*query/i,
    /useQuery\s*\(/i,
    /useLazyQuery\s*\(/i,
    // tRPC/React Query
    /trpc\.\w+\.\w+\.query/i,
    /\.useQuery\s*\(/i,
    // Prisma/Drizzle queries
    /prisma\.\w+\.findMany/i,
    /db\.query\./i,
    // Generic method chaining that isn't vector search
    /\.query\s*\(\s*\)/i,  // Empty query call
  ]
  return genericQueryPatterns.some(p => p.test(lineContent))
}

/**
 * Check if file has vector store imports (required for RAG detection)
 */
function hasVectorStoreImport(content: string): boolean {
  const vectorStoreImports = [
    /from\s+['"]pinecone/i,
    /from\s+['"]@pinecone-database/i,
    /from\s+['"]weaviate/i,
    /from\s+['"]chromadb/i,
    /from\s+['"]@qdrant/i,
    /from\s+['"]qdrant/i,
    /from\s+['"]@langchain\/vectorstores/i,
    /from\s+['"]langchain\/vectorstores/i,
    /from\s+['"]faiss/i,
    /from\s+['"]milvus/i,
    /from\s+['"]@supabase.*vector/i,
    /pgvector/i,
    /VectorStore/i,
    /Embeddings/i,
  ]
  return vectorStoreImports.some(p => p.test(content))
}

/**
 * Check if a file is in a RAG/retrieval context based on path and content
 */
function isRAGContextFile(filePath: string, content: string): boolean {
  // Skip client-side fuzzy search libraries - these are NOT vector stores
  if (isClientSideFuzzySearch(content)) {
    return false
  }

  // Must have vector store imports to be considered RAG context
  if (!hasVectorStoreImport(content)) {
    return false
  }

  // File path indicators of RAG code
  const ragPathPatterns = [
    /\/(rag|retrieval|retriever|embedding|vector|knowledge)\//i,
    /\/(search|index|indexer|embeddings?)\//i,
    /(rag|retriever|embedding|vector|knowledge).*\.(ts|js|tsx|jsx|py)$/i,
    /(search|retrieval|indexer).*\.(ts|js|tsx|jsx|py)$/i,
  ]

  if (ragPathPatterns.some(p => p.test(filePath))) {
    return true
  }

  // Content patterns suggesting RAG usage - must be actual vector store clients
  const ragContentPatterns = [
    // Vector store patterns - specific to actual vector DBs
    /VectorStore|Embeddings?|Retriever/i,
    /similaritySearch|query_engine|retriever/i,
    /vectorStore|embeddingModel|documentLoader/i,
    // Framework imports - actual vector store SDKs
    /from\s+['"](?:langchain|llama[-_]?index|@pinecone|@qdrant|chromadb|weaviate)/i,
    /import.*(?:Pinecone|Chroma|Weaviate|Qdrant|Milvus|PGVector)/i,
    // Vercel AI SDK RAG
    /VercelKVVectorStore|SupabaseVectorStore|createEmbedding/i,
    // Query patterns - but NOT generic .search() which could be Fuse.js
    /\.retrieve\(|\.query\(/i,
    /sourceDocuments|retrievedDocs|retrievedChunks/i,
    // Supabase vector search
    /\.rpc\s*\(\s*['"`]match_documents/i,
    /pgvector|embedding.*vector/i,
  ]

  return ragContentPatterns.some(p => p.test(content))
}

/**
 * Check if line/context has access control scoping
 */
function hasAccessControlScoping(context: string): boolean {
  const accessPatterns = [
    // User/tenant scoping
    /userId|user_id|user\.id|currentUser/i,
    /tenantId|tenant_id|tenant\.id|orgId|org_id|workspaceId/i,
    // Filter parameters
    /filter\s*[:=]\s*\{[^}]*(?:user|tenant|org)/i,
    /where\s*[:=].*(?:user|tenant|org)/i,
    /metadata\s*[:=].*(?:user|tenant|org)/i,
    /namespace\s*[:=]/i,
    // Access check functions
    /checkAccess|verifyPermission|canRead|canAccess|hasAccess/i,
    /getAuthorized|filterByUser|filterByTenant/i,
  ]
  return accessPatterns.some(p => p.test(context))
}

/**
 * Check if response is filtered/processed before return
 */
function hasResponseFiltering(context: string): boolean {
  const filterPatterns = [
    // Content filtering
    /\.map\s*\([^)]*\.(title|name|id|metadata)\)/i,
    /\.filter\s*\(/i,
    /sanitize|redact|mask|strip/i,
    // Only returning specific fields
    /return\s*\{[^}]*(?:id|title|summary)[^}]*\}(?![^}]*content)/i,
  ]
  return filterPatterns.some(p => p.test(context))
}

/**
 * Check if there's authentication in the route/function
 */
function hasAuthenticationInContext(content: string): boolean {
  const authPatterns = [
    /getSession|getCurrentUser|getServerSession/i,
    /auth\(\)|requireAuth|verifyToken/i,
    /req\.user|request\.user|context\.user/i,
    /isAuthenticated|checkAuth|withAuth/i,
    /Authorization.*Bearer/i,
    /userId|user\.id|currentUserId/i,
  ]
  return authPatterns.some(p => p.test(content))
}

/**
 * Get surrounding context lines
 */
function getSurroundingContext(content: string, lineIndex: number, windowSize: number = 25): string {
  const lines = content.split('\n')
  const start = Math.max(0, lineIndex - windowSize)
  const end = Math.min(lines.length, lineIndex + windowSize)
  return lines.slice(start, end).join('\n')
}

// ============================================================================
// Pattern Definitions
// ============================================================================

interface RAGSafetyPattern {
  name: string
  pattern: RegExp
  riskType: 'unscoped_retrieval' | 'context_exposure' | 'context_logging' | 'corpus_poisoning' | 'pii_leakage' | 'query_injection' | 'embedding_poisoning' | 'chunk_injection'
  baseSeverity: VulnerabilitySeverity
  description: string
  suggestedFix: string
}

/**
 * Unscoped retrieval query patterns
 * Detects vector store queries without user/tenant filtering
 */
const UNSCOPED_RETRIEVAL_PATTERNS: RAGSafetyPattern[] = [
  // Generic vector store queries
  {
    name: 'Unscoped vector store query',
    pattern: /\.(?:query|search|similaritySearch|retrieve)\s*\(\s*(?:["'`][^"'`]+["'`]|[a-zA-Z_]\w*)\s*\)/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'high',
    description: 'Vector store query without user/tenant scoping. Retrieved documents may belong to other users, enabling cross-tenant data access.',
    suggestedFix: 'Add filter/metadata parameter to scope queries: .query(query, { filter: { userId: currentUser.id } })',
  },
  // LangChain retriever invoke
  {
    name: 'LangChain retriever without filter',
    pattern: /retriever\.(?:invoke|getRelevantDocuments)\s*\(\s*(?:["'`][^"'`]+["'`]|[a-zA-Z_]\w*)\s*\)/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'high',
    description: 'LangChain retriever invocation without metadata filter. Documents from all users may be retrieved.',
    suggestedFix: 'Use a filtered retriever or add metadata filter: retriever.invoke(query, { filter: { userId } })',
  },
  // LlamaIndex query engine
  {
    name: 'LlamaIndex query engine without filter',
    pattern: /query_engine\.query\s*\(\s*["'`][^"'`]+["'`]\s*\)/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'high',
    description: 'LlamaIndex query without node postprocessors or filters. All indexed documents are searchable.',
    suggestedFix: 'Add node_postprocessors to filter by user/tenant metadata before retrieval.',
  },
  // Pinecone query
  {
    name: 'Pinecone query without metadata filter',
    pattern: /\.query\s*\(\s*\{[^}]*(?:vector|topK)[^}]*\}\s*\)/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'medium',
    description: 'Pinecone query may lack metadata filtering. Verify namespace or filter is set.',
    suggestedFix: 'Add filter parameter: .query({ vector, topK, filter: { userId: { $eq: currentUserId } } })',
  },
  // Chroma query
  {
    name: 'Chroma collection query',
    pattern: /collection\.query\s*\(\s*\{[^}]*query_texts[^}]*\}\s*\)/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'medium',
    description: 'ChromaDB query without where filter. All documents in collection are searchable.',
    suggestedFix: 'Add where parameter: collection.query({ query_texts, where: { userId: currentUserId } })',
  },
  // Weaviate search
  {
    name: 'Weaviate search without filter',
    pattern: /\.nearText\s*\([^)]+\)\.(?:do|withLimit)/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'medium',
    description: 'Weaviate nearText search without where filter. Results may include other users\' data.',
    suggestedFix: 'Add .withWhere() to filter by user: .nearText({...}).withWhere({ path: ["userId"], operator: "Equal", valueString: userId })',
  },
  // Supabase vector search
  {
    name: 'Supabase vector search without RLS',
    pattern: /\.rpc\s*\(\s*['"`]match_documents['"`]/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'medium',
    description: 'Supabase vector search function called. Ensure RLS policies filter by user.',
    suggestedFix: 'Verify Row Level Security (RLS) is enabled and filters documents by authenticated user.',
  },
]

/**
 * Raw context exposure patterns
 * Detects retrieved documents being returned directly to clients
 */
const CONTEXT_EXPOSURE_PATTERNS: RAGSafetyPattern[] = [
  // Returning sourceDocuments in response
  {
    name: 'Source documents in API response',
    pattern: /(?:res\.json|NextResponse\.json|return)\s*\([^)]*(?:sourceDocuments|retrievedDocs|documents|chunks)/gi,
    riskType: 'context_exposure',
    baseSeverity: 'medium',
    description: 'Raw retrieved documents returned in API response. Source content may leak sensitive information from the knowledge base.',
    suggestedFix: 'Return only synthesized response or document IDs/titles. If source attribution needed, filter to metadata only.',
  },
  // Spreading documents into response
  {
    name: 'Retrieved context spread in response',
    pattern: /(?:res\.json|return)\s*\(\s*\{[^}]*\.\.\.(?:docs|documents|chunks|sourceDocuments|context)/gi,
    riskType: 'context_exposure',
    baseSeverity: 'medium',
    description: 'Retrieved document objects spread into response. Full document content may be exposed.',
    suggestedFix: 'Extract and return only safe fields: { sources: docs.map(d => ({ id: d.id, title: d.title })) }',
  },
  // Returning raw context in response object
  {
    name: 'Raw retrieval context in response',
    pattern: /return\s*\{[^}]*(?:context|retrievedContext|ragContext)\s*:/gi,
    riskType: 'context_exposure',
    baseSeverity: 'low',
    description: 'Retrieved context included in response object. Review what data is actually exposed.',
    suggestedFix: 'Ensure context field contains only safe, summarized content - not raw document text.',
  },
  // WebSocket/stream context exposure
  {
    name: 'Context in streaming response',
    pattern: /(?:socket|ws|stream)\.(?:send|emit|write)\s*\([^)]*(?:sourceDocuments|context|chunks)/gi,
    riskType: 'context_exposure',
    baseSeverity: 'medium',
    description: 'Retrieved context sent via streaming/WebSocket. Clients receive raw source data.',
    suggestedFix: 'Stream only AI-generated text. Send source attribution separately with filtered metadata.',
  },
]

/**
 * Context logging patterns
 * Detects logging of retrieved documents or prompts with context
 */
const CONTEXT_LOGGING_PATTERNS: RAGSafetyPattern[] = [
  // Logging retrieved documents
  {
    name: 'Retrieved documents logged',
    pattern: /(?:console|logger)\.\w+\s*\([^)]*(?:retrievedDocs|sourceDocuments|documents|chunks)/gi,
    riskType: 'context_logging',
    baseSeverity: 'info',
    description: 'Retrieved documents logged. If logs are accessible, sensitive document content may be exposed.',
    suggestedFix: 'Log document IDs/titles only: console.log("Retrieved:", docs.map(d => d.id))',
  },
  // Logging full prompt with context
  {
    name: 'Full prompt with context logged',
    pattern: /(?:console|logger)\.\w+\s*\([^)]*(?:fullPrompt|promptWithContext|augmentedPrompt)/gi,
    riskType: 'context_logging',
    baseSeverity: 'low',
    description: 'Full prompt (including retrieved context) logged. May expose sensitive document content in logs.',
    suggestedFix: 'Log prompt length/metadata only. Avoid logging full prompt content in production.',
  },
  // Debug logging of RAG context
  {
    name: 'RAG context debug logging',
    pattern: /(?:console\.(?:debug|log)|logger\.debug)\s*\([^)]*(?:context|ragContext|retrievalContext)/gi,
    riskType: 'context_logging',
    baseSeverity: 'info',
    description: 'RAG context logged for debugging. Ensure debug logging is disabled in production.',
    suggestedFix: 'Use conditional logging: if (process.env.NODE_ENV !== "production") console.debug(...)',
  },
  // Storing prompts with context
  {
    name: 'Prompt with context persisted',
    pattern: /(?:\.create|\.insert|\.save)\s*\([^)]*(?:fullPrompt|promptWithContext|augmentedPrompt)/gi,
    riskType: 'context_logging',
    baseSeverity: 'medium',
    description: 'Full prompt with retrieved context being persisted. May store sensitive document content.',
    suggestedFix: 'Store user query and response separately. Do not persist raw retrieved context.',
  },
]

// ============================================================================
// AI Detection Roadmap Phase 1: Enhanced RAG Detection
// ============================================================================

/**
 * Corpus Poisoning Patterns
 * Detects user uploads directly embedded without sanitization
 */
const CORPUS_POISONING_PATTERNS: RAGSafetyPattern[] = [
  // User content embedded directly
  {
    name: 'User content embedded directly',
    pattern: /(?:embeddings?\.create|createEmbedding|embed)\s*\([^)]*(?:document\.content|user\.content|req\.body|req\.json|upload|file\.content)/gi,
    riskType: 'corpus_poisoning',
    baseSeverity: 'high',
    description: 'User-provided content embedded directly without sanitization. Malicious instructions in uploads could poison the RAG corpus.',
    suggestedFix: 'Sanitize user content before embedding: const sanitized = sanitizeForRAG(content); await embed(sanitized)',
  },
  // External content fetched and embedded
  {
    name: 'External content embedded without validation',
    pattern: /(?:fetch|axios\.get|httpx\.get)\s*\([^)]+\)[^;]*(?:embed|addDocument|upsert|index)/gi,
    riskType: 'corpus_poisoning',
    baseSeverity: 'high',
    description: 'External content fetched and embedded without validation. External sources could contain prompt injection payloads.',
    suggestedFix: 'Validate and sanitize external content before embedding. Check source trustworthiness.',
  },
  // PDF/file content indexed without scanning
  {
    name: 'File content indexed without sanitization',
    pattern: /(?:pdfParser|parse|readFile)[^;]*(?:addToCorpus|embedDocument|vectorStore\.add|index\.upsert)/gi,
    riskType: 'corpus_poisoning',
    baseSeverity: 'medium',
    description: 'File content indexed without sanitization. PDFs and documents may contain hidden injection instructions.',
    suggestedFix: 'Scan file content for injection patterns before indexing. Consider content classification.',
  },
  // User messages embedded
  {
    name: 'User messages embedded to corpus',
    pattern: /(?:messages?|msg|chat)[^;]*(?:embedDocument|addToCorpus|vectorStore\.add)/gi,
    riskType: 'corpus_poisoning',
    baseSeverity: 'medium',
    description: 'User messages being embedded into corpus. Messages could contain crafted injection payloads.',
    suggestedFix: 'Filter user messages for instruction-like patterns. Use separate namespace for user content.',
  },
  // Direct upsert without sanitization
  {
    name: 'Direct vector upsert with user data',
    pattern: /\.upsert\s*\(\s*\[\s*\{[^}]*content\s*:\s*(?:document|user|upload|req)/gi,
    riskType: 'corpus_poisoning',
    baseSeverity: 'high',
    description: 'User data upserted directly to vector store. Content should be sanitized first.',
    suggestedFix: 'Sanitize content before upserting: { content: sanitize(document.content), ... }',
  },
]

/**
 * PII Leakage Patterns
 * Detects PII fields in embedded documents or retrieval responses
 */
const PII_LEAKAGE_PATTERNS: RAGSafetyPattern[] = [
  // PII fields in metadata
  {
    name: 'PII in document metadata',
    pattern: /metadata\s*:\s*\{[^}]*(?:email|ssn|phone(?:Number)?|fullName|dateOfBirth|dob|address|socialSecurity)/gi,
    riskType: 'pii_leakage',
    baseSeverity: 'high',
    description: 'PII fields stored in document metadata. This data will be exposed when documents are retrieved.',
    suggestedFix: 'Remove PII from metadata. Store only non-sensitive identifiers: { userId: user.id, category: doc.type }',
  },
  // SSN/financial data in embedded docs
  {
    name: 'Sensitive financial/identity data embedded',
    pattern: /(?:metadata|doc|document)\s*[:{][^}]*(?:ssn|socialSecurity|cardNumber|cvv|accountNum|insuranceId)/gi,
    riskType: 'pii_leakage',
    baseSeverity: 'critical',
    description: 'Highly sensitive data (SSN, financial) in embedded documents. This is a compliance violation.',
    suggestedFix: 'Never embed SSN, card numbers, or financial account data. Use tokenized references instead.',
  },
  // Patient/medical data in embeddings
  {
    name: 'PHI in embedded documents',
    pattern: /(?:embed|metadata|doc)[^;]*(?:patientName|patientDob|patientSsn|medicalRecord|diagnosis)/gi,
    riskType: 'pii_leakage',
    baseSeverity: 'critical',
    description: 'Protected Health Information (PHI) in embedded documents. HIPAA compliance violation.',
    suggestedFix: 'Remove PHI before embedding. Use de-identification and tokenization for medical data.',
  },
  // Returning PII in search results
  {
    name: 'PII in retrieval response',
    pattern: /return\s*(?:results\.map|docs\.map)[^}]*(?:email|phone|ssn|fullName|address)/gi,
    riskType: 'pii_leakage',
    baseSeverity: 'high',
    description: 'PII fields returned in retrieval response. User PII may be exposed to unauthorized queries.',
    suggestedFix: 'Filter PII from responses: return docs.map(d => ({ id: d.id, content: d.content })) // no PII',
  },
  // Direct metadata exposure with PII
  {
    name: 'Metadata with PII exposed in response',
    pattern: /return\s*\{[^}]*metadata\.[^}]*(?:email|phone|ssn|name|address)/gi,
    riskType: 'pii_leakage',
    baseSeverity: 'high',
    description: 'Document metadata containing PII exposed in response.',
    suggestedFix: 'Filter metadata before returning. Only include non-sensitive fields.',
  },
]

// ============================================================================
// Phase 1 Enhancement Backlog: Advanced RAG Attack Detection
// ============================================================================

/**
 * Query Injection Patterns
 * Detects user queries used in retrieval without sanitization
 */
const QUERY_INJECTION_PATTERNS: RAGSafetyPattern[] = [
  // User input directly in vector store query
  {
    name: 'User input directly in retrieval query',
    pattern: /(?:vectorStore|retriever|index|collection)\.(?:query|invoke|search|similaritySearch)\s*\(\s*(?:req\.|user\.|input\.|body\.|params\.)/gi,
    riskType: 'query_injection',
    baseSeverity: 'high',
    description: 'User input flows directly to vector store query without sanitization. Could manipulate retrieval results.',
    suggestedFix: 'Validate and sanitize user queries: const sanitizedQuery = sanitizeQuery(userInput)',
  },
  // Query from request body without validation
  {
    name: 'Query from request body without validation',
    pattern: /(?:const|let|var)\s*\{\s*query\s*\}.*(?:req\.body|req\.json|request\.body)[\s\S]{0,100}(?:search|query|retrieve|similaritySearch)/gi,
    riskType: 'query_injection',
    baseSeverity: 'medium',
    description: 'Query destructured from request body and used in retrieval. Validate before use.',
    suggestedFix: 'Add input validation: const { query } = validateSchema(req.body, querySchema)',
  },
  // Query template with user input interpolation
  {
    name: 'Query template with user input',
    pattern: /(?:prompt|query|searchQuery)\s*=\s*[`'"].*\$\{.*(?:user|input|query|req).*\}.*[`'"]/gi,
    riskType: 'query_injection',
    baseSeverity: 'medium',
    description: 'Query template interpolates user input. Could inject adversarial retrieval instructions.',
    suggestedFix: 'Use parameterized queries or sanitize user input before interpolation.',
  },
  // Direct query passthrough in API
  {
    name: 'Query passthrough to vector store',
    pattern: /app\.(?:post|get)\s*\([^)]+(?:search|query|retrieve)[^)]*\)[^{]*\{[^}]*(?:vectorStore|retriever)\.(?:query|search)\s*\(\s*(?:req|ctx)\.(?:body|query)/gi,
    riskType: 'query_injection',
    baseSeverity: 'high',
    description: 'API endpoint passes request directly to vector store. No validation layer.',
    suggestedFix: 'Add validation middleware. Sanitize and validate queries before retrieval.',
  },
  // No query length validation
  {
    name: 'Query without length validation',
    pattern: /(?:query|search|retrieve)\s*\(\s*(?:userQuery|searchQuery|q)\s*\)(?![\s\S]{0,50}(?:\.length|\.trim\(\)|maxLength|minLength))/gi,
    riskType: 'query_injection',
    baseSeverity: 'low',
    description: 'Query used without visible length validation. Consider adding bounds.',
    suggestedFix: 'Add query length validation: if (query.length > MAX_QUERY_LENGTH) throw new Error("Query too long")',
  },
]

/**
 * Embedding Poisoning Patterns
 * Detects adversarial document embedding vulnerabilities
 */
const EMBEDDING_POISONING_PATTERNS: RAGSafetyPattern[] = [
  // User document embedded without validation
  {
    name: 'User document embedded without validation',
    pattern: /(?:embed|embeddings?\.(?:create|embed|generate)|createEmbedding)[\s\S]{0,50}(?:user|req\.|upload|file)[\s\S]{0,80}(?:vectorStore|index)\.(?:add|upsert|insert)/gis,
    riskType: 'embedding_poisoning',
    baseSeverity: 'high',
    description: 'User-provided documents embedded directly. Adversarial content could poison retrieval.',
    suggestedFix: 'Validate and sanitize user documents before embedding. Implement content classification.',
  },
  // Retrieval without similarity threshold
  {
    name: 'Retrieval without similarity threshold',
    pattern: /similaritySearch\s*\(\s*[^,)]+\s*,\s*\d+\s*\)(?![\s\S]{0,50}(?:filter|threshold|score\s*>|minScore|scoreThreshold))/gi,
    riskType: 'embedding_poisoning',
    baseSeverity: 'medium',
    description: 'Vector search without similarity threshold. Low-relevance adversarial content may be retrieved.',
    suggestedFix: 'Add similarity threshold: similaritySearch(query, k, { scoreThreshold: 0.7 })',
  },
  // Batch embedding without deduplication
  {
    name: 'Batch embedding without duplicate detection',
    pattern: /(?:for|forEach|map)\s*\([^)]+\)[\s\S]{0,100}(?:vectorStore|index)\.(?:add|upsert)(?![\s\S]{0,80}(?:exists|duplicate|similar|dedup))/gis,
    riskType: 'embedding_poisoning',
    baseSeverity: 'low',
    description: 'Batch document embedding without duplicate detection. Attackers could flood corpus.',
    suggestedFix: 'Check for duplicate or near-duplicate documents before embedding.',
  },
  // Dynamic embedding model selection
  {
    name: 'Dynamic embedding model from config',
    pattern: /(?:embeddingModel|embeddings?)\s*=\s*(?:new\s+)?(?:config|options|params)\[?\s*['".]?(?:model|embedding)/gi,
    riskType: 'embedding_poisoning',
    baseSeverity: 'medium',
    description: 'Embedding model selected from configuration. Malicious config could use compromised model.',
    suggestedFix: 'Use hardcoded embedding model or validate against allowlist.',
  },
  // External URL content embedded
  {
    name: 'External URL content embedded directly',
    pattern: /(?:fetch|axios\.get|httpx\.get)\s*\([^)]+\)[\s\S]{0,150}(?:embed|vectorStore\.add|index\.upsert)/gis,
    riskType: 'embedding_poisoning',
    baseSeverity: 'high',
    description: 'Content from external URLs embedded without validation. Source could be compromised.',
    suggestedFix: 'Validate URL source against allowlist. Sanitize fetched content before embedding.',
  },
]

/**
 * Phase 6 Task 4: Cross-Tenant RAG Detection Patterns
 * Detect shared vector stores without tenant filtering that could leak data
 */
const CROSS_TENANT_PATTERNS: RAGSafetyPattern[] = [
  // Shared vector store without tenant filter
  {
    name: 'Shared vector store without tenant filter',
    pattern: /\b(?:vectorStore|index|collection)\s*=\s*(?:new\s+)?(?:PineconeStore|ChromaDB|Weaviate|Qdrant|Milvus|PGVector|VectorStore)\s*\([^)]*\)(?![\s\S]{0,100}(?:filter|where|tenant|user|org|namespace))/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'high',
    description: 'Vector store initialized without tenant filtering. In multi-tenant applications, this could leak data across tenants.',
    suggestedFix: 'Always include tenant/user ID in vector store configuration or filters: new VectorStore({ namespace: tenantId })',
  },
  // Query without tenant in metadata filter
  {
    name: 'Vector query missing tenant filter',
    pattern: /\.(?:query|search|similaritySearch)\s*\(\s*[^,)]+(?:,\s*\{[^}]*\})?\s*\)(?![\s\S]{0,80}(?:tenantId|tenant_id|orgId|org_id|userId|user_id|namespace))/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'high',
    description: 'Vector store query without tenant filtering. Results may include documents from other tenants.',
    suggestedFix: 'Add tenant filter to query: .query(q, { filter: { tenantId: ctx.tenant.id } })',
  },
  // Global index access pattern
  {
    name: 'Global index without scoping',
    pattern: /(?:const|let|var)\s+(?:index|vectorIndex|searchIndex)\s*=\s*(?:await\s+)?(?:getIndex|loadIndex|connectIndex|initializeIndex)\s*\(\s*(?:['"`][^'"`]+['"`])?\s*\)(?![\s\S]{0,50}(?:tenant|user|org|scope))/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'medium',
    description: 'Global index loaded without tenant scoping. Consider using tenant-specific indexes or namespaces.',
    suggestedFix: 'Use tenant-scoped index: const index = await getIndex(tenantId) or use namespace parameter',
  },
  // Multi-tenant store without isolation
  {
    name: 'Multi-tenant store missing isolation',
    pattern: /(?:multiTenant|shared|global)(?:Store|Index|Collection)\s*\.(?:query|search|add|upsert)(?![\s\S]{0,80}(?:tenantId|tenant_id|isolate|partition|namespace))/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'critical',
    description: 'Multi-tenant store accessed without tenant isolation. Data from all tenants is accessible.',
    suggestedFix: 'Always pass tenant identifier: multiTenantStore.query(q, { tenantId })',
  },
  // Embedding documents without tenant metadata
  {
    name: 'Document embedded without tenant metadata',
    pattern: /\.(?:addDocuments|upsert|add)\s*\(\s*\[?\s*\{[^}]*(?:content|text|pageContent)[^}]*\}(?![^}]*(?:tenantId|tenant_id|orgId|organizationId|userId|user_id))/gi,
    riskType: 'corpus_poisoning',
    baseSeverity: 'high',
    description: 'Documents embedded without tenant metadata. Without tenant ID, documents cannot be filtered per-tenant.',
    suggestedFix: 'Include tenant ID in document metadata: { content, metadata: { tenantId: ctx.tenant.id } }',
  },
  // Retriever without tenant context
  {
    name: 'Retriever created without tenant context',
    pattern: /(?:asRetriever|createRetriever|getRetriever)\s*\(\s*(?:\{[^}]*\})?\s*\)(?![\s\S]{0,80}(?:filter|tenant|user|org|metadata))/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'medium',
    description: 'Retriever created without tenant filtering configuration. Retrieved documents may cross tenant boundaries.',
    suggestedFix: 'Configure retriever with tenant filter: vectorStore.asRetriever({ filter: { tenantId } })',
  },
  // Semantic search across all tenants
  {
    name: 'Semantic search without tenant restriction',
    pattern: /semanticSearch\s*\(\s*[^,)]+\s*\)(?![\s\S]{0,50}(?:tenant|user|org|filter|where|scope))/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'high',
    description: 'Semantic search without tenant restriction. Search results span all tenants.',
    suggestedFix: 'Add tenant restriction: semanticSearch(query, { tenantId: ctx.tenant.id })',
  },
  // RAG chain without tenant context
  {
    name: 'RAG chain missing tenant context',
    pattern: /(?:createRetrievalChain|RetrievalQAChain|ConversationalRetrievalChain)\.(?:fromLLM|from)?\s*\([^)]*\)(?![\s\S]{0,100}(?:filter|tenant|user|metadata))/gi,
    riskType: 'unscoped_retrieval',
    baseSeverity: 'medium',
    description: 'RAG chain created without tenant context. Chain may retrieve documents from all tenants.',
    suggestedFix: 'Pass tenant-filtered retriever to chain or add metadata filtering',
  },
]

/**
 * Chunk Boundary Exploitation Patterns
 * Detects cross-chunk injection vulnerabilities
 */
const CHUNK_INJECTION_PATTERNS: RAGSafetyPattern[] = [
  // User content chunked without per-chunk validation
  {
    name: 'User content chunked without validation',
    pattern: /(?:splitter|textSplitter|chunker)\.(?:split|createDocuments|chunk)[\s\S]{0,50}(?:user|upload|req)[\s\S]{0,100}(?:vectorStore|index)\.(?:add|upsert)(?![\s\S]{0,50}(?:sanitize|validate|filter))/gis,
    riskType: 'chunk_injection',
    baseSeverity: 'medium',
    description: 'User content split and embedded without per-chunk validation. Injection could span chunks.',
    suggestedFix: 'Validate each chunk before embedding: chunks.map(c => sanitizeChunk(c))',
  },
  // Context joined without separators
  {
    name: 'Context chunks joined without separators',
    pattern: /\.map\s*\([^)]*(?:pageContent|content|text)[^)]*\)\.join\s*\(\s*['"]['"]\s*\)/gi,
    riskType: 'chunk_injection',
    baseSeverity: 'low',
    description: 'Retrieved chunks joined without separators. Adjacent chunk content could be misinterpreted.',
    suggestedFix: 'Use clear separators: chunks.map(c => c.content).join("\\n---\\n")',
  },
  // Chunk metadata from user input
  {
    name: 'Chunk metadata from user input',
    pattern: /(?:vectorStore|index)\.(?:add|upsert)[\s\S]{0,100}metadata\s*:\s*(?:user|req\.|input\.|body\.)/gi,
    riskType: 'chunk_injection',
    baseSeverity: 'medium',
    description: 'Chunk metadata derived from user input. Could inject malicious metadata for filtering.',
    suggestedFix: 'Generate metadata server-side. Validate any user-provided metadata fields.',
  },
  // No chunk size limits
  {
    name: 'Chunking without size validation',
    pattern: /(?:splitter|textSplitter)\.(?:split|createDocuments)\s*\(\s*(?:content|text|document)(?![\s\S]{0,50}(?:maxChunkSize|chunkSize|maxLength))/gi,
    riskType: 'chunk_injection',
    baseSeverity: 'low',
    description: 'Text splitting without explicit size limits. Very long inputs could cause issues.',
    suggestedFix: 'Configure chunk size limits: new TextSplitter({ chunkSize: 1000, chunkOverlap: 200 })',
  },
  // Overlapping chunks with user content
  {
    name: 'Large chunk overlap with user content',
    pattern: /(?:chunkOverlap|overlap)\s*[:=]\s*(?:\d{3,}|[a-zA-Z])[\s\S]{0,100}(?:user|upload)/gi,
    riskType: 'chunk_injection',
    baseSeverity: 'low',
    description: 'Large chunk overlap configured. User-injected content could appear in multiple chunks.',
    suggestedFix: 'Use reasonable overlap (10-20% of chunk size). Validate user content before chunking.',
  },
]

// ============================================================================
// Main Detection Function
// ============================================================================

/**
 * Map risk type to vulnerability category
 */
function mapRiskTypeToCategory(riskType: RAGSafetyPattern['riskType']): VulnerabilityCategory {
  switch (riskType) {
    case 'corpus_poisoning':
      return 'ai_rag_corpus_poisoning'
    case 'pii_leakage':
      return 'ai_rag_pii_leakage'
    case 'query_injection':
      return 'ai_rag_query_injection'
    case 'embedding_poisoning':
      return 'ai_rag_embedding_poisoning'
    case 'chunk_injection':
      return 'ai_rag_chunk_injection'
    default:
      return 'ai_rag_exfiltration'
  }
}

/**
 * Main detection function for RAG data safety issues
 */
export function detectRAGSafetyIssues(
  content: string,
  filePath: string,
  options?: { parsed?: ParsedFile }
): Vulnerability[] {
  const vulnerabilities: Vulnerability[] = []

  // Skip non-applicable files
  if (isScannerOrFixtureFile(filePath)) return vulnerabilities
  if (isDocumentationFile(filePath)) return vulnerabilities

  // Only scan files in RAG context
  if (!isRAGContextFile(filePath, content)) {
    return vulnerabilities
  }

  const lines = options?.parsed?.lines ?? content.split('\n')
  const isTestFile = isTestOrMockFile(filePath)
  const isExample = isExampleDirectory(filePath)
  const isLibrary = isLibraryCode(filePath)
  const hasAuth = hasAuthenticationInContext(content)

  // Process all pattern categories
  const allPatterns: RAGSafetyPattern[] = [
    ...UNSCOPED_RETRIEVAL_PATTERNS,
    ...CONTEXT_EXPOSURE_PATTERNS,
    ...CONTEXT_LOGGING_PATTERNS,
    // AI Detection Roadmap Phase 1
    ...CORPUS_POISONING_PATTERNS,
    ...PII_LEAKAGE_PATTERNS,
    // Phase 1 Enhancement Backlog
    ...QUERY_INJECTION_PATTERNS,
    ...EMBEDDING_POISONING_PATTERNS,
    ...CHUNK_INJECTION_PATTERNS,
    // Phase 6: Cross-tenant detection
    ...CROSS_TENANT_PATTERNS,
  ]

  for (const pattern of allPatterns) {
    const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
    let match

    while ((match = regex.exec(content)) !== null) {
      const lineNumber = content.substring(0, match.index).split('\n').length
      const lineContent = lines[lineNumber - 1]?.trim() || ''

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

      // Skip generic query patterns (req.query, searchParams, etc.)
      if (isGenericQueryPattern(lineContent)) continue

      // Get surrounding context for analysis
      const context = getSurroundingContext(content, lineNumber - 1, 25)

      // Calculate severity based on context
      let severity = pattern.baseSeverity
      let description = pattern.description
      const notes: string[] = []

      // Apply context-aware severity adjustments
      if (pattern.riskType === 'unscoped_retrieval') {
        // Check for access control in surrounding context
        if (hasAccessControlScoping(context)) {
          severity = 'info'
          notes.push('Access control scoping detected nearby')
        } else if (!hasAuth) {
          // No auth at all - higher risk
          if (severity === 'medium') severity = 'high'
          notes.push('No authentication detected in this file')
        }
      }

      if (pattern.riskType === 'context_exposure') {
        // Check if response is filtered
        if (hasResponseFiltering(context)) {
          severity = 'info'
          notes.push('Response filtering detected')
        } else if (!hasAuth) {
          // Unauthenticated endpoint exposing context - higher risk
          if (severity === 'medium') severity = 'high'
          notes.push('Endpoint may be unauthenticated')
        }
      }

      // Corpus poisoning - check for sanitization
      if (pattern.riskType === 'corpus_poisoning') {
        // Check for content sanitization in context
        if (/sanitize|validate|filter|clean|strip/i.test(context)) {
          severity = 'info'
          notes.push('Content sanitization detected nearby')
        }
        // Check for content classification/scanning
        if (/classify|scan|detect|check.*injection/i.test(context)) {
          severity = 'info'
          notes.push('Content scanning detected')
        }
      }

      // PII leakage - critical data types remain high severity
      if (pattern.riskType === 'pii_leakage') {
        // Check for PII redaction/masking
        if (/redact|mask|anonymize|deidentify|tokenize/i.test(context)) {
          severity = 'info'
          notes.push('PII redaction detected')
        }
        // SSN, CVV, and PHI patterns remain critical regardless of context
        if (/ssn|cvv|patient/i.test(pattern.name.toLowerCase())) {
          // Keep severity high/critical for these
        }
      }

      // Query injection - check for input validation
      if (pattern.riskType === 'query_injection') {
        // Check for input validation/sanitization
        if (/sanitize|validate|clean|escape|zod|schema\.parse|safeParse/i.test(context)) {
          severity = 'low'
          notes.push('Input validation detected nearby')
        }
        // Check for query length/bounds validation
        if (/maxLength|minLength|\.length\s*[<>]|slice\s*\(\s*0/i.test(context)) {
          if (severity === 'high') severity = 'medium'
          notes.push('Length validation detected')
        }
        // Check for rate limiting
        if (/rateLimit|throttle|limiter/i.test(context)) {
          if (severity === 'high') severity = 'medium'
          notes.push('Rate limiting detected')
        }
      }

      // Embedding poisoning - check for content validation
      if (pattern.riskType === 'embedding_poisoning') {
        // Check for content sanitization
        if (/sanitize|validate|filter|clean|strip|scan/i.test(context)) {
          severity = 'low'
          notes.push('Content validation detected nearby')
        }
        // Check for content classification
        if (/classify|moderation|detect.*injection|contentFilter/i.test(context)) {
          severity = 'info'
          notes.push('Content classification detected')
        }
        // Check for similarity threshold
        if (/threshold|scoreThreshold|minScore|score\s*>/i.test(context)) {
          if (severity === 'medium') severity = 'low'
          notes.push('Similarity threshold configured')
        }
      }

      // Chunk injection - check for chunk validation
      if (pattern.riskType === 'chunk_injection') {
        // Check for per-chunk validation
        if (/chunks?\.map\s*\([^)]*sanitize|validate.*chunk|chunk.*validate/i.test(context)) {
          severity = 'info'
          notes.push('Chunk validation detected')
        }
        // Check for separator usage
        if (/separator|delimiter|join\s*\(\s*['"][^'"]{2,}['"]\s*\)/i.test(context)) {
          if (severity === 'low') severity = 'info'
          notes.push('Chunk separators detected')
        }
        // Check for metadata sanitization
        if (/metadata\s*[:=]\s*\{[^}]*(?:id|type|source)[^}]*\}/i.test(context)) {
          if (severity === 'medium') severity = 'low'
          notes.push('Server-generated metadata pattern')
        }
      }

      // Downgrade test files
      if (isTestFile) {
        severity = 'info'
        notes.push('in test file')
      }

      // Downgrade example/demo directories
      if (isExample && severity !== 'info') {
        severity = 'info'
        notes.push('in example/demo directory')
      }

      // Downgrade library code - base classes are intentionally generic
      if (isLibrary && severity !== 'info') {
        severity = 'info'
        notes.push('library code - consumers add access controls')
      }

      // Build final description
      if (notes.length > 0) {
        description += ` (${notes.join('; ')})`
      }

      vulnerabilities.push({
        id: `ai-rag-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
        filePath,
        lineNumber,
        lineContent,
        severity,
        category: mapRiskTypeToCategory(pattern.riskType),
        title: pattern.name,
        description,
        suggestedFix: pattern.suggestedFix,
        confidence: severity === 'info' ? 'low' : 'medium',
        layer: 2,
        source: 'ai_code' as const,
        requiresAIValidation: severity !== 'info' && pattern.riskType !== 'context_logging',
        baseConfidence: BASE_CONFIDENCE,
      })
    }
  }

  return vulnerabilities
}

// Export helper for use in other modules
export { isRAGContextFile }
