/**
 * Score Calculator
 *
 * Calculates points earned from exercise attempts based on:
 * - Exercise difficulty (Básico: 10, Intermedio: 20, Avanzado: 30)
 * - Streak bonus (3+ days: +50%)
 * - First attempt bonus (+20%)
 * - Speed bonus (<60s: +10%)
 * - Hint penalty (-2 per hint)
 */

import { ExerciseDifficulty } from '@prisma/client';
import { prisma } from '../../../shared/database/prisma.client';
import { logger } from '../../../shared/utils/logger';
import { StreakCalculator } from './streak.calculator';

// ============================================
// CONSTANTS
// ============================================

const BASE_POINTS = {
  [ExerciseDifficulty.BASIC]: 10,
  [ExerciseDifficulty.INTERMEDIATE]: 20,
  [ExerciseDifficulty.ADVANCED]: 30,
  [ExerciseDifficulty.EXPERT]: 40,
} as const;

const BONUS_MULTIPLIERS = {
  STREAK_3_DAYS: 1.5, // +50%
  FIRST_ATTEMPT: 1.2, // +20%
  SPEED_BONUS: 1.1, // +10%
} as const;

const HINT_PENALTY = 2; // -2 points per hint
const SPEED_THRESHOLD_SECONDS = 60;

// ============================================
// TYPES
// ============================================

export interface ScoreCalculationParams {
  exerciseId: string;
  userId: string;
  isCorrect: boolean;
  timeSpentSeconds: number;
  hintsUsed: number;
  attemptNumber: number;
}

export interface ScoreCalculationResult {
  basePoints: number;
  streakMultiplier: number;
  firstAttemptMultiplier: number;
  speedMultiplier: number;
  hintPenalty: number;
  finalPoints: number;
  breakdown: string[];
}

export interface StreakInfo {
  currentStreak: number;
  hasStreakBonus: boolean;
}

// ============================================
// SCORE CALCULATOR
// ============================================

export class ScoreCalculator {
  /**
   * Calculate points earned from an exercise attempt
   */
  static async calculate(params: ScoreCalculationParams): Promise<ScoreCalculationResult> {
    const { exerciseId, userId, isCorrect, timeSpentSeconds, hintsUsed, attemptNumber } = params;

    // Get exercise difficulty
    const exercise = await prisma.exercise.findUnique({
      where: { id: exerciseId },
      select: { difficulty: true },
    });

    if (!exercise) {
      throw new Error(`Exercise ${exerciseId} not found`);
    }

    // Get user streak info
    const streakInfo = await this.getUserStreak(userId);

    const breakdown: string[] = [];
    let totalPoints = 0;

    // 1. Base points
    const basePoints = BASE_POINTS[exercise.difficulty];
    totalPoints = basePoints;
    breakdown.push(`Base: ${basePoints} puntos (${exercise.difficulty})`);

    // 2. First attempt bonus (+20%)
    let firstAttemptMultiplier = 1.0;
    if (isCorrect && attemptNumber === 1) {
      firstAttemptMultiplier = BONUS_MULTIPLIERS.FIRST_ATTEMPT;
      const bonusPoints = Math.round(basePoints * (firstAttemptMultiplier - 1));
      totalPoints += bonusPoints;
      breakdown.push(`Primer intento: +${bonusPoints} puntos (+20%)`);
    }

    // 3. Speed bonus (+10% if completed in under 60 seconds)
    let speedMultiplier = 1.0;
    if (isCorrect && timeSpentSeconds < SPEED_THRESHOLD_SECONDS) {
      speedMultiplier = BONUS_MULTIPLIERS.SPEED_BONUS;
      const bonusPoints = Math.round(basePoints * (speedMultiplier - 1));
      totalPoints += bonusPoints;
      breakdown.push(`Velocidad: +${bonusPoints} puntos (+10%, <60s)`);
    }

    // 4. Streak bonus (+50% for 3+ day streak)
    let streakMultiplier = 1.0;
    if (isCorrect && streakInfo.hasStreakBonus) {
      streakMultiplier = BONUS_MULTIPLIERS.STREAK_3_DAYS;
      const bonusPoints = Math.round(basePoints * (streakMultiplier - 1));
      totalPoints += bonusPoints;
      breakdown.push(`Racha (${streakInfo.currentStreak} días): +${bonusPoints} puntos (+50%)`);
    }

    // 5. Hint penalty (-2 per hint)
    const hintPenalty = hintsUsed * HINT_PENALTY;
    totalPoints = Math.max(0, totalPoints - hintPenalty);
    if (hintPenalty > 0) {
      breakdown.push(`Pistas: -${hintPenalty} puntos (-2 cada una)`);
    }

    // If incorrect, no points
    if (!isCorrect) {
      totalPoints = 0;
      breakdown.push('Incorrecto: 0 puntos');
    }

    const result: ScoreCalculationResult = {
      basePoints,
      streakMultiplier,
      firstAttemptMultiplier,
      speedMultiplier,
      hintPenalty,
      finalPoints: totalPoints,
      breakdown,
    };

    logger.info({
      userId,
      exerciseId,
      finalPoints: totalPoints,
      breakdown,
    }, 'Score calculated');

    return result;
  }

  /**
   * Get user's current streak information
   * Delegates to StreakCalculator for robust timezone-aware calculation.
   */
  static async getUserStreak(userId: string, timezone?: string): Promise<StreakInfo> {
    return StreakCalculator.getUserStreakInfo(userId, timezone);
  }

  /**
   * Calculate total points for a user across all attempts
   */
  static async calculateUserTotalPoints(userId: string): Promise<number> {
    const result = await prisma.exerciseAttempt.aggregate({
      where: {
        userId,
        status: 'CORRECT',
      },
      _sum: {
        pointsEarned: true,
      },
    });

    return result._sum.pointsEarned || 0;
  }

  /**
   * Calculate total points for a user in a specific module
   */
  static async calculateUserModulePoints(userId: string, moduleId: string): Promise<number> {
    const result = await prisma.exerciseAttempt.aggregate({
      where: {
        userId,
        status: 'CORRECT',
        exercises: {
          moduleId,
        },
      },
      _sum: {
        pointsEarned: true,
      },
    });

    return result._sum?.pointsEarned || 0;
  }

  /**
   * Recalculate all points for a user (e.g., after scoring rules change)
   */
  static async recalculateUserPoints(userId: string): Promise<void> {
    // Get all exercise attempts for the user
    const attempts = await prisma.exerciseAttempt.findMany({
      where: { userId },
      include: {
        exercises: {
          select: {
            difficulty: true,
          },
        },
      },
    });

    // Update rankings will be handled by the position calculator
    logger.info({ userId, attemptsCount: attempts.length }, 'Recalculating user points');
  }

  /**
   * Get quick score estimate (without full calculation)
   */
  static getQuickScore(
    difficulty: ExerciseDifficulty,
    isCorrect: boolean,
    hintsUsed: number
  ): number {
    if (!isCorrect) return 0;

    let points = BASE_POINTS[difficulty];
    points -= hintsUsed * HINT_PENALTY;
    return Math.max(0, points);
  }

  /**
   * Calculate maximum possible points for an exercise
   */
  static getMaxPossiblePoints(
    difficulty: ExerciseDifficulty,
    hasStreak: boolean
  ): number {
    let points: number = BASE_POINTS[difficulty];

    // First attempt bonus
    points = Math.round(points * BONUS_MULTIPLIERS.FIRST_ATTEMPT);

    // Speed bonus
    points = Math.round(points * BONUS_MULTIPLIERS.SPEED_BONUS);

    // Streak bonus
    if (hasStreak) {
      points = Math.round(points * BONUS_MULTIPLIERS.STREAK_3_DAYS);
    }

    return points;
  }
}

export default ScoreCalculator;
