/**
 * OpenAI Provider Implementation
 *
 * Validation using OpenAI GPT-5-mini model.
 */

import type { Vulnerability, ScanFile, ValidationStatus } from '../../shared/types'
import type { ContextEngineResult } from '../../model/taint-types'
import type { ProjectContext } from '../../model/project-context'
import { buildProjectContext } from '../../model/project-context'
import type { ValidationStats, AIValidationResult, StatsAccumulator } from '../types'
import { createStatsAccumulator } from '../types'
import { getOpenAIClient, GPT5_MINI_PRICING, FILES_PER_API_BATCH, PARALLEL_API_BATCHES } from '../clients'
import { makeOpenAIRequestWithRetry } from '../utils/retry'
import { parseMultiFileValidationResponse, parseValidationResponse, applyValidationResults } from '../utils/response-parser'
import { buildMultiFileValidationRequest } from '../request-builder'
import { assembleValidationPrompt } from '../prompts/validation'

/**
 * Validate findings using OpenAI GPT-5-mini
 * This mirrors the Anthropic validation flow but uses OpenAI's API
 */
export async function validateWithOpenAI(
  findings: Vulnerability[],
  files: ScanFile[],
  ceResult: ContextEngineResult | undefined,
  stats: ValidationStats
): Promise<AIValidationResult> {
  const client = getOpenAIClient()

  // Extract project context from CE result or build from scratch
  const context: ProjectContext = ceResult?.projectContext ?? buildProjectContext(files)
  const fileTaintAnalyses = ceResult?.fileTaintAnalyses
  const routeMap = ceResult?.routeMap
  const frameworkAnalyses = ceResult?.frameworkAnalyses
  if (!ceResult) {
    console.log('[OpenAI Validation] No ContextEngineResult provided, built project context from scratch:', {
      hasAuthMiddleware: context.auth.hasGlobalMiddleware,
      authProvider: context.auth.authProvider,
      orm: context.dataAccess.orm,
      framework: context.frameworks.primary,
    })
  }

  // Group findings by file for efficient validation
  const findingsByFile = new Map<string, Vulnerability[]>()
  for (const finding of findings) {
    const existing = findingsByFile.get(finding.filePath) || []
    existing.push(finding)
    findingsByFile.set(finding.filePath, existing)
  }

  const validatedFindings: Vulnerability[] = []
  const fileEntries = Array.from(findingsByFile.entries())

  // Track metrics (thread-safe accumulator)
  let totalApiBatches = 0
  const statsLock: StatsAccumulator = createStatsAccumulator()

  const totalFileBatches = Math.ceil(fileEntries.length / FILES_PER_API_BATCH)
  console.log(`[OpenAI Validation] Processing ${fileEntries.length} files in ${totalFileBatches} API batch(es) (${PARALLEL_API_BATCHES} parallel)`)

  // Create all batch definitions
  const allBatches: Array<{
    batchNum: number
    fileBatch: Array<[string, Vulnerability[]]>
  }> = []

  for (let batchStart = 0; batchStart < fileEntries.length; batchStart += FILES_PER_API_BATCH) {
    const fileBatch = fileEntries.slice(batchStart, batchStart + FILES_PER_API_BATCH)
    const batchNum = Math.floor(batchStart / FILES_PER_API_BATCH) + 1
    allBatches.push({ batchNum, fileBatch })
  }

  // Process a single batch - returns validated findings for that batch
  const processBatch = async (
    batchDef: { batchNum: number; fileBatch: Array<[string, Vulnerability[]]> }
  ): Promise<Vulnerability[]> => {
    const { batchNum, fileBatch } = batchDef
    const batchFindings: Vulnerability[] = []

    // Prepare file data for batch request
    const fileDataList: Array<{ file: ScanFile; findings: Vulnerability[]; filePath: string }> = []
    const filesWithoutContent: Array<{ filePath: string; findings: Vulnerability[] }> = []

    for (const [filePath, fileFindings] of fileBatch) {
      const file = files.find(f => f.path === filePath)
      if (!file) {
        filesWithoutContent.push({ filePath, findings: fileFindings })
      } else {
        fileDataList.push({ file, findings: fileFindings, filePath })
      }
    }

    // Handle files without content
    for (const { findings: fileFindings } of filesWithoutContent) {
      for (const f of fileFindings) {
        batchFindings.push({
          ...f,
          validatedByAI: false,
          validationStatus: 'not_validated' as ValidationStatus,
          validationNotes: 'File content not available for validation',
        })
      }
    }

    if (fileDataList.length === 0) {
      return batchFindings
    }

    try {
      // Build multi-file validation request with scoped context, taint data, and route map
      const validationRequest = buildMultiFileValidationRequest(
        fileDataList.map(({ file, findings: fileFindings }) => ({ file, findings: fileFindings })),
        context,
        { contextMode: 'scoped' },
        fileTaintAnalyses,
        routeMap,
        ceResult?.crossFileTaintPaths,
        frameworkAnalyses
      )

      // Assemble category-aware prompt for this batch
      const batchCategories = [...new Set(
        fileDataList.flatMap(({ findings: fileFindings }) => fileFindings.map(f => f.category))
      )]
      const systemPrompt = assembleValidationPrompt(batchCategories)

      // Call OpenAI GPT-5-mini with retry logic
      const response = await makeOpenAIRequestWithRetry(async () =>
        client.chat.completions.create({
          model: 'gpt-5-mini-2025-08-07',
          messages: [
            { role: 'system', content: systemPrompt },
            { role: 'user', content: validationRequest },
          ],
          max_completion_tokens: 16384, // Must accommodate 8 files × multiple findings with full validation fields
          response_format: {
            type: 'json_schema',
            json_schema: {
              name: 'validation_response',
              strict: true,
              schema: {
                type: 'object',
                properties: {
                  validations: {
                    type: 'array',
                    items: {
                      type: 'object',
                      properties: {
                        file: { type: 'string' },
                        validations: {
                          type: 'array',
                          items: {
                            type: 'object',
                            properties: {
                              index: { type: 'number' },
                              keep: { type: 'boolean' },
                              notes: {
                                type: 'string',
                                description: 'Required: brief reason for the decision (5-15 words for rejections, 10-30 words for accepts)',
                              },
                              adjustedSeverity: {
                                type: ['string', 'null'],
                                enum: ['critical', 'high', 'medium', 'low', 'info', null],
                                default: null
                              },
                              impact: {
                                type: ['string', 'null'],
                                description: 'Context-aware explanation of why this matters (1-2 sentences)',
                                default: null
                              },
                              fixSuggestion: {
                                type: ['string', 'null'],
                                description: 'Specific, actionable fix for this code context',
                                default: null
                              }
                            },
                            required: ['index', 'keep', 'notes', 'adjustedSeverity', 'impact', 'fixSuggestion'],
                            additionalProperties: false
                          }
                        }
                      },
                      required: ['file', 'validations'],
                      additionalProperties: false
                    }
                  }
                },
                required: ['validations'],
                additionalProperties: false
              }
            }
          }
        })
      )

      // Track API call stats (accumulate to shared stats)
      statsLock.apiCalls++

      // Extract token usage from OpenAI response
      const usage = response.usage
      if (usage) {
        const promptTokens = usage.prompt_tokens || 0
        const completionTokens = usage.completion_tokens || 0
        const cachedTokens = (usage as any).prompt_tokens_details?.cached_tokens || 0
        const freshInputTokens = promptTokens - cachedTokens

        statsLock.estimatedInputTokens += freshInputTokens
        statsLock.estimatedOutputTokens += completionTokens
        statsLock.cacheReadTokens += cachedTokens

        console.log(`[OpenAI] Batch ${batchNum} tokens: ${promptTokens} input (${cachedTokens} cached), ${completionTokens} output`)

        const freshCost = (freshInputTokens * GPT5_MINI_PRICING.input) / 1_000_000
        const cachedCost = (cachedTokens * GPT5_MINI_PRICING.cached) / 1_000_000
        const outputCost = (completionTokens * GPT5_MINI_PRICING.output) / 1_000_000
        statsLock.estimatedCost += freshCost + cachedCost + outputCost
      }

      // Parse response content
      const content = response.choices[0]?.message?.content
      if (!content) {
        for (const { findings: fileFindings } of fileDataList) {
          for (const f of fileFindings) {
            batchFindings.push({
              ...f,
              validatedByAI: false,
              validationStatus: 'not_validated' as ValidationStatus,
              validationNotes: 'No valid response from OpenAI',
            })
          }
        }
        return batchFindings
      }

      // Parse structured JSON response (with validations wrapper from response_format)
      let parsedContent: any
      try {
        parsedContent = JSON.parse(content)
        console.log(`[OpenAI Debug] Raw parsed content keys:`, Object.keys(parsedContent))
        // Unwrap the validations array if present (from structured output)
        if (parsedContent.validations && Array.isArray(parsedContent.validations)) {
          console.log(`[OpenAI Debug] Unwrapping 'validations' array with ${parsedContent.validations.length} items`)
          parsedContent = parsedContent.validations
        } else if (Array.isArray(parsedContent)) {
          console.log(`[OpenAI Debug] Content is already an array with ${parsedContent.length} items`)
        } else {
          console.log(`[OpenAI Debug] Content structure:`, typeof parsedContent, Array.isArray(parsedContent))
        }
      } catch (e) {
        console.warn('[OpenAI] Failed to parse JSON response:', e)
        parsedContent = content
      }

      // Parse multi-file response
      const expectedFiles = fileDataList.map(({ filePath }) => filePath)
      const validationResultsMap = parseMultiFileValidationResponse(
        typeof parsedContent === 'string' ? parsedContent : JSON.stringify(parsedContent),
        expectedFiles
      )

      console.log(`[OpenAI] Batch ${batchNum} parsed ${validationResultsMap.size} file results from ${fileDataList.length} files`)
      if (validationResultsMap.size === 0) {
        console.warn(`[OpenAI] WARNING: No file results parsed! Content type: ${typeof parsedContent}, isArray: ${Array.isArray(parsedContent)}`)
        if (Array.isArray(parsedContent) && parsedContent.length > 0) {
          console.log(`[OpenAI] First item structure:`, Object.keys(parsedContent[0]))
        }
      }

      // Log any missing files from the response (these will be REJECTED)
      if (validationResultsMap.size !== fileDataList.length) {
        const missing = fileDataList
          .filter(({ filePath }) => !validationResultsMap.has(filePath))
          .map(({ filePath }) => filePath)
        if (missing.length > 0) {
          console.warn(`[OpenAI] Missing ${missing.length} files from response (will be REJECTED): ${missing.join(', ')}`)
        }
      }

      // Apply results per file
      for (const { filePath, findings: fileFindings } of fileDataList) {
        const fileResults = validationResultsMap.get(filePath)
        console.log(`[OpenAI] File ${filePath}: ${fileResults?.length || 0} validation results for ${fileFindings.length} findings`)

        if (!fileResults || fileResults.length === 0) {
          const singleFileResults = parseValidationResponse(content)
          if (singleFileResults.length > 0 && fileDataList.length === 1) {
            const { processed: processedFindings, dismissedCount } = applyValidationResults(fileFindings, singleFileResults)
            statsLock.validatedFindings += processedFindings.length + dismissedCount
            statsLock.dismissedFindings += dismissedCount
            for (const processed of processedFindings) {
              if (processed.validationStatus === 'confirmed') statsLock.confirmedFindings++
              else if (processed.validationStatus === 'downgraded') statsLock.downgradedFindings++
              batchFindings.push(processed)
            }
          } else {
            // No validation results - REJECT all findings for this file (conservative approach)
            console.warn(`[OpenAI] No validation results for ${filePath} - REJECTING ${fileFindings.length} findings`)
            statsLock.validatedFindings += fileFindings.length
            statsLock.dismissedFindings += fileFindings.length
            // Don't add to batchFindings - findings are rejected
          }
        } else {
          const { processed: processedFindings, dismissedCount } = applyValidationResults(fileFindings, fileResults)
          statsLock.validatedFindings += processedFindings.length + dismissedCount
          statsLock.dismissedFindings += dismissedCount
          for (const processed of processedFindings) {
            if (processed.validationStatus === 'confirmed') statsLock.confirmedFindings++
            else if (processed.validationStatus === 'downgraded') statsLock.downgradedFindings++
            batchFindings.push(processed)
          }
        }
      }

    } catch (error) {
      console.error(`[OpenAI Validation] Error in batch ${batchNum}:`, error)
      for (const { findings: fileFindings } of fileDataList) {
        for (const f of fileFindings) {
          batchFindings.push({
            ...f,
            validatedByAI: false,
            validationStatus: 'not_validated' as ValidationStatus,
            validationNotes: 'Validation failed due to API error',
          })
        }
      }
    }

    return batchFindings
  }

  // Process batches in parallel groups
  const startTime = Date.now()
  for (let i = 0; i < allBatches.length; i += PARALLEL_API_BATCHES) {
    const parallelGroup = allBatches.slice(i, i + PARALLEL_API_BATCHES)
    const batchNums = parallelGroup.map(b => b.batchNum).join(', ')
    console.log(`[OpenAI Validation] Processing batches ${batchNums} in parallel`)

    const results = await Promise.all(parallelGroup.map(processBatch))
    for (const batchResults of results) {
      validatedFindings.push(...batchResults)
    }
    totalApiBatches += parallelGroup.length
  }
  const totalDuration = Date.now() - startTime

  // Copy accumulated stats back
  stats.apiCalls = statsLock.apiCalls
  stats.estimatedInputTokens = statsLock.estimatedInputTokens
  stats.estimatedOutputTokens = statsLock.estimatedOutputTokens
  stats.cacheReadTokens = statsLock.cacheReadTokens
  stats.estimatedCost = statsLock.estimatedCost
  stats.validatedFindings = statsLock.validatedFindings
  stats.confirmedFindings = statsLock.confirmedFindings
  stats.dismissedFindings = statsLock.dismissedFindings
  stats.downgradedFindings = statsLock.downgradedFindings

  // Calculate cache hit rate
  const totalCacheableTokens = stats.cacheCreationTokens + stats.cacheReadTokens
  stats.cacheHitRate = totalCacheableTokens > 0
    ? stats.cacheReadTokens / totalCacheableTokens
    : 0

  // Log validation stats
  const avgTimePerFileMs = fileEntries.length > 0
    ? (totalDuration / fileEntries.length).toFixed(0)
    : '0'
  const totalDurationSec = (totalDuration / 1000).toFixed(1)

  console.log(`[OpenAI Validation] Stats:`)
  console.log(`  - Total findings: ${stats.totalFindings}`)
  console.log(`  - AI validated: ${stats.validatedFindings}`)
  console.log(`  - Confirmed: ${stats.confirmedFindings}`)
  console.log(`  - Dismissed: ${stats.dismissedFindings}`)
  console.log(`  - Downgraded: ${stats.downgradedFindings}`)
  console.log(`  - API calls: ${stats.apiCalls}`)
  console.log(`  - Performance:`)
  console.log(`    - Total duration: ${totalDurationSec}s`)
  console.log(`    - Total API batches: ${totalApiBatches}`)
  console.log(`    - Avg time per file: ${avgTimePerFileMs}ms`)
  console.log(`  - Token usage:`)
  console.log(`    - Input (fresh): ${stats.estimatedInputTokens} tokens`)
  console.log(`    - Cached: ${stats.cacheReadTokens} tokens`)
  console.log(`    - Output: ${stats.estimatedOutputTokens} tokens`)
  console.log(`  - Estimated cost: $${stats.estimatedCost.toFixed(4)}`)

  return { vulnerabilities: validatedFindings, stats }
}

