/**
 * AI-Powered Award Eligibility Service
 *
 * Determines project eligibility for special awards using
 * AI interpretation of plain-language criteria.
 *
 * GDPR Compliance:
 * - All project data is anonymized before AI processing
 * - IDs replaced with sequential identifiers
 * - No personal information sent to OpenAI
 */

import { getOpenAI, getConfiguredModel, buildCompletionParams } from '@/lib/openai'
import { logAIUsage, extractTokenUsage } from '@/server/utils/ai-usage'
import { classifyAIError, createParseError, logAIError } from './ai-errors'
import { sanitizeUserInput } from '@/server/services/ai-prompt-guard'
import {
  anonymizeProjectsForAI,
  validateAnonymizedProjects,
  toProjectWithRelations,
  type AnonymizedProjectForAI,
  type ProjectAIMapping,
} from './anonymization'
import type { SubmissionSource } from '@prisma/client'

// ─── Constants ───────────────────────────────────────────────────────────────

const BATCH_SIZE = 20

// Structured system prompt for award eligibility
const AI_ELIGIBILITY_SYSTEM_PROMPT = `You are an award eligibility evaluator for an ocean conservation competition.

## Your Role
Determine whether each project meets the criteria for a specific award category.

## Evaluation Dimensions
- Geographic Relevance: Does the project's location/focus match the award's geographic requirements?
- Category Fit: Does the project category align with the award criteria?
- Topic Alignment: Does the project's ocean issue focus match the award's thematic area?
- Maturity Level: Is the project at the right stage for this award?

## Output Format
Return a JSON object:
{
  "evaluations": [
    {
      "project_id": "PROJECT_001",
      "eligible": true/false,
      "confidence": 0.0-1.0,
      "quality_score": 0-100,
      "reasoning": "2-3 sentence explanation covering key dimensions",
      "dimensionScores": {
        "geographic": 0.0-1.0,
        "category": 0.0-1.0,
        "topic": 0.0-1.0,
        "maturity": 0.0-1.0
      }
    }
  ]
}

quality_score is a 0-100 integer measuring how well the project fits the award criteria (used for ranking shortlists). 100 = perfect fit, 0 = no fit. Even ineligible projects should receive a score for reference.

## Guidelines
- Base evaluation only on provided data — do not infer missing information
- Be inclusive: if the project reasonably fits the criteria, mark eligible=true
- eligible=true when the project's location, category, or topic aligns with the award criteria
- Do not require perfection across all dimensions — strong fit in the primary criterion (e.g. geographic) is sufficient
- confidence reflects how clearly the data supports the determination
- No personal identifiers in reasoning`

// ─── Types ──────────────────────────────────────────────────────────────────

export interface EligibilityResult {
  projectId: string
  eligible: boolean
  confidence: number
  qualityScore: number
  reasoning: string
  method: 'AUTO' | 'AI'
}

interface ProjectForEligibility {
  id: string
  title: string
  description?: string | null
  competitionCategory?: string | null
  country?: string | null
  geographicZone?: string | null
  tags: string[]
  oceanIssue?: string | null
  institution?: string | null
  foundedAt?: Date | null
  wantsMentorship?: boolean
  submissionSource?: SubmissionSource | string
  submittedAt?: Date | null
  _count?: {
    teamMembers?: number
    files?: number
  }
  files?: Array<{ fileType: string | null }>
}

// ─── AI Criteria Interpretation ─────────────────────────────────────────────

/**
 * Process a batch for AI eligibility evaluation
 */
async function processEligibilityBatch(
  openai: NonNullable<Awaited<ReturnType<typeof getOpenAI>>>,
  model: string,
  criteriaText: string,
  anonymized: AnonymizedProjectForAI[],
  mappings: ProjectAIMapping[],
  userId?: string,
  entityId?: string
): Promise<{
  results: EligibilityResult[]
  tokensUsed: number
}> {
  const results: EligibilityResult[] = []
  let tokensUsed = 0

  // Sanitize user-supplied criteria
  const { sanitized: safeCriteria } = sanitizeUserInput(criteriaText)

  const userPrompt = `CRITERIA: ${safeCriteria}
PROJECTS: ${JSON.stringify(anonymized)}
Evaluate eligibility for each project.`

  const MAX_PARSE_RETRIES = 2
  let parseAttempts = 0
  let response: Awaited<ReturnType<typeof openai.chat.completions.create>>

  try {
    const params = buildCompletionParams(model, {
      messages: [
        { role: 'system', content: AI_ELIGIBILITY_SYSTEM_PROMPT },
        { role: 'user', content: userPrompt },
      ],
      jsonMode: true,
      temperature: 0.1,
      maxTokens: 4000,
    })

    response = await openai.chat.completions.create(params)
    const usage = extractTokenUsage(response)
    tokensUsed = usage.totalTokens

    // Log usage
    await logAIUsage({
      userId,
      action: 'AWARD_ELIGIBILITY',
      entityType: 'Award',
      entityId,
      model,
      promptTokens: usage.promptTokens,
      completionTokens: usage.completionTokens,
      totalTokens: usage.totalTokens,
      batchSize: anonymized.length,
      itemsProcessed: anonymized.length,
      status: 'SUCCESS',
    })

    // Parse with retry logic
    let parsed: {
      evaluations: Array<{
        project_id: string
        eligible: boolean
        confidence: number
        quality_score?: number
        reasoning: string
      }>
    }

    while (true) {
      try {
        const content = response.choices[0]?.message?.content
        if (!content) {
          throw new Error('Empty response from AI')
        }
        parsed = JSON.parse(content)
        break
      } catch (parseError) {
        if (parseError instanceof SyntaxError && parseAttempts < MAX_PARSE_RETRIES) {
          parseAttempts++
          console.warn(`[AI Eligibility] JSON parse failed, retrying (${parseAttempts}/${MAX_PARSE_RETRIES})`)

          // Retry the API call with hint
          const retryParams = buildCompletionParams(model, {
            messages: [
              { role: 'system', content: AI_ELIGIBILITY_SYSTEM_PROMPT },
              { role: 'user', content: userPrompt + '\n\nIMPORTANT: Please ensure valid JSON output.' },
            ],
            jsonMode: true,
            temperature: 0.1,
            maxTokens: 4000,
          })
          response = await openai.chat.completions.create(retryParams)
          const retryUsage = extractTokenUsage(response)
          tokensUsed += retryUsage.totalTokens
          continue
        }
        throw parseError
      }
    }

    // Map results back to real IDs
    for (const eval_ of parsed.evaluations || []) {
      const mapping = mappings.find((m) => m.anonymousId === eval_.project_id)
      if (mapping) {
        results.push({
          projectId: mapping.realId,
          eligible: eval_.eligible,
          confidence: eval_.confidence,
          qualityScore: Math.max(0, Math.min(100, eval_.quality_score ?? 0)),
          reasoning: eval_.reasoning,
          method: 'AI',
        })
      }
    }

  } catch (error) {
    if (error instanceof SyntaxError) {
      const parseError = createParseError(error.message)
      logAIError('AwardEligibility', 'batch processing', parseError)

      await logAIUsage({
        userId,
        action: 'AWARD_ELIGIBILITY',
        entityType: 'Award',
        entityId,
        model,
        promptTokens: 0,
        completionTokens: 0,
        totalTokens: tokensUsed,
        batchSize: anonymized.length,
        itemsProcessed: 0,
        status: 'ERROR',
        errorMessage: parseError.message,
      })

      // Flag all for manual review
      for (const mapping of mappings) {
        results.push({
          projectId: mapping.realId,
          eligible: false,
          confidence: 0,
          qualityScore: 0,
          reasoning: 'AI response parse error — requires manual review',
          method: 'AI',
        })
      }
    } else {
      throw error
    }
  }

  return { results, tokensUsed }
}

export async function aiInterpretCriteria(
  criteriaText: string,
  projects: ProjectForEligibility[],
  userId?: string,
  awardId?: string
): Promise<EligibilityResult[]> {
  const results: EligibilityResult[] = []

  try {
    const openai = await getOpenAI()
    if (!openai) {
      console.warn('[AI Eligibility] OpenAI not configured')
      return projects.map((p) => ({
        projectId: p.id,
        eligible: false,
        confidence: 0,
        qualityScore: 0,
        reasoning: 'AI unavailable — requires manual eligibility review',
        method: 'AI' as const,
      }))
    }

    const model = await getConfiguredModel()
    console.log(`[AI Eligibility] Using model: ${model} for ${projects.length} projects`)

    // Convert and anonymize projects
    const projectsWithRelations = projects.map(toProjectWithRelations)
    const { anonymized, mappings } = anonymizeProjectsForAI(projectsWithRelations, 'ELIGIBILITY')

    // Validate anonymization
    if (!validateAnonymizedProjects(anonymized)) {
      console.error('[AI Eligibility] Anonymization validation failed')
      throw new Error('GDPR compliance check failed: PII detected in anonymized data')
    }

    let totalTokens = 0

    // Process in batches
    for (let i = 0; i < anonymized.length; i += BATCH_SIZE) {
      const batchAnon = anonymized.slice(i, i + BATCH_SIZE)
      const batchMappings = mappings.slice(i, i + BATCH_SIZE)

      console.log(`[AI Eligibility] Processing batch ${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(anonymized.length / BATCH_SIZE)}`)

      const { results: batchResults, tokensUsed } = await processEligibilityBatch(
        openai,
        model,
        criteriaText,
        batchAnon,
        batchMappings,
        userId,
        awardId
      )

      results.push(...batchResults)
      totalTokens += tokensUsed
    }

    console.log(`[AI Eligibility] Completed. Total tokens: ${totalTokens}`)

  } catch (error) {
    const classified = classifyAIError(error)
    logAIError('AwardEligibility', 'aiInterpretCriteria', classified)

    // Log failed attempt
    await logAIUsage({
      userId,
      action: 'AWARD_ELIGIBILITY',
      entityType: 'Award',
      entityId: awardId,
      model: 'unknown',
      promptTokens: 0,
      completionTokens: 0,
      totalTokens: 0,
      batchSize: projects.length,
      itemsProcessed: 0,
      status: 'ERROR',
      errorMessage: classified.message,
    })

    // Return all as needing manual review
    return projects.map((p) => ({
      projectId: p.id,
      eligible: false,
      confidence: 0,
      qualityScore: 0,
      reasoning: `AI error: ${classified.message}`,
      method: 'AI' as const,
    }))
  }

  return results
}
