/**
 * Position Calculator
 *
 * Calculates ranking positions for users globally and per module.
 * Handles tie-breaking and updates ranking tables.
 */

import { prisma } from '../../../shared/database/prisma.client';
import { logger } from '../../../shared/utils/logger';
import { ScoreCalculator } from './score.calculator';

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

export interface RankingPosition {
  userId: string;
  position: number;
  points: number;
  exercisesCompleted: number;
  streak: number;
  perfectExercises: number;
  averageScore?: number | undefined;
  achievementsUnlocked: number;
  tied?: boolean;
}

export interface GlobalRankingEntry {
  position: number;
  points: number;
  exercisesCompleted: number;
  streak: number;
  perfectExercises: number;
  achievementsUnlocked: number;
}

export interface ModuleRankingEntry extends GlobalRankingEntry {
  moduleId: string;
  moduleName: string;
}

export interface UserRankingInfo {
  global: GlobalRankingEntry;
  byModule: ModuleRankingEntry[];
  totalUsers: number;
}

// ============================================
// POSITION CALCULATOR
// ============================================

export class PositionCalculator {
  /**
   * Update global ranking for a specific user
   */
  static async updateUserGlobalRanking(userId: string): Promise<RankingPosition> {
    // Get user's stats
    const stats = await this.getUserGlobalStats(userId);

    // Check if ranking entry exists (findFirst because findUnique doesn't work with null in compound key)
    const existing = await prisma.ranking.findFirst({
      where: {
        userId,
        moduleId: null,
      },
    });

    if (existing) {
      // Update existing entry
      const updated = await prisma.ranking.update({
        where: { id: existing.id },
        data: {
          points: stats.totalPoints,
          exercisesCompleted: stats.exercisesCompleted,
          streak: stats.currentStreak,
          perfectExercises: stats.perfectExercises,
          averageScore: stats.averageScore,
          achievementsUnlocked: stats.achievementsUnlocked,
          lastUpdated: new Date(),
        },
      });

      // Recalculate position
      const position = await this.calculatePosition(userId, null);
      await prisma.ranking.update({
        where: { id: updated.id },
        data: { position },
      });

      return {
        userId,
        position,
        points: stats.totalPoints,
        exercisesCompleted: stats.exercisesCompleted,
        streak: stats.currentStreak,
        perfectExercises: stats.perfectExercises,
        averageScore: stats.averageScore,
        achievementsUnlocked: stats.achievementsUnlocked,
      };
    } else {
      // Create new entry
      const position = await this.getNextAvailablePosition(null);
      const created = await prisma.ranking.create({
        data: {
          userId,
          moduleId: null,
          position,
          points: stats.totalPoints,
          exercisesCompleted: stats.exercisesCompleted,
          streak: stats.currentStreak,
          perfectExercises: stats.perfectExercises,
          averageScore: stats.averageScore,
          achievementsUnlocked: stats.achievementsUnlocked,
        },
      });

      return {
        userId,
        position: created.position,
        points: created.points,
        exercisesCompleted: created.exercisesCompleted,
        streak: created.streak,
        perfectExercises: created.perfectExercises,
        averageScore: created.averageScore ?? undefined,
        achievementsUnlocked: created.achievementsUnlocked,
      };
    }
  }

  /**
   * Update module ranking for a specific user
   */
  static async updateUserModuleRanking(userId: string, moduleId: string): Promise<RankingPosition> {
    // Get user's stats for this module
    const stats = await this.getUserModuleStats(userId, moduleId);

    // Check if ranking entry exists
    const existing = await prisma.ranking.findUnique({
      where: {
        userId_moduleId: {
          userId,
          moduleId,
        },
      },
    });

    if (existing) {
      // Update existing entry
      const updated = await prisma.ranking.update({
        where: { id: existing.id },
        data: {
          points: stats.totalPoints,
          exercisesCompleted: stats.exercisesCompleted,
          streak: stats.currentStreak,
          perfectExercises: stats.perfectExercises,
          averageScore: stats.averageScore,
          achievementsUnlocked: 0, // Module-specific achievements not tracked yet
          lastUpdated: new Date(),
        },
      });

      // Recalculate position
      const position = await this.calculatePosition(userId, moduleId);
      await prisma.ranking.update({
        where: { id: updated.id },
        data: { position },
      });

      return {
        userId,
        position,
        points: stats.totalPoints,
        exercisesCompleted: stats.exercisesCompleted,
        streak: stats.currentStreak,
        perfectExercises: stats.perfectExercises,
        averageScore: stats.averageScore ?? undefined,
        achievementsUnlocked: 0,
      };
    } else {
      // Create new entry
      const position = await this.getNextAvailablePosition(moduleId);
      const created = await prisma.ranking.create({
        data: {
          userId,
          moduleId,
          position,
          points: stats.totalPoints,
          exercisesCompleted: stats.exercisesCompleted,
          streak: stats.currentStreak,
          perfectExercises: stats.perfectExercises,
          averageScore: stats.averageScore,
          achievementsUnlocked: 0,
        },
      });

      return {
        userId,
        position: created.position,
        points: created.points,
        exercisesCompleted: created.exercisesCompleted,
        streak: created.streak,
        perfectExercises: created.perfectExercises,
        averageScore: created.averageScore ?? undefined,
        achievementsUnlocked: 0,
      };
    }
  }

  /**
   * Get user's global stats
   */
  private static async getUserGlobalStats(userId: string): Promise<{
    totalPoints: number;
    exercisesCompleted: number;
    currentStreak: number;
    perfectExercises: number;
    averageScore: number;
    achievementsUnlocked: number;
  }> {
    // Get points from attempts
    const pointsResult = await prisma.exerciseAttempt.aggregate({
      where: {
        userId,
        status: 'CORRECT',
      },
      _sum: {
        pointsEarned: true,
      },
    });

    // Get completed exercises (unique)
    const completedExercises = await prisma.exerciseAttempt.groupBy({
      by: ['exerciseId'],
      where: {
        userId,
        status: 'CORRECT',
      },
    });

    // Get perfect exercises
    const perfectCount = await prisma.exerciseAttempt.count({
      where: {
        userId,
        isPerfect: true,
      },
    });

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

    // Get average score (points earned out of max possible)
    const attempts = await prisma.exerciseAttempt.findMany({
      where: { userId },
      select: { pointsEarned: true },
    });

    const averageScore =
      attempts.length > 0
        ? attempts.reduce((sum, a) => sum + a.pointsEarned, 0) / attempts.length
        : 0;

    // Get achievements unlocked
    const achievementsCount = await prisma.userAchievement.count({
      where: {
        userId,
        unlockedAt: { not: null },
      },
    });

    return {
      totalPoints: pointsResult._sum.pointsEarned || 0,
      exercisesCompleted: completedExercises.length,
      currentStreak: streakInfo.currentStreak,
      perfectExercises: perfectCount,
      averageScore: Math.round(averageScore * 100) / 100,
      achievementsUnlocked: achievementsCount,
    };
  }

  /**
   * Get user's module-specific stats
   */
  private static async getUserModuleStats(userId: string, moduleId: string): Promise<{
    totalPoints: number;
    exercisesCompleted: number;
    currentStreak: number;
    perfectExercises: number;
    averageScore: number | null;
  }> {
    // Get points from attempts in this module
    const pointsResult = await prisma.exerciseAttempt.aggregate({
      where: {
        userId,
        status: 'CORRECT',
        exercises: {
          moduleId,
        },
      },
      _sum: {
        pointsEarned: true,
      },
    });

    // Get completed exercises in this module
    const completedExercises = await prisma.exerciseAttempt.groupBy({
      by: ['exerciseId'],
      where: {
        userId,
        status: 'CORRECT',
        exercises: {
          moduleId,
        },
      },
    });

    // Get perfect exercises in this module
    const perfectCount = await prisma.exerciseAttempt.count({
      where: {
        userId,
        isPerfect: true,
        exercises: {
          moduleId,
        },
      },
    });

    // Get streak info (global streak, not module-specific)
    const streakInfo = await ScoreCalculator.getUserStreak(userId);

    // Get average score for module
    const attempts = await prisma.exerciseAttempt.findMany({
      where: {
        userId,
        exercises: { moduleId },
      },
      select: { pointsEarned: true },
    });

    const averageScore =
      attempts.length > 0
        ? attempts.reduce((sum, a) => sum + a.pointsEarned, 0) / attempts.length
        : 0;

    return {
      totalPoints: pointsResult._sum?.pointsEarned || 0,
      exercisesCompleted: completedExercises.length,
      currentStreak: streakInfo.currentStreak,
      perfectExercises: perfectCount,
      averageScore: averageScore > 0 ? Math.round(averageScore * 100) / 100 : null,
    };
  }

  /**
   * Calculate user's position in ranking
   * Ties are allowed (multiple users can have same position)
   */
  private static async calculatePosition(userId: string, moduleId: string | null): Promise<number> {
    // Get user's points
    // Use findFirst because findUnique doesn't work with null in compound key
    const userRanking = await prisma.ranking.findFirst({
      where: {
        userId,
        ...(moduleId ? { moduleId } : { moduleId: null }),
      },
      select: { points: true },
    });

    if (!userRanking) {
      return 1;
    }

    // Count users with more points
    const higherRanked = await prisma.ranking.count({
      where: {
        moduleId,
        points: {
          gt: userRanking.points,
        },
      },
    });

    return higherRanked + 1;
  }

  /**
   * Get next available position (for new entries)
   */
  private static async getNextAvailablePosition(moduleId: string | null): Promise<number> {
    const count = await prisma.ranking.count({
      where: { moduleId },
    });

    return count + 1;
  }

  /**
   * Recalculate all global rankings
   * Use this periodically or when scoring rules change
   */
  static async recalculateAllGlobalRankings(): Promise<void> {
    logger.info('Starting global ranking recalculation');

    // Get all users with at least one correct attempt
    const users = await prisma.exerciseAttempt.groupBy({
      by: ['userId'],
      where: { status: 'CORRECT' },
    });

    let processed = 0;
    for (const { userId } of users) {
      await this.updateUserGlobalRanking(userId);
      processed++;

      if (processed % 100 === 0) {
        logger.info({ processed, total: users.length }, 'Ranking recalculation progress');
      }
    }

    logger.info({ total: users.length }, 'Global ranking recalculation complete');
  }

  /**
   * Recalculate all module rankings
   */
  static async recalculateAllModuleRankings(moduleId: string): Promise<void> {
    logger.info({ moduleId }, 'Starting module ranking recalculation');

    // Get all users with correct attempts in this module
    const users = await prisma.exerciseAttempt.groupBy({
      by: ['userId'],
      where: {
        status: 'CORRECT',
        exercises: { moduleId },
      },
    });

    let processed = 0;
    for (const { userId } of users) {
      await this.updateUserModuleRanking(userId, moduleId);
      processed++;

      if (processed % 100 === 0) {
        logger.info({ processed, total: users.length }, 'Module ranking recalculation progress');
      }
    }

    logger.info({ moduleId, total: users.length }, 'Module ranking recalculation complete');
  }

  /**
   * Get global ranking (anonymous - no personal data)
   */
  static async getGlobalRanking(
    limit: number = 100,
    offset: number = 0
  ): Promise<GlobalRankingEntry[]> {
    const rankings = await prisma.ranking.findMany({
      where: { moduleId: null },
      orderBy: [
        { points: 'desc' },
        { exercisesCompleted: 'desc' },
        { lastUpdated: 'asc' }, // Tie-breaker: earlier achievement wins
      ],
      take: limit,
      skip: offset,
      select: {
        position: true,
        points: true,
        exercisesCompleted: true,
        streak: true,
        perfectExercises: true,
        achievementsUnlocked: true,
      },
    });

    // Assign positions (handle ties)
    return rankings.map((r) => ({
      position: r.position,
      points: r.points,
      exercisesCompleted: r.exercisesCompleted,
      streak: r.streak,
      perfectExercises: r.perfectExercises,
      achievementsUnlocked: r.achievementsUnlocked,
    }));
  }

  /**
   * Get module ranking (anonymous)
   */
  static async getModuleRanking(
    moduleId: string,
    limit: number = 100,
    offset: number = 0
  ): Promise<ModuleRankingEntry[]> {
    // Get module info
    const moduleInfo = await prisma.modules.findUnique({
      where: { id: moduleId },
      select: { name: true },
    });

    const rankings = await prisma.ranking.findMany({
      where: { moduleId },
      orderBy: [
        { points: 'desc' },
        { exercisesCompleted: 'desc' },
        { lastUpdated: 'asc' },
      ],
      take: limit,
      skip: offset,
      select: {
        position: true,
        points: true,
        exercisesCompleted: true,
        streak: true,
        perfectExercises: true,
        achievementsUnlocked: true,
      },
    });

    return rankings.map((r) => ({
      position: r.position,
      points: r.points,
      exercisesCompleted: r.exercisesCompleted,
      streak: r.streak,
      perfectExercises: r.perfectExercises,
      achievementsUnlocked: r.achievementsUnlocked,
      moduleId,
      moduleName: moduleInfo?.name || 'Módulo Desconocido',
    }));
  }

  /**
   * Get user's complete ranking information (anonymous representation)
   */
  static async getUserRankingInfo(userId: string): Promise<UserRankingInfo> {
    // Get global ranking (use findFirst because findUnique doesn't work with null in compound key)
    const globalRanking = await prisma.ranking.findFirst({
      where: {
        userId,
        moduleId: null,
      },
      select: {
        position: true,
        points: true,
        exercisesCompleted: true,
        streak: true,
        perfectExercises: true,
        achievementsUnlocked: true,
      },
    });

    if (!globalRanking) {
      // User hasn't completed any exercises yet
      return {
        global: {
          position: 0,
          points: 0,
          exercisesCompleted: 0,
          streak: 0,
          perfectExercises: 0,
          achievementsUnlocked: 0,
        },
        byModule: [],
        totalUsers: 0,
      };
    }

    // Get module rankings
    const moduleRankings = await prisma.ranking.findMany({
      where: {
        userId,
        moduleId: { not: null },
      },
      include: {
        modules: {
          select: {
            id: true,
            name: true,
          },
        },
      },
    });

    // Get total users in global ranking
    const totalUsers = await prisma.ranking.count({
      where: { moduleId: null },
    });

    return {
      global: {
        position: globalRanking.position,
        points: globalRanking.points,
        exercisesCompleted: globalRanking.exercisesCompleted,
        streak: globalRanking.streak,
        perfectExercises: globalRanking.perfectExercises,
        achievementsUnlocked: globalRanking.achievementsUnlocked,
      },
      byModule: moduleRankings
        .filter((r) => r.modules !== null)
        .map((r) => ({
          position: r.position,
          points: r.points,
          exercisesCompleted: r.exercisesCompleted,
          streak: r.streak,
          perfectExercises: r.perfectExercises,
          achievementsUnlocked: r.achievementsUnlocked,
          moduleId: r.modules!.id,
          moduleName: r.modules!.name,
        })),
      totalUsers,
    };
  }

  /**
   * Get ranking around a user (for showing user's position in context)
   */
  static async getRankingAroundUser(
    userId: string,
    moduleId: string | null = null,
    radius: number = 5
  ): Promise<GlobalRankingEntry[]> {
    // Use findFirst because findUnique doesn't work with null in compound key
    const userRanking = await prisma.ranking.findFirst({
      where: {
        userId,
        ...(moduleId ? { moduleId } : { moduleId: null }),
      },
      select: { position: true },
    });

    if (!userRanking) {
      return [];
    }

    const userPosition = userRanking.position;
    const start = Math.max(1, userPosition - radius);
    const end = userPosition + radius;

    const rankings = await prisma.ranking.findMany({
      where: {
        moduleId,
        position: {
          gte: start,
          lte: end,
        },
      },
      orderBy: [{ position: 'asc' }],
      select: {
        position: true,
        points: true,
        exercisesCompleted: true,
        streak: true,
        perfectExercises: true,
        achievementsUnlocked: true,
      },
    });

    return rankings;
  }
}

export default PositionCalculator;
