/**
 * AI-Powered Project Tagging Service
 *
 * Analyzes projects and assigns expertise tags automatically.
 *
 * Features:
 * - Single project tagging (on-submit or manual)
 * - Batch tagging for rounds
 * - Confidence scores for each tag
 * - Additive only - never removes existing tags
 *
 * GDPR Compliance:
 * - All project data is anonymized before AI processing
 * - Only necessary fields sent to OpenAI
 * - No personal identifiers in prompts or responses
 */

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

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

export interface TagSuggestion {
  tagId: string
  tagName: string
  confidence: number
  reasoning: string
}

export interface TaggingResult {
  projectId: string
  suggestions: TagSuggestion[]
  applied: TagSuggestion[]
  tokensUsed: number
}

export interface BatchTaggingResult {
  processed: number
  failed: number
  skipped: number
  errors: string[]
  results: TaggingResult[]
}

interface AvailableTag {
  id: string
  name: string
  category: string | null
  description: string | null
}

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

const DEFAULT_BATCH_SIZE = 10
const MAX_BATCH_SIZE = 25
const CONFIDENCE_THRESHOLD = 0.5
const DEFAULT_MAX_TAGS = 5

// System prompt optimized for tag suggestion
const TAG_SUGGESTION_SYSTEM_PROMPT = `You are an expert at categorizing ocean conservation and sustainability projects.

Analyze the project and suggest the most relevant expertise tags from the provided list.
Consider the project's focus areas, technology, methodology, and domain.

Return JSON with this format:
{
  "suggestions": [
    {
      "tag_name": "exact tag name from list",
      "confidence": 0.0-1.0,
      "reasoning": "brief explanation why this tag fits"
    }
  ]
}

Rules:
- Only suggest tags from the provided list (exact names)
- Order by relevance (most relevant first)
- Confidence should reflect how well the tag matches
- Maximum 7 suggestions per project
- Be conservative - only suggest tags that truly apply`

// ─── Helper Functions ────────────────────────────────────────────────────────

/**
 * Get system settings for AI tagging
 */
async function getTaggingSettings(): Promise<{
  enabled: boolean
  maxTags: number
}> {
  const settings = await prisma.systemSettings.findMany({
    where: {
      key: {
        in: ['ai_tagging_enabled', 'ai_tagging_max_tags'],
      },
    },
  })

  const settingsMap = new Map(settings.map((s) => [s.key, s.value]))

  return {
    enabled: settingsMap.get('ai_tagging_enabled') === 'true',
    maxTags: parseInt(settingsMap.get('ai_tagging_max_tags') || String(DEFAULT_MAX_TAGS)),
  }
}

/**
 * Get all active expertise tags
 */
async function getAvailableTags(): Promise<AvailableTag[]> {
  return prisma.expertiseTag.findMany({
    where: { isActive: true },
    select: {
      id: true,
      name: true,
      category: true,
      description: true,
    },
    orderBy: [{ category: 'asc' }, { sortOrder: 'asc' }],
  })
}

/**
 * Convert project to format for anonymization
 */
function toProjectWithRelations(project: {
  id: string
  title: string
  description?: string | null
  competitionCategory?: string | null
  oceanIssue?: string | null
  country?: string | null
  geographicZone?: string | null
  institution?: string | null
  tags: string[]
  foundedAt?: Date | null
  wantsMentorship?: boolean
  submissionSource?: string
  submittedAt?: Date | null
  _count?: { teamMembers?: number; files?: number }
  files?: Array<{ fileType: string | null }>
}): ProjectWithRelations {
  return {
    id: project.id,
    title: project.title,
    description: project.description,
    competitionCategory: project.competitionCategory as any,
    oceanIssue: project.oceanIssue as any,
    country: project.country,
    geographicZone: project.geographicZone,
    institution: project.institution,
    tags: project.tags,
    foundedAt: project.foundedAt,
    wantsMentorship: project.wantsMentorship ?? false,
    submissionSource: (project.submissionSource as any) ?? 'MANUAL',
    submittedAt: project.submittedAt,
    _count: {
      teamMembers: project._count?.teamMembers ?? 0,
      files: project._count?.files ?? 0,
    },
    files: project.files?.map((f) => ({ fileType: (f.fileType as any) ?? null })) ?? [],
  }
}

// ─── AI Tagging Core ─────────────────────────────────────────────────────────

/**
 * Call OpenAI to get tag suggestions for a project
 */
async function getAISuggestions(
  anonymizedProject: AnonymizedProjectForAI,
  availableTags: AvailableTag[],
  userId?: string
): Promise<{ suggestions: TagSuggestion[]; tokensUsed: number }> {
  const openai = await getOpenAI()
  if (!openai) {
    console.warn('[AI Tagging] OpenAI not configured')
    return { suggestions: [], tokensUsed: 0 }
  }

  const model = await getConfiguredModel()

  // Build tag list for prompt
  const tagList = availableTags.map((t) => ({
    name: t.name,
    category: t.category,
    description: t.description,
  }))

  const userPrompt = `PROJECT:
${JSON.stringify(anonymizedProject, null, 2)}

AVAILABLE TAGS:
${JSON.stringify(tagList, null, 2)}

Suggest relevant tags for this project.`

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

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

    // Log usage
    await logAIUsage({
      userId,
      action: 'PROJECT_TAGGING',
      entityType: 'Project',
      entityId: anonymizedProject.project_id,
      model,
      promptTokens: usage.promptTokens,
      completionTokens: usage.completionTokens,
      totalTokens: usage.totalTokens,
      batchSize: 1,
      itemsProcessed: 1,
      status: 'SUCCESS',
    })

    const content = response.choices[0]?.message?.content
    if (!content) {
      throw new Error('Empty response from AI')
    }

    const parsed = JSON.parse(content) as {
      suggestions: Array<{
        tag_name: string
        confidence: number
        reasoning: string
      }>
    }

    // Map to TagSuggestion format, matching tag names to IDs
    const suggestions: TagSuggestion[] = []
    for (const s of parsed.suggestions || []) {
      const tag = availableTags.find(
        (t) => t.name.toLowerCase() === s.tag_name.toLowerCase()
      )
      if (tag) {
        suggestions.push({
          tagId: tag.id,
          tagName: tag.name,
          confidence: Math.max(0, Math.min(1, s.confidence)),
          reasoning: s.reasoning,
        })
      }
    }

    return { suggestions, tokensUsed: usage.totalTokens }
  } catch (error) {
    if (error instanceof SyntaxError) {
      const parseError = createParseError(error.message)
      logAIError('Tagging', 'getAISuggestions', parseError)
    }

    await logAIUsage({
      userId,
      action: 'PROJECT_TAGGING',
      entityType: 'Project',
      entityId: anonymizedProject.project_id,
      model,
      promptTokens: 0,
      completionTokens: 0,
      totalTokens: 0,
      batchSize: 1,
      itemsProcessed: 0,
      status: 'ERROR',
      errorMessage: error instanceof Error ? error.message : 'Unknown error',
    })

    throw error
  }
}

// ─── Public API ──────────────────────────────────────────────────────────────

/**
 * Tag a single project with AI-suggested expertise tags
 *
 * Behavior:
 * - Only applies tags with confidence >= 0.5
 * - Additive only - never removes existing tags
 * - Respects maxTags setting
 */
export async function tagProject(
  projectId: string,
  userId?: string
): Promise<TaggingResult> {
  const settings = await getTaggingSettings()
  if (!settings.enabled) {
    return {
      projectId,
      suggestions: [],
      applied: [],
      tokensUsed: 0,
    }
  }

  // Fetch project with needed fields
  const project = await prisma.project.findUnique({
    where: { id: projectId },
    include: {
      projectTags: true,
      files: { select: { fileType: true } },
      _count: { select: { teamMembers: true, files: true } },
    },
  })

  if (!project) {
    throw new Error(`Project not found: ${projectId}`)
  }

  // Get available tags
  const availableTags = await getAvailableTags()
  if (availableTags.length === 0) {
    return {
      projectId,
      suggestions: [],
      applied: [],
      tokensUsed: 0,
    }
  }

  // Anonymize project data
  const projectWithRelations = toProjectWithRelations(project)
  const { anonymized, mappings } = anonymizeProjectsForAI([projectWithRelations], 'FILTERING')

  // Validate anonymization
  if (!validateAnonymizedProjects(anonymized)) {
    throw new Error('GDPR compliance check failed: PII detected in anonymized data')
  }

  // Get AI suggestions
  const { suggestions, tokensUsed } = await getAISuggestions(
    anonymized[0],
    availableTags,
    userId
  )

  // Filter by confidence threshold
  const validSuggestions = suggestions.filter(
    (s) => s.confidence >= CONFIDENCE_THRESHOLD
  )

  // Get existing tag IDs to avoid duplicates
  const existingTagIds = new Set(project.projectTags.map((pt) => pt.tagId))

  // Calculate how many more tags we can add
  const currentTagCount = project.projectTags.length
  const remainingSlots = Math.max(0, settings.maxTags - currentTagCount)

  // Filter out existing tags and limit to remaining slots
  const newSuggestions = validSuggestions
    .filter((s) => !existingTagIds.has(s.tagId))
    .slice(0, remainingSlots)

  // Apply new tags
  const applied: TagSuggestion[] = []
  for (const suggestion of newSuggestions) {
    try {
      await prisma.projectTag.create({
        data: {
          projectId,
          tagId: suggestion.tagId,
          confidence: suggestion.confidence,
          source: 'AI',
        },
      })
      applied.push(suggestion)
    } catch (error) {
      // Skip if tag already exists (race condition)
      console.warn(`[AI Tagging] Failed to apply tag ${suggestion.tagName}: ${error}`)
    }
  }

  return {
    projectId,
    suggestions,
    applied,
    tokensUsed,
  }
}

/**
 * Batch tag all untagged projects in a round
 *
 * Only processes projects with zero tags.
 */
export async function batchTagProjects(
  roundId: string,
  userId?: string,
  onProgress?: (processed: number, total: number) => void
): Promise<BatchTaggingResult> {
  const settings = await getTaggingSettings()
  if (!settings.enabled) {
    return {
      processed: 0,
      failed: 0,
      skipped: 0,
      errors: ['AI tagging is disabled'],
      results: [],
    }
  }

  // Get untagged projects in round
  const projects = await prisma.project.findMany({
    where: {
      roundId,
      projectTags: { none: {} }, // Only projects with no tags
    },
    include: {
      files: { select: { fileType: true } },
      _count: { select: { teamMembers: true, files: true } },
    },
  })

  if (projects.length === 0) {
    return {
      processed: 0,
      failed: 0,
      skipped: 0,
      errors: [],
      results: [],
    }
  }

  const results: TaggingResult[] = []
  let processed = 0
  let failed = 0
  const errors: string[] = []

  for (let i = 0; i < projects.length; i++) {
    const project = projects[i]
    try {
      const result = await tagProject(project.id, userId)
      results.push(result)
      processed++
    } catch (error) {
      failed++
      errors.push(`${project.title}: ${error instanceof Error ? error.message : 'Unknown error'}`)
    }

    // Report progress
    if (onProgress) {
      onProgress(i + 1, projects.length)
    }
  }

  return {
    processed,
    failed,
    skipped: 0,
    errors,
    results,
  }
}

/**
 * Get tag suggestions for a project without applying them
 * Useful for preview/review before applying
 */
export async function getTagSuggestions(
  projectId: string,
  userId?: string
): Promise<TagSuggestion[]> {
  // Fetch project
  const project = await prisma.project.findUnique({
    where: { id: projectId },
    include: {
      files: { select: { fileType: true } },
      _count: { select: { teamMembers: true, files: true } },
    },
  })

  if (!project) {
    throw new Error(`Project not found: ${projectId}`)
  }

  // Get available tags
  const availableTags = await getAvailableTags()
  if (availableTags.length === 0) {
    return []
  }

  // Anonymize project data
  const projectWithRelations = toProjectWithRelations(project)
  const { anonymized } = anonymizeProjectsForAI([projectWithRelations], 'FILTERING')

  // Validate anonymization
  if (!validateAnonymizedProjects(anonymized)) {
    throw new Error('GDPR compliance check failed')
  }

  // Get AI suggestions
  const { suggestions } = await getAISuggestions(anonymized[0], availableTags, userId)

  return suggestions
}

/**
 * Manually add a tag to a project
 */
export async function addProjectTag(
  projectId: string,
  tagId: string
): Promise<void> {
  await prisma.projectTag.upsert({
    where: { projectId_tagId: { projectId, tagId } },
    create: { projectId, tagId, source: 'MANUAL', confidence: 1.0 },
    update: { source: 'MANUAL', confidence: 1.0 },
  })
}

/**
 * Remove a tag from a project
 */
export async function removeProjectTag(
  projectId: string,
  tagId: string
): Promise<void> {
  await prisma.projectTag.deleteMany({
    where: { projectId, tagId },
  })
}
