/**
 * Exercise Generator Worker
 *
 * Background worker for generating AI-powered mathematical exercises.
 * Uses MiniMax-M2.5 via DashScope for exercise generation with
 * proper notation validation and database persistence.
 */

import { prisma } from '../shared/database/prisma.client';
import { logger } from '../shared/utils/logger';
import { aiConfig, AIGeneratedExercise, AIExerciseRequest } from '../config/ai';
import { PromptBuilder } from '../modules/exercise/generators/prompt-builder';
import { NotationPreserver } from '../modules/exercise/generators/notation-preserver';
import { ExerciseType, ExerciseDifficulty, TopicType, ModuleType } from '@prisma/client';

export interface ExerciseGeneratorJob {
  id: string;
  topic: TopicType;
  moduleType: ModuleType;
  exerciseType: ExerciseType;
  difficulty: ExerciseDifficulty;
  count: number;
  moduleId?: string;
  topicId?: string;
  context?: string;
  concepts?: string[];
  learningObjectives?: string[];
  isPublished?: boolean;
  customPoints?: number;
  timeLimitSeconds?: number;
}

export interface ExerciseGeneratorResult {
  jobId: string;
  success: boolean;
  exercisesGenerated: number;
  exercisesSaved: number;
  exerciseIds: string[];
  errors: string[];
  metadata: {
    topic: TopicType;
    difficulty: ExerciseDifficulty;
    modelUsed: string;
    generationTimeMs: number;
  };
}

export interface ExerciseGeneratorEvent {
  type: 'STARTED' | 'PROGRESS' | 'COMPLETED' | 'FAILED';
  jobId: string;
  data?: any;
  error?: string;
}

type ProgressCallback = (event: ExerciseGeneratorEvent) => void;

class ExerciseGeneratorWorker {
  private promptBuilder: PromptBuilder;
  private notationPreserver: NotationPreserver;
  private isProcessing: boolean;
  private currentJobId: string | null;

  constructor() {
    this.promptBuilder = new PromptBuilder();
    this.notationPreserver = new NotationPreserver();
    this.isProcessing = false;
    this.currentJobId = null;
  }

  async processJob(
    job: ExerciseGeneratorJob,
    onProgress?: ProgressCallback
  ): Promise<ExerciseGeneratorResult> {
    const startTime = Date.now();
    const errors: string[] = [];
    const exerciseIds: string[] = [];

    this.isProcessing = true;
    this.currentJobId = job.id;

    logger.info({
      jobId: job.id,
      topic: job.topic,
      moduleType: job.moduleType,
      difficulty: job.difficulty,
      count: job.count,
    }, 'Starting exercise generation job');

    onProgress?.({
      type: 'STARTED',
      jobId: job.id,
      data: { topic: job.topic, difficulty: job.difficulty, count: job.count },
    });

    try {
      const request: AIExerciseRequest = {
        topic: job.topic,
        moduleType: job.moduleType,
        exerciseType: job.exerciseType,
        difficulty: job.difficulty,
        count: job.count,
        context: job.context,
        concepts: job.concepts,
        learningObjectives: job.learningObjectives,
      };

      const { systemPrompt, userPrompt } = this.promptBuilder.buildExercisePrompt(request);

      const response = await aiConfig.generateCompletion(systemPrompt, userPrompt);

      let parsedResponse: { exercises: AIGeneratedExercise[] };
      try {
        parsedResponse = JSON.parse(response);
      } catch (parseError) {
        throw new Error(`Failed to parse AI response: ${parseError instanceof Error ? parseError.message : 'Invalid JSON'}`);
      }

      const generatedExercises = parsedResponse.exercises || [];

      if (generatedExercises.length === 0) {
        throw new Error('No exercises generated by AI');
      }

      logger.info({
        jobId: job.id,
        generatedCount: generatedExercises.length,
      }, 'Exercises generated, beginning validation and storage');

      for (let i = 0; i < generatedExercises.length; i++) {
        const exercise = generatedExercises[i];

        onProgress?.({
          type: 'PROGRESS',
          jobId: job.id,
          data: { current: i + 1, total: generatedExercises.length, step: 'validating' },
        });

        const validated = this.notationPreserver.validateAndFixNotations(job.topic, exercise);

        if (!validated.validation.isValid) {
          logger.warn({
            jobId: job.id,
            exerciseIndex: i,
            issues: validated.validation.issues,
          }, 'Exercise had notation issues, attempting to fix');
        }

        try {
          const exerciseId = await this.saveExercise(
            validated.correctedExercise,
            job,
            request
          );
          exerciseIds.push(exerciseId);

          onProgress?.({
            type: 'PROGRESS',
            jobId: job.id,
            data: { current: i + 1, total: generatedExercises.length, step: 'saved', exerciseId },
          });
        } catch (saveError) {
          const errorMsg = saveError instanceof Error ? saveError.message : 'Unknown save error';
          errors.push(`Failed to save exercise ${i + 1}: ${errorMsg}`);
          logger.error({
            jobId: job.id,
            exerciseIndex: i,
            error: saveError,
          }, 'Failed to save exercise to database');
        }
      }

      const generationTime = Date.now() - startTime;

      logger.info({
        jobId: job.id,
        exercisesGenerated: generatedExercises.length,
        exercisesSaved: exerciseIds.length,
        errorsCount: errors.length,
        generationTimeMs: generationTime,
      }, 'Exercise generation job completed');

      onProgress?.({
        type: 'COMPLETED',
        jobId: job.id,
        data: {
          exercisesGenerated: generatedExercises.length,
          exercisesSaved: exerciseIds.length,
          exerciseIds,
          errors,
        },
      });

      return {
        jobId: job.id,
        success: exerciseIds.length > 0 && errors.length === 0,
        exercisesGenerated: generatedExercises.length,
        exercisesSaved: exerciseIds.length,
        exerciseIds,
        errors,
        metadata: {
          topic: job.topic,
          difficulty: job.difficulty,
          modelUsed: aiConfig.getModelInfo().model,
          generationTimeMs: generationTime,
        },
      };
    } catch (error) {
      const generationTime = Date.now() - startTime;
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';

      logger.error({
        jobId: job.id,
        error,
        generationTimeMs: generationTime,
      }, 'Exercise generation job failed');

      onProgress?.({
        type: 'FAILED',
        jobId: job.id,
        error: errorMessage,
      });

      return {
        jobId: job.id,
        success: false,
        exercisesGenerated: 0,
        exercisesSaved: 0,
        exerciseIds: [],
        errors: [errorMessage],
        metadata: {
          topic: job.topic,
          difficulty: job.difficulty,
          modelUsed: aiConfig.getModelInfo().model,
          generationTimeMs: generationTime,
        },
      };
    } finally {
      this.isProcessing = false;
      this.currentJobId = null;
    }
  }

  private async saveExercise(
    exercise: AIGeneratedExercise,
    job: ExerciseGeneratorJob,
    request: AIExerciseRequest
  ): Promise<string> {
    const order = job.moduleId
      ? await this.getNextExerciseOrder(job.moduleId)
      : 1;

    const difficulty = this.mapDifficulty(exercise.difficulty || job.difficulty);

    const exerciseData: any = {
      moduleId: job.moduleId || (await this.getOrCreateDefaultModule(job.moduleType)),
      topicId: job.topicId || null,
      type: request.exerciseType,
      difficulty,
      order,
      statement: exercise.statement,
      correctAnswer: exercise.correctAnswer,
      solutionSteps: exercise.solutionSteps,
      formulas: exercise.formulas,
      hints: exercise.hints,
      isAIGenerated: true,
      isPublished: job.isPublished ?? false,
      points: job.customPoints || this.calculatePoints(exercise, difficulty),
      timeLimitSeconds: job.timeLimitSeconds || exercise.estimatedTimeSeconds,
    };

    if (request.exerciseType === ExerciseType.MULTIPLE_CHOICE && exercise.multipleChoiceOptions) {
      exerciseData.multipleChoiceOptions = exercise.multipleChoiceOptions;
    }

    if (request.exerciseType === ExerciseType.PROOF) {
      exerciseData.proofRequirements = {
        givens: [],
        toProve: exercise.correctAnswer,
        theorems: [],
      };
    }

    if (request.exerciseType === ExerciseType.CALCULATION) {
      exerciseData.calculationSteps = {
        steps: exercise.solutionSteps?.map(s => ({
          step: s.step,
          operation: s.explanation,
          result: s.latexFormula || '',
        })) || [],
        finalResult: exercise.correctAnswer,
      };
    }

    const created = await prisma.exercise.create({
      data: exerciseData,
    });

    logger.debug({
      exerciseId: created.id,
      type: request.exerciseType,
      difficulty,
      jobId: job.id,
    }, 'Exercise saved to database');

    return created.id;
  }

  private async getNextExerciseOrder(moduleId: string): Promise<number> {
    const lastExercise = await prisma.exercise.findFirst({
      where: { moduleId },
      orderBy: { order: 'desc' },
      select: { order: true },
    });

    return (lastExercise?.order || 0) + 1;
  }

  private async getOrCreateDefaultModule(moduleType: ModuleType): Promise<string> {
    let module = await prisma.modules.findFirst({
      where: { type: moduleType },
    });

    if (!module) {
      module = await prisma.modules.create({
        data: {
          id: crypto.randomUUID(),
          name: `${moduleType} - Auto Generated`,
          description: `Module for ${moduleType} exercises`,
          type: moduleType,
          order: 1,
          isPublished: false,
        },
      });

      logger.info({ moduleId: module.id, moduleType }, 'Created default module for exercises');
    }

    return module.id;
  }

  private mapDifficulty(difficulty: string): ExerciseDifficulty {
    const normalized = difficulty.toUpperCase();
    if (Object.values(ExerciseDifficulty).includes(normalized as ExerciseDifficulty)) {
      return normalized as ExerciseDifficulty;
    }
    return ExerciseDifficulty.INTERMEDIATE;
  }

  private calculatePoints(
    exercise: AIGeneratedExercise,
    difficulty: ExerciseDifficulty
  ): number {
    const basePoints: Record<ExerciseDifficulty, number> = {
      [ExerciseDifficulty.BASIC]: 10,
      [ExerciseDifficulty.INTERMEDIATE]: 15,
      [ExerciseDifficulty.ADVANCED]: 25,
      [ExerciseDifficulty.EXPERT]: 40,
    };

    let points = basePoints[difficulty] || 15;

    if (exercise.solutionSteps && exercise.solutionSteps.length > 5) {
      points += Math.floor(exercise.solutionSteps.length / 3) * 5;
    }

    if (exercise.hints && exercise.hints.length > 2) {
      points += (exercise.hints.length - 2) * 2;
    }

    return Math.min(points, 100);
  }

  getStatus(): { isProcessing: boolean; currentJobId: string | null } {
    return {
      isProcessing: this.isProcessing,
      currentJobId: this.currentJobId,
    };
  }

  async healthCheck(): Promise<boolean> {
    try {
      const dbHealthy = await prisma.healthCheck();
      const aiHealthy = await aiConfig.healthCheck();
      return dbHealthy && aiHealthy;
    } catch (error) {
      logger.error({ error }, 'Exercise generator worker health check failed');
      return false;
    }
  }
}

export const exerciseGeneratorWorker = new ExerciseGeneratorWorker();

export async function processExerciseGenerationJob(
  job: ExerciseGeneratorJob,
  onProgress?: ProgressCallback
): Promise<ExerciseGeneratorResult> {
  return await exerciseGeneratorWorker.processJob(job, onProgress);
}

export default exerciseGeneratorWorker;
