import OpenAI from 'openai'
import type { ChatCompletionCreateParamsNonStreaming } from 'openai/resources/chat/completions'
import { prisma } from './prisma'

// OpenAI client singleton with lazy initialization
const globalForOpenAI = globalThis as unknown as {
  openai: OpenAI | undefined
  openaiInitialized: boolean
}

// ─── Model Type Detection ────────────────────────────────────────────────────

/**
 * Reasoning models that require different API parameters:
 * - Use max_completion_tokens instead of max_tokens
 * - Don't support response_format: json_object (must instruct JSON in prompt)
 * - Don't support temperature parameter
 * - Don't support system messages (use developer or user role instead)
 */
const REASONING_MODEL_PREFIXES = ['o1', 'o3', 'o4']

/**
 * Models that use max_completion_tokens instead of max_tokens.
 * This includes reasoning models AND newer GPT models (GPT-5+).
 */
const NEW_TOKEN_PARAM_PREFIXES = ['o1', 'o3', 'o4', 'gpt-5', 'gpt-6', 'gpt-7']

/**
 * Models that don't support custom temperature values.
 * These only accept the default temperature (1).
 */
const NO_TEMPERATURE_PREFIXES = ['o1', 'o3', 'o4', 'gpt-5', 'gpt-6', 'gpt-7']

/**
 * Check if a model is a reasoning model (o1, o3, o4 series)
 * These models have additional restrictions (no temperature, no json_object, etc.)
 */
export function isReasoningModel(model: string): boolean {
  const modelLower = model.toLowerCase()
  return REASONING_MODEL_PREFIXES.some(prefix =>
    modelLower.startsWith(prefix) ||
    modelLower.includes(`/${prefix}`) ||
    modelLower.includes(`-${prefix}`)
  )
}

/**
 * Check if a model requires max_completion_tokens instead of max_tokens.
 * This includes reasoning models AND newer GPT models (GPT-5+).
 */
export function usesNewTokenParam(model: string): boolean {
  const modelLower = model.toLowerCase()
  return NEW_TOKEN_PARAM_PREFIXES.some(prefix =>
    modelLower.startsWith(prefix) ||
    modelLower.includes(`/${prefix}`) ||
    modelLower.includes(`-${prefix}`)
  )
}

/**
 * Check if a model supports custom temperature values.
 * Newer models (o-series, GPT-5+) only accept default temperature (1).
 */
export function supportsTemperature(model: string): boolean {
  const modelLower = model.toLowerCase()
  return !NO_TEMPERATURE_PREFIXES.some(prefix =>
    modelLower.startsWith(prefix) ||
    modelLower.includes(`/${prefix}`) ||
    modelLower.includes(`-${prefix}`)
  )
}

// ─── Chat Completion Parameter Builder ───────────────────────────────────────

type MessageRole = 'system' | 'user' | 'assistant' | 'developer'

export interface ChatCompletionOptions {
  messages: Array<{ role: MessageRole; content: string }>
  maxTokens?: number
  temperature?: number
  jsonMode?: boolean
}

/**
 * Build chat completion parameters with correct settings for the model type.
 * Handles differences between standard models and reasoning models.
 */
export function buildCompletionParams(
  model: string,
  options: ChatCompletionOptions
): ChatCompletionCreateParamsNonStreaming {
  const isReasoning = isReasoningModel(model)

  // Convert messages for reasoning models (system -> developer)
  const messages = options.messages.map(msg => {
    if (isReasoning && msg.role === 'system') {
      return { role: 'developer' as const, content: msg.content }
    }
    return msg as { role: 'system' | 'user' | 'assistant' | 'developer'; content: string }
  })

  // For reasoning models requesting JSON, append JSON instruction to last user message
  if (isReasoning && options.jsonMode) {
    // Find last user message index (polyfill for findLastIndex)
    let lastUserIdx = -1
    for (let i = messages.length - 1; i >= 0; i--) {
      if (messages[i].role === 'user') {
        lastUserIdx = i
        break
      }
    }
    if (lastUserIdx !== -1) {
      messages[lastUserIdx] = {
        ...messages[lastUserIdx],
        content: messages[lastUserIdx].content + '\n\nIMPORTANT: Respond with valid JSON only, no other text.',
      }
    }
  }

  const params: ChatCompletionCreateParamsNonStreaming = {
    model,
    messages: messages as ChatCompletionCreateParamsNonStreaming['messages'],
  }

  // Token limit parameter differs between model types
  // Newer models (GPT-5+, o-series) use max_completion_tokens
  if (options.maxTokens) {
    if (usesNewTokenParam(model)) {
      params.max_completion_tokens = options.maxTokens
    } else {
      params.max_tokens = options.maxTokens
    }
  }

  // Newer models (o-series, GPT-5+) don't support custom temperature
  if (supportsTemperature(model) && options.temperature !== undefined) {
    params.temperature = options.temperature
  }

  // Reasoning models don't support response_format: json_object
  if (!isReasoning && options.jsonMode) {
    params.response_format = { type: 'json_object' }
  }

  return params
}

/**
 * Get OpenAI API key from SystemSettings
 */
async function getOpenAIApiKey(): Promise<string | null> {
  try {
    const setting = await prisma.systemSettings.findUnique({
      where: { key: 'openai_api_key' },
    })
    return setting?.value || process.env.OPENAI_API_KEY || null
  } catch {
    // Fall back to env var if database isn't available
    return process.env.OPENAI_API_KEY || null
  }
}

/**
 * Create OpenAI client instance
 */
async function createOpenAIClient(): Promise<OpenAI | null> {
  const apiKey = await getOpenAIApiKey()

  if (!apiKey) {
    console.warn('OpenAI API key not configured')
    return null
  }

  return new OpenAI({
    apiKey,
  })
}

/**
 * Get the OpenAI client singleton
 * Returns null if API key is not configured
 */
export async function getOpenAI(): Promise<OpenAI | null> {
  if (globalForOpenAI.openaiInitialized) {
    return globalForOpenAI.openai || null
  }

  const client = await createOpenAIClient()

  if (process.env.NODE_ENV !== 'production') {
    globalForOpenAI.openai = client || undefined
    globalForOpenAI.openaiInitialized = true
  }

  return client
}

/**
 * Check if OpenAI is configured and available
 */
export async function isOpenAIConfigured(): Promise<boolean> {
  const apiKey = await getOpenAIApiKey()
  return !!apiKey
}

/**
 * List available models from OpenAI API
 */
export async function listAvailableModels(): Promise<{
  success: boolean
  models?: string[]
  error?: string
}> {
  try {
    const client = await getOpenAI()

    if (!client) {
      return {
        success: false,
        error: 'OpenAI API key not configured',
      }
    }

    const response = await client.models.list()
    const chatModels = response.data
      .filter((m) => m.id.includes('gpt') || m.id.includes('o1') || m.id.includes('o3') || m.id.includes('o4'))
      .map((m) => m.id)
      .sort()

    return {
      success: true,
      models: chatModels,
    }
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error',
    }
  }
}

/**
 * Validate that a specific model is available
 */
export async function validateModel(modelId: string): Promise<{
  valid: boolean
  error?: string
}> {
  try {
    const client = await getOpenAI()

    if (!client) {
      return {
        valid: false,
        error: 'OpenAI API key not configured',
      }
    }

    // Try a minimal completion with the model using correct parameters
    const params = buildCompletionParams(modelId, {
      messages: [{ role: 'user', content: 'test' }],
      maxTokens: 1,
    })

    await client.chat.completions.create(params)

    return { valid: true }
  } catch (error) {
    const message = error instanceof Error ? error.message : 'Unknown error'
    // Check for specific model errors
    if (message.includes('does not exist') || message.includes('model_not_found')) {
      return {
        valid: false,
        error: `Model "${modelId}" is not available with your API key`,
      }
    }
    return {
      valid: false,
      error: message,
    }
  }
}

/**
 * Test OpenAI connection with the configured model
 */
export async function testOpenAIConnection(): Promise<{
  success: boolean
  error?: string
  model?: string
  modelTested?: string
}> {
  try {
    const client = await getOpenAI()

    if (!client) {
      return {
        success: false,
        error: 'OpenAI API key not configured',
      }
    }

    // Get the configured model
    const configuredModel = await getConfiguredModel()

    // Test with the configured model using correct parameters
    const params = buildCompletionParams(configuredModel, {
      messages: [{ role: 'user', content: 'Hello' }],
      maxTokens: 5,
    })

    const response = await client.chat.completions.create(params)

    return {
      success: true,
      model: response.model,
      modelTested: configuredModel,
    }
  } catch (error) {
    const message = error instanceof Error ? error.message : 'Unknown error'
    const configuredModel = await getConfiguredModel()

    // Check for model-specific errors
    if (message.includes('does not exist') || message.includes('model_not_found')) {
      return {
        success: false,
        error: `Model "${configuredModel}" is not available. Check Settings → AI to select a valid model.`,
        modelTested: configuredModel,
      }
    }

    return {
      success: false,
      error: message,
      modelTested: configuredModel,
    }
  }
}

// Default models for different use cases
export const AI_MODELS = {
  ASSIGNMENT: 'gpt-4o', // Best for complex reasoning
  QUICK: 'gpt-4o-mini', // Faster, cheaper for simple tasks
} as const

/**
 * Get the admin-configured AI model from SystemSettings.
 * Falls back to the provided default if not configured.
 */
export async function getConfiguredModel(fallback: string = AI_MODELS.ASSIGNMENT): Promise<string> {
  try {
    const setting = await prisma.systemSettings.findUnique({
      where: { key: 'ai_model' },
    })
    return setting?.value || process.env.OPENAI_MODEL || fallback
  } catch {
    return process.env.OPENAI_MODEL || fallback
  }
}
