/**
 * Badge Awarder
 *
 * Checks and awards badges to users based on their achievements.
 * Evaluates all badge conditions after each significant action.
 */

import { prisma } from '../../../shared/database/prisma.client';
import { logger } from '../../../shared/utils/logger';
import { ScoreCalculator } from './score.calculator';
import { PositionCalculator } from './position.calculator';
import {
  BADGE_DEFINITIONS,
  getBadgeByCode,
  getBadgesByRequirementType,
} from '../definitions/badge-definitions';

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

export interface BadgeAwardResult {
  awarded: boolean;
  badges: AwardedBadge[];
}

export interface AwardedBadge {
  code: string;
  name: string;
  icon: string;
  rarity: string;
  points: number;
  message: string;
}

export interface UserBadgeProgress {
  code: string;
  name: string;
  description: string;
  category: string;
  rarity: string;
  icon: string;
  requirementValue: number;
  currentProgress: number;
  unlocked: boolean;
  unlockedAt?: Date | undefined;
}

// ============================================
// BADGE AWARDER
// ============================================

export class BadgeAwarder {
  /**
   * Check and award badges after an exercise attempt
   */
  static async checkAndAwardBadgesAfterExercise(
    userId: string,
    exerciseId: string,
    isCorrect: boolean
  ): Promise<BadgeAwardResult> {
    const awardedBadges: AwardedBadge[] = [];

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

    if (!exercise) {
      logger.warn({ exerciseId }, 'Exercise not found for badge checking');
      return { awarded: false, badges: [] };
    }

    // 1. Check exercise completion badges
    const exerciseBadges = await this.checkExerciseCompletionBadges(userId);
    awardedBadges.push(...exerciseBadges);

    // 2. Check perfect score badges
    if (isCorrect) {
      const perfectBadges = await this.checkPerfectScoreBadges(userId);
      awardedBadges.push(...perfectBadges);
    }

    // 3. Check streak badges
    const streakBadges = await this.checkStreakBadges(userId);
    awardedBadges.push(...streakBadges);

    // 4. Check special time-based badges
    const timeBadges = await this.checkTimeBasedBadges(userId);
    awardedBadges.push(...timeBadges);

    // 5. Check module completion badges
    const moduleBadges = await this.checkModuleCompletionBadges(userId, exercise.moduleId);
    awardedBadges.push(...moduleBadges);

    // 6. Check autodidact badge (no hints)
    const autodidactBadges = await this.checkAutodidactBadges(userId);
    awardedBadges.push(...autodidactBadges);

    // 7. Check ranking badges (after position update)
    await PositionCalculator.updateUserGlobalRanking(userId);
    const rankingBadges = await this.checkRankingBadges(userId);
    awardedBadges.push(...rankingBadges);

    // 8. Check explorer badge (all difficulties)
    const explorerBadges = await this.checkExplorerBadge(userId);
    awardedBadges.push(...explorerBadges);

    return {
      awarded: awardedBadges.length > 0,
      badges: awardedBadges,
    };
  }

  /**
   * Check exercise completion badges
   */
  private static async checkExerciseCompletionBadges(userId: string): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];
    const badges = getBadgesByRequirementType('EXERCISES_COMPLETED');

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

    for (const badge of badges) {
      // Skip module-specific and special badges
      if (['ALGEBRA_MASTER', 'MODULE_FUNDAMENTOS', 'MODULE_SISTEMAS', 'MODULE_APLICACIONES', 'PERSISTENT', 'FIRST_TRY_WARRIOR', 'EXPLORER', 'SPEED_DEMON', 'NIGHT_OWL_EXERCISES'].includes(badge.code)) {
        continue;
      }

      if (completedCount.length >= badge.requirementValue) {
        const awarded_ = await this.awardBadge(userId, badge.code);
        if (awarded_) awarded.push(awarded_);
      }
    }

    return awarded;
  }

  /**
   * Check perfect score badges
   */
  private static async checkPerfectScoreBadges(userId: string): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];
    const badges = getBadgesByRequirementType('PERFECT_SCORES');

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

    for (const badge of badges) {
      if (perfectCount >= badge.requirementValue) {
        const awarded_ = await this.awardBadge(userId, badge.code);
        if (awarded_) awarded.push(awarded_);
      }
    }

    return awarded;
  }

  /**
   * Check streak badges
   */
  private static async checkStreakBadges(userId: string): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];
    const badges = getBadgesByRequirementType('STREAK_DAYS');

    const streakInfo = await ScoreCalculator.getUserStreak(userId);
    const currentStreak = streakInfo.currentStreak;

    for (const badge of badges) {
      if (currentStreak >= badge.requirementValue) {
        const awarded_ = await this.awardBadge(userId, badge.code);
        if (awarded_) awarded.push(awarded_);
      }
    }

    return awarded;
  }

  /**
   * Check module completion badges
   */
  private static async checkModuleCompletionBadges(
    userId: string,
    moduleId: string
  ): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];

    // Check if module is completed
    const progress = await prisma.progress.findUnique({
      where: {
        userId_moduleId: {
          userId,
          moduleId,
        },
      },
    });

    if (!progress || !progress.isCompleted) {
      return awarded;
    }

    // Award first module badge
    const firstModule = await this.awardBadge(userId, 'FIRST_MODULE');
    if (firstModule) awarded.push(firstModule);

    // Check if perfect module (100% score, all perfect)
    if (progress.percentage >= 100 && progress.perfectExercises >= progress.totalExercises) {
      const perfectModule = await this.awardBadge(userId, 'PERFECT_MODULE');
      if (perfectModule) awarded.push(perfectModule);
    }

    // Get module to check specific module badges
    const module = await prisma.modules.findUnique({
      where: { id: moduleId },
      select: { type: true },
    });

    if (module) {
      // Award specific module badges
      const moduleBadgeMap: Record<string, string> = {
        FUNDAMENTOS: 'MODULE_FUNDAMENTOS',
        SISTEMAS_ESPACIOS: 'MODULE_SISTEMAS',
        APLICACIONES: 'MODULE_APLICACIONES',
      };

      const badgeCode = moduleBadgeMap[module.type];
      if (badgeCode) {
        const moduleBadge = await this.awardBadge(userId, badgeCode);
        if (moduleBadge) awarded.push(moduleBadge);
      }
    }

    // Check for Algebra Master (all modules completed)
    const completedModules = await prisma.progress.count({
      where: {
        userId,
        isCompleted: true,
      },
    });

    if (completedModules >= 3) {
      const masterBadge = await this.awardBadge(userId, 'ALGEBRA_MASTER');
      if (masterBadge) awarded.push(masterBadge);
    }

    return awarded;
  }

  /**
   * Check ranking badges
   */
  private static async checkRankingBadges(userId: string): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];
    const badges = getBadgesByRequirementType('RANKING_POSITION');

    // Get user's ranking position (use findFirst for nullable moduleId)
    const ranking = await prisma.ranking.findFirst({
      where: {
        userId,
        moduleId: null,
      },
      select: { position: true },
    });

    if (!ranking) {
      return awarded;
    }

    for (const badge of badges) {
      // Position must be less than or equal to requirement (lower is better)
      if (ranking.position <= badge.requirementValue) {
        const awarded_ = await this.awardBadge(userId, badge.code);
        if (awarded_) awarded.push(awarded_);
      }
    }

    return awarded;
  }

  /**
   * Check time-based badges (Early Bird, Night Owl)
   */
  private static async checkTimeBasedBadges(userId: string): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];

    // Get recent attempts from today
    const now = new Date();
    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);

    const recentAttempts = await prisma.exerciseAttempt.findMany({
      where: {
        userId,
        status: 'CORRECT',
        createdAt: {
          gte: today,
          lt: tomorrow,
        },
      },
      select: { createdAt: true },
    });

    // Check for Early Bird (before 6 AM)
    const earlyHour = 6;
    for (const attempt of recentAttempts) {
      const hour = attempt.createdAt.getHours();
      if (hour < earlyHour) {
        const earlyBird = await this.awardBadge(userId, 'EARLY_BIRD');
        if (earlyBird) awarded.push(earlyBird);
        break;
      }
    }

    // Check for Night Owl (after midnight but before 4 AM - already counted in early bird)
    // Actually Night Owl is for exercises done late at night, say after 10 PM or before 4 AM
    const lateHour = 22; // 10 PM
    for (const attempt of recentAttempts) {
      const hour = attempt.createdAt.getHours();
      if (hour >= lateHour || hour < 4) {
        // For NIGHT_OWL_EXERCISES badge (5 exercises after midnight)
        const nightOwlExercises = await prisma.exerciseAttempt.count({
          where: {
            userId,
            status: 'CORRECT',
            createdAt: {
              gte: today,
              lt: tomorrow,
            },
          },
        });

        const attemptHour = attempt.createdAt.getHours();
        if (attemptHour >= 0 && attemptHour < 4) {
          if (nightOwlExercises >= 5) {
            const nightOwl = await this.awardBadge(userId, 'NIGHT_OWL_EXERCISES');
            if (nightOwl) awarded.push(nightOwl);
          }
          break;
        }
      }
    }

    return awarded;
  }

  /**
   * Check autodidact badge (exercises without hints)
   */
  private static async checkAutodidactBadges(userId: string): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];

    // Count exercises completed without hints
    const noHintExercises = await prisma.exerciseAttempt.groupBy({
      by: ['exerciseId'],
      where: {
        userId,
        status: 'CORRECT',
        hintsUsed: 0,
      },
    });

    // AUTODIDACT badge requires 25 exercises without hints
    const autodidactBadge = getBadgeByCode('AUTODIDACT');
    if (autodidactBadge && noHintExercises.length >= autodidactBadge.requirementValue) {
      const awarded_ = await this.awardBadge(userId, 'AUTODIDACT');
      if (awarded_) awarded.push(awarded_);
    }

    return awarded;
  }

  /**
   * Check explorer badge (all difficulty levels tried)
   */
  private static async checkExplorerBadge(userId: string): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];

    // Get unique difficulties attempted
    const attempts = await prisma.exerciseAttempt.findMany({
      where: {
        userId,
        status: 'CORRECT',
      },
      select: {
        exercises: {
          select: {
            difficulty: true,
          },
        },
      },
      distinct: ['exerciseId'],
    });

    const difficulties = new Set(attempts.map((a) => a.exercises.difficulty));

    // If all 4 difficulties have been tried
    if (difficulties.size >= 4) {
      const explorer = await this.awardBadge(userId, 'EXPLORER');
      if (explorer) awarded.push(explorer);
    }

    return awarded;
  }

  /**
   * Check persistent badge (completed after retries)
   */
  static async checkPersistentBadge(userId: string): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];

    // Find exercises completed on 3rd or later attempt
    const retries = await prisma.exerciseAttempt.findMany({
      where: {
        userId,
        status: 'CORRECT',
        attemptNumber: {
          gte: 3,
        },
      },
      take: 1,
    });

    if (retries.length > 0) {
      const persistent = await this.awardBadge(userId, 'PERSISTENT');
      if (persistent) awarded.push(persistent);
    }

    return awarded;
  }

  /**
   * Check first try warrior badge
   */
  static async checkFirstTryWarriorBadge(userId: string): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];

    // Count exercises completed on first try
    const firstTries = await prisma.exerciseAttempt.count({
      where: {
        userId,
        status: 'CORRECT',
        attemptNumber: 1,
      },
    });

    if (firstTries >= 50) {
      const warrior = await this.awardBadge(userId, 'FIRST_TRY_WARRIOR');
      if (warrior) awarded.push(warrior);
    }

    return awarded;
  }

  /**
   * Check speed demon badge
   */
  static async checkSpeedDemonBadge(userId: string): Promise<AwardedBadge[]> {
    const awarded: AwardedBadge[] = [];

    // Count exercises completed in under 60 seconds
    const fastExercises = await prisma.exerciseAttempt.count({
      where: {
        userId,
        status: 'CORRECT',
        timeSpentSeconds: {
          lt: 60,
        },
      },
    });

    if (fastExercises >= 10) {
      const speedDemon = await this.awardBadge(userId, 'SPEED_DEMON');
      if (speedDemon) awarded.push(speedDemon);
    }

    return awarded;
  }

  /**
   * Award a badge to a user (if not already awarded)
   */
  private static async awardBadge(
    userId: string,
    badgeCode: string
  ): Promise<AwardedBadge | null> {
    const badge = getBadgeByCode(badgeCode);
    if (!badge) {
      logger.warn({ badgeCode }, 'Badge not found');
      return null;
    }

    // Check if already unlocked
    const existing = await prisma.userAchievement.findUnique({
      where: {
        userId_achievementId: {
          userId,
          achievementId: badge.code,
        },
      },
    });

    if (existing && existing.unlockedAt) {
      return null; // Already unlocked
    }

    // Get or create achievement
    let achievement = await prisma.achievement.findUnique({
      where: { id: badge.code },
    });

    if (!achievement) {
      achievement = await prisma.achievement.create({
        data: {
          id: badge.code,
          code: badge.code,
          name: badge.name,
          description: badge.description,
          category: badge.category,
          rarity: badge.rarity,
          icon: badge.icon,
          requirementType: badge.requirementType,
          requirementValue: badge.requirementValue,
          points: badge.points,
          ...(badge.metadata ? { metadata: badge.metadata } : {}),
        },
      });
    }

    // Unlock achievement
    await prisma.userAchievement.upsert({
      where: {
        userId_achievementId: {
          userId,
          achievementId: badge.code,
        },
      },
      create: {
        userId,
        achievementId: badge.code,
        progress: badge.requirementValue,
        unlockedAt: new Date(),
      },
      update: {
        progress: badge.requirementValue,
        unlockedAt: existing?.unlockedAt || new Date(),
      },
    });

    logger.info({
      userId,
      badgeCode,
      badgeName: badge.name,
    }, 'Badge awarded');

    return {
      code: badge.code,
      name: badge.name,
      icon: badge.icon,
      rarity: badge.rarity,
      points: badge.points,
      message: badge.metadata?.unlockMessage || `¡Has desbloqueado "${badge.name}"!`,
    };
  }

  /**
   * Get all badges with user progress
   */
  static async getUserBadges(userId: string): Promise<UserBadgeProgress[]> {
    // Get all badge definitions
    const results: UserBadgeProgress[] = [];

    for (const badge of BADGE_DEFINITIONS) {
      // Get user achievement
      const userAchievement = await prisma.userAchievement.findUnique({
        where: {
          userId_achievementId: {
            userId,
            achievementId: badge.code,
          },
        },
      });

      // Calculate current progress
      const currentProgress = await this.calculateBadgeProgress(userId, badge);

      results.push({
        code: badge.code,
        name: badge.name,
        description: badge.description,
        category: badge.category,
        rarity: badge.rarity,
        icon: badge.icon,
        requirementValue: badge.requirementValue,
        currentProgress,
        unlocked: userAchievement?.unlockedAt !== null || false,
        unlockedAt: userAchievement?.unlockedAt || undefined,
      });
    }

    return results;
  }

  /**
   * Calculate current progress for a badge
   */
  private static async calculateBadgeProgress(
    userId: string,
    badge: typeof BADGE_DEFINITIONS[0]
  ): Promise<number> {
    switch (badge.requirementType) {
      case 'EXERCISES_COMPLETED':
        // General exercise count
        if (['PERSISTENT', 'FIRST_TRY_WARRIOR', 'EXPLORER', 'SPEED_DEMON', 'NIGHT_OWL_EXERCISES'].includes(badge.code)) {
          switch (badge.code) {
            case 'PERSISTENT':
              return await prisma.exerciseAttempt.count({
                where: { userId, status: 'CORRECT', attemptNumber: { gte: 3 } },
              });
            case 'FIRST_TRY_WARRIOR':
              return await prisma.exerciseAttempt.count({
                where: { userId, status: 'CORRECT', attemptNumber: 1 },
              });
            case 'SPEED_DEMON':
              return await prisma.exerciseAttempt.count({
                where: { userId, status: 'CORRECT', timeSpentSeconds: { lt: 60 } },
              });
            case 'NIGHT_OWL_EXERCISES':
              const nightAttempts = await prisma.exerciseAttempt.findMany({
                where: { userId, status: 'CORRECT' },
                select: { createdAt: true },
              });
              return nightAttempts.filter((a) => a.createdAt.getHours() < 4).length;
          }
        }
        // Standard exercise count
        const completed = await prisma.exerciseAttempt.groupBy({
          by: ['exerciseId'],
          where: { userId, status: 'CORRECT' },
        });
        return completed.length;

      case 'MODULES_COMPLETED':
        return await prisma.progress.count({
          where: { userId, isCompleted: true },
        });

      case 'PERFECT_SCORES':
        return await prisma.exerciseAttempt.count({
          where: { userId, isPerfect: true },
        });

      case 'STREAK_DAYS':
        const streakInfo = await ScoreCalculator.getUserStreak(userId);
        return streakInfo.currentStreak;

      case 'RANKING_POSITION':
        const ranking = await prisma.ranking.findFirst({
          where: { userId, moduleId: null },
        });
        // For ranking, we invert: position 1 = 100% progress
        if (!ranking) return 0;
        // For top 100 badge, if position is 50, progress is 100 (50 <= 100)
        if (badge.requirementValue === 100) return ranking.position <= 100 ? 100 : 0;
        if (badge.requirementValue === 50) return ranking.position <= 50 ? 50 : 0;
        if (badge.requirementValue === 10) return ranking.position <= 10 ? 10 : 0;
        if (badge.requirementValue === 3) return ranking.position <= 3 ? 3 : 0;
        if (badge.requirementValue === 1) return ranking.position === 1 ? 1 : 0;
        return ranking.position <= badge.requirementValue ? 1 : 0;

      case 'EXERCISES_WITHOUT_HINTS':
        const noHints = await prisma.exerciseAttempt.groupBy({
          by: ['exerciseId'],
          where: { userId, status: 'CORRECT', hintsUsed: 0 },
        });
        return noHints.length;

      case 'EARLY_BIRD':
        const earlyAttempts = await prisma.exerciseAttempt.findMany({
          where: { userId, status: 'CORRECT' },
          select: { createdAt: true },
        });
        for (const a of earlyAttempts) {
          if (a.createdAt.getHours() < 6) return 1;
        }
        return 0;

      case 'NIGHT_OWL':
        // Same as NIGHT_OWL_EXERCISES logic
        return 0; // Handled separately

      case 'PERFECT_MODULE':
        // Check if any module has 100% with all perfect
        const perfectModules = await prisma.progress.findMany({
          where: {
            userId,
            isCompleted: true,
            percentage: { gte: 100 },
          },
        });
        for (const p of perfectModules) {
          if (p.perfectExercises >= p.totalExercises && p.totalExercises > 0) {
            return 1;
          }
        }
        return 0;

      default:
        return 0;
    }
  }

  /**
   * Get unlocked badges for a user
   */
  static async getUnlockedBadges(userId: string): Promise<UserBadgeProgress[]> {
    const allBadges = await this.getUserBadges(userId);
    return allBadges.filter((b) => b.unlocked);
  }

  /**
   * Get badge progress for a specific badge
   */
  static async getBadgeProgress(userId: string, badgeCode: string): Promise<UserBadgeProgress | null> {
    const badge = getBadgeByCode(badgeCode);
    if (!badge) return null;

    const userAchievement = await prisma.userAchievement.findUnique({
      where: {
        userId_achievementId: {
          userId,
          achievementId: badge.code,
        },
      },
    });

    const currentProgress = await this.calculateBadgeProgress(userId, badge);

    return {
      code: badge.code,
      name: badge.name,
      description: badge.description,
      category: badge.category,
      rarity: badge.rarity,
      icon: badge.icon,
      requirementValue: badge.requirementValue,
      currentProgress,
      unlocked: userAchievement?.unlockedAt !== null || false,
      unlockedAt: userAchievement?.unlockedAt || undefined,
    };
  }

  /**
   * Initialize all badges in the database
   */
  static async initializeBadges(): Promise<void> {
    logger.info('Initializing badges in database');

    for (const badge of BADGE_DEFINITIONS) {
      await prisma.achievement.upsert({
        where: { id: badge.code },
        create: {
          id: badge.code,
          code: badge.code,
          name: badge.name,
          description: badge.description,
          category: badge.category,
          rarity: badge.rarity,
          icon: badge.icon,
          requirementType: badge.requirementType,
          requirementValue: badge.requirementValue,
          points: badge.points,
          ...(badge.metadata ? { metadata: badge.metadata } : {}),
        },
        update: {},
      });
    }

    logger.info({ count: BADGE_DEFINITIONS.length }, 'Badges initialized');
  }
}

export default BadgeAwarder;
