/**
 * AI Configuration for Exercise Generation
 *
 * Manages the connection to Aliyun DashScope API (OpenAI compatible)
 * using MiniMax-M2.5 model for mathematical exercise generation.
 */

import OpenAI from 'openai';
import { logger } from '../shared/utils/logger';
import { AIExerciseRequest } from '../modules/exercise/generators/prompt-builder';

export type { AIExerciseRequest };

/**
 * AI Configuration Interface
 */
export interface AIConfig {
  apiKey: string;
  baseURL: string;
  model: string;
  temperature: number;
  maxTokens: number;
  timeout: number;
}

/**
 * Default AI Configuration for Aliyun DashScope
 * Note: apiKey is evaluated lazily when actually needed, not at import time
 */
const DEFAULT_AI_CONFIG: Omit<AIConfig, 'apiKey'> = {
  baseURL: process.env.DASHSCOPE_BASE_URL || 'https://coding-intl.dashscope.aliyuncs.com/v1',
  model: process.env.DASHSCOPE_MODEL || 'MiniMax-M2.5',
  temperature: parseFloat(process.env.AI_TEMPERATURE || '0.7'),
  maxTokens: parseInt(process.env.AI_MAX_TOKENS || '4000', 10),
  timeout: parseInt(process.env.AI_TIMEOUT || '60000', 10),
};

/**
 * Get API key from environment (lazy evaluation)
 */
function getApiKey(): string {
  const key = process.env.DASHSCOPE_API_KEY;
  if (!key) {
    throw new Error('DASHSCOPE_API_KEY environment variable is required. Please set it in your .env file.');
  }
  return key;
}

/**
 * Generated Exercise Interface
 */
export interface AIGeneratedExercise {
  statement: string;
  correctAnswer: string;
  solutionSteps: Array<{
    step: number;
    explanation: string;
    latexFormula?: string;
  }>;
  formulas?: Array<{
    latex: string;
    description: string;
    step?: number;
  }>;
  hints?: Array<{
    hint: string;
    cost: number;
  }>;
  multipleChoiceOptions?: Array<{
    option: string;
    isCorrect: boolean;
    explanation?: string;
  }>;
  difficulty: string;
  estimatedTimeSeconds: number;
}

/**
 * AI Client Singleton with lazy initialization
 * Does not throw error at import time - only when actually used
 */
class AIConfigManager {
  private static instance: AIConfigManager | null = null;
  private config: AIConfig;
  private client: OpenAI | null = null;
  private initialized = false;

  private constructor() {
    // Store config without apiKey initially (lazy load)
    this.config = {
      ...DEFAULT_AI_CONFIG,
      apiKey: '', // Will be loaded lazily when needed
    };
  }

  /**
   * Get singleton instance (lazy - only creates instance, doesn't initialize client)
   */
  public static getInstance(): AIConfigManager {
    if (!AIConfigManager.instance) {
      AIConfigManager.instance = new AIConfigManager();
    }
    return AIConfigManager.instance;
  }

  /**
   * Initialize the AI client (lazy - only called when actually needed)
   */
  private initializeClient(): void {
    if (this.initialized) {
      return;
    }

    try {
      // Load API key lazily at initialization time, not at import time
      const apiKey = getApiKey();
      this.config.apiKey = apiKey;

      this.client = new OpenAI({
        apiKey: this.config.apiKey,
        baseURL: this.config.baseURL,
        timeout: this.config.timeout,
        dangerouslyAllowBrowser: false,
        maxRetries: 3,
      });

      this.initialized = true;

      logger.info({
        baseURL: this.config.baseURL,
        model: this.config.model,
      }, 'AI Client initialized successfully');
    } catch (error) {
      logger.error({ error }, 'Failed to initialize AI client');
      throw error;
    }
  }

  /**
   * Get the OpenAI client instance
   */
  public getClient(): OpenAI {
    this.initializeClient();
    return this.client as OpenAI;
  }

  /**
   * Get current configuration
   */
  public getConfig(): AIConfig {
    return { ...this.config };
  }

  /**
   * Update configuration dynamically
   */
  public updateConfig(updates: Partial<AIConfig>): void {
    this.config = { ...this.config, ...updates };

    // Reinitialize client if critical config changed
    if (updates.apiKey || updates.baseURL || updates.timeout) {
      this.initializeClient();
    }

    logger.info({ config: this.config }, 'AI configuration updated');
  }

  /**
   * Generate completion for exercise generation
   */
  public async generateCompletion(
    systemPrompt: string,
    userPrompt: string,
    options?: {
      temperature?: number;
      maxTokens?: number;
      responseFormat?: { type: 'json_object' | 'text' };
    }
  ): Promise<string> {
    try {
      const client = this.getClient();

      const response = await client.chat.completions.create({
        model: this.config.model,
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: userPrompt },
        ],
        temperature: options?.temperature ?? this.config.temperature,
        max_tokens: options?.maxTokens ?? this.config.maxTokens,
        response_format: options?.responseFormat ?? { type: 'json_object' },
      });

      const content = response.choices[0]?.message?.content;

      if (!content) {
        throw new Error('Empty response from AI model');
      }

      logger.debug({
        model: this.config.model,
        tokensUsed: response.usage?.total_tokens,
        finishReason: response.choices[0]?.finish_reason,
      }, 'AI completion generated successfully');

      return content;
    } catch (error) {
      logger.error({ error }, 'Failed to generate AI completion');
      throw error;
    }
  }

  /**
   * Generate multiple exercises in a single request
   */
  public async generateExercises(
    request: AIExerciseRequest
  ): Promise<AIGeneratedExercise[]> {
    const { PromptBuilder } = await import('../modules/exercise/generators/prompt-builder');
    const { NotationPreserver } = await import('../modules/exercise/generators/notation-preserver');

    const promptBuilder = new PromptBuilder();
    const notationPreserver = new NotationPreserver();

    // Build prompt based on topic and requirements
    const { systemPrompt, userPrompt } = promptBuilder.buildExercisePrompt(request);

    try {
      // Generate exercises
      const response = await this.generateCompletion(systemPrompt, userPrompt);

      // Parse JSON response
      let parsedResponse: any;
      try {
        parsedResponse = JSON.parse(response);
      } catch (parseError) {
        logger.error({ response }, 'Failed to parse AI response as JSON');
        throw new Error('Invalid JSON response from AI model');
      }

      // Extract exercises
      const exercises: AIGeneratedExercise[] = parsedResponse.exercises || [];

      // Validate and fix notations
      const validatedExercises = exercises.map(exercise => ({
        ...exercise,
        ...notationPreserver.validateAndFixNotations(request.topic, exercise),
      }));

      logger.info({
        count: validatedExercises.length,
        topic: request.topic,
        difficulty: request.difficulty,
      }, 'Exercises generated and validated successfully');

      return validatedExercises;
    } catch (error) {
      logger.error({ error, request }, 'Failed to generate exercises');
      throw error;
    }
  }

  /**
   * Generate exercises with progress callback for SSE streaming
   * The progress callback receives a percentage (0-100) during generation
   */
  public async generateExercisesWithProgress(
    request: AIExerciseRequest,
    onProgress?: (progress: number) => void
  ): Promise<AIGeneratedExercise[]> {
    const { PromptBuilder } = await import('../modules/exercise/generators/prompt-builder');
    const { NotationPreserver } = await import('../modules/exercise/generators/notation-preserver');

    const promptBuilder = new PromptBuilder();
    const notationPreserver = new NotationPreserver();

    // Progress: 0-10% - Building prompts
    if (onProgress) onProgress(5);

    // Build prompt based on topic and requirements
    const { systemPrompt, userPrompt } = promptBuilder.buildExercisePrompt(request);

    if (onProgress) onProgress(10);

    try {
      // Progress: 10-80% - AI generation (the longest part)
      // Generate exercises using streaming for progress feedback
      const response = await this.generateCompletionWithProgress(
        systemPrompt,
        userPrompt,
        (streamProgress) => {
          if (onProgress) {
            // Map stream progress (0-100) to our range (10-80)
            const mappedProgress = 10 + Math.floor(streamProgress * 0.7);
            onProgress(mappedProgress);
          }
        }
      );

      // Progress: 80-90% - Parsing response
      if (onProgress) onProgress(80);

      // Parse JSON response
      let parsedResponse: any;
      try {
        parsedResponse = JSON.parse(response);
      } catch (parseError) {
        logger.error({ response }, 'Failed to parse AI response as JSON');
        throw new Error('Invalid JSON response from AI model');
      }

      if (onProgress) onProgress(85);

      // Extract exercises
      const exercises: AIGeneratedExercise[] = parsedResponse.exercises || [];

      // Progress: 90-100% - Validating notations
      if (onProgress) onProgress(90);

      // Validate and fix notations
      const validatedExercises = exercises.map(exercise => ({
        ...exercise,
        ...notationPreserver.validateAndFixNotations(request.topic, exercise),
      }));

      if (onProgress) onProgress(100);

      logger.info({
        count: validatedExercises.length,
        topic: request.topic,
        difficulty: request.difficulty,
      }, 'Exercises generated and validated successfully');

      return validatedExercises;
    } catch (error) {
      logger.error({ error, request }, 'Failed to generate exercises');
      throw error;
    }
  }

  /**
   * Generate completion with progress simulation for long requests
   * Uses periodic progress updates during the API call
   */
  private async generateCompletionWithProgress(
    systemPrompt: string,
    userPrompt: string,
    onProgress?: (progress: number) => void,
    options?: {
      temperature?: number;
      maxTokens?: number;
      responseFormat?: { type: 'json_object' | 'text' };
    }
  ): Promise<string> {
    try {
      const client = this.getClient();

      // Start progress simulation - the API call can take up to 60 seconds
      let simulatedProgress = 10;
      const progressInterval = setInterval(() => {
        if (onProgress && simulatedProgress < 80) {
          // Slowly increment progress up to 80% while waiting
          // Rate: ~1% per 750ms for a ~60s total wait time
          simulatedProgress += 1;
          onProgress(simulatedProgress);
        }
      }, 750);

      const response = await client.chat.completions.create({
        model: this.config.model,
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: userPrompt },
        ],
        temperature: options?.temperature ?? this.config.temperature,
        max_tokens: options?.maxTokens ?? this.config.maxTokens,
        response_format: options?.responseFormat ?? { type: 'json_object' },
      });

      clearInterval(progressInterval);

      const content = response.choices[0]?.message?.content;

      if (!content) {
        throw new Error('Empty response from AI model');
      }

      logger.debug({
        model: this.config.model,
        tokensUsed: response.usage?.total_tokens,
        finishReason: response.choices[0]?.finish_reason,
      }, 'AI completion generated successfully');

      return content;
    } catch (error) {
      logger.error({ error }, 'Failed to generate AI completion');
      throw error;
    }
  }

  /**
   * Health check for AI service
   */
  public async healthCheck(): Promise<boolean> {
    try {
      const response = await this.generateCompletion(
        'You are a helpful assistant.',
        'Respond with {"status": "ok"} in JSON format.',
        { maxTokens: 50, responseFormat: { type: 'json_object' } }
      );

      const parsed = JSON.parse(response);
      return parsed.status === 'ok';
    } catch (error) {
      logger.error({ error }, 'AI health check failed');
      return false;
    }
  }

  /**
   * Get model info
   */
  public getModelInfo(): { model: string; baseURL: string; maxTokens: number } {
    return {
      model: this.config.model,
      baseURL: this.config.baseURL,
      maxTokens: this.config.maxTokens,
    };
  }
}

/**
 * Export singleton instance
 */
export const aiConfig = AIConfigManager.getInstance();

/**
 * Export convenience functions
 */
export function getAIClient(): OpenAI {
  return aiConfig.getClient();
}

export async function generateAICompletion(
  systemPrompt: string,
  userPrompt: string,
  options?: {
    temperature?: number;
    maxTokens?: number;
    responseFormat?: { type: 'json_object' | 'text' };
  }
): Promise<string> {
  return await aiConfig.generateCompletion(systemPrompt, userPrompt, options);
}

export async function generateExercises(
  request: AIExerciseRequest
): Promise<AIGeneratedExercise[]> {
  return await aiConfig.generateExercises(request);
}

export default aiConfig;
