/**
 * Infrastructure Adapter: InMemoryAchievementRepository
 * 
 * In-memory implementation of IAchievementRepository
 */

import { ADMIN_ACHIEVEMENTS, COMMUNITY_ACHIEVEMENTS, DRIVER_ACHIEVEMENTS, STEWARD_ACHIEVEMENTS } from "@core/identity/domain/AchievementConstants";
import { Achievement, type AchievementCategory } from "@core/identity/domain/entities/Achievement";
import { UserAchievement } from "@core/identity/domain/entities/UserAchievement";
import { AchievementRepository } from "@core/identity/domain/repositories/AchievementRepository";
import type { Logger } from "@core/shared/domain/Logger";

export class InMemoryAchievementRepository implements AchievementRepository {
  private achievements: Map<string, Achievement> = new Map();
  private userAchievements: Map<string, UserAchievement> = new Map();
  private readonly logger: Logger;

  constructor(logger: Logger) {
    this.logger = logger;
    this.logger.info('InMemoryAchievementRepository initialized.');
    // Seed with predefined achievements
    this.seedAchievements();
  }

  private seedAchievements(): void {
    this.logger.debug('Seeding predefined achievements.');
    const allAchievements = [
      ...DRIVER_ACHIEVEMENTS,
      ...STEWARD_ACHIEVEMENTS,
      ...ADMIN_ACHIEVEMENTS,
      ...COMMUNITY_ACHIEVEMENTS,
    ];

    for (const props of allAchievements) {
      const achievement = Achievement.create(props);
      this.achievements.set(achievement.id, achievement);
      this.logger.debug(`Seeded achievement: ${achievement.id} (${achievement.name}).`);
    }
    this.logger.info(`Seeded ${allAchievements.length} predefined achievements.`);
  }

  // Achievement operations
  async findAchievementById(id: string): Promise<Achievement | null> {
    this.logger.debug(`Finding achievement by id: ${id}`);
    try {
      const achievement = this.achievements.get(id) ?? null;
      if (achievement) {
        this.logger.info(`Found achievement: ${id}.`);
      } else {
        this.logger.warn(`Achievement with id ${id} not found.`);
      }
      return achievement;
    } catch (error) {
      this.logger.error(`Error finding achievement by id ${id}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async findAllAchievements(): Promise<Achievement[]> {
    this.logger.debug('Finding all achievements.');
    try {
      const achievements = Array.from(this.achievements.values());
      this.logger.info(`Found ${achievements.length} achievements.`);
      return achievements;
    } catch (error) {
      this.logger.error('Error finding all achievements:', error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async findAchievementsByCategory(category: AchievementCategory): Promise<Achievement[]> {
    this.logger.debug(`Finding achievements by category: ${category}`);
    try {
      const achievements = Array.from(this.achievements.values())
        .filter(a => a.category === category);
      this.logger.info(`Found ${achievements.length} achievements for category: ${category}.`);
      return achievements;
    } catch (error) {
      this.logger.error(`Error finding achievements by category ${category}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async createAchievement(achievement: Achievement): Promise<Achievement> {
    this.logger.debug(`Creating achievement: ${achievement.id}`);
    try {
      if (this.achievements.has(achievement.id)) {
        this.logger.warn(`Achievement with ID ${achievement.id} already exists.`);
        throw new Error('Achievement with this ID already exists');
      }
      this.achievements.set(achievement.id, achievement);
      this.logger.info(`Achievement ${achievement.id} created successfully.`);
      return achievement;
    } catch (error) {
      this.logger.error(`Error creating achievement ${achievement.id}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  // UserAchievement operations
  async findUserAchievementById(id: string): Promise<UserAchievement | null> {
    this.logger.debug(`Finding user achievement by id: ${id}`);
    try {
      const userAchievement = this.userAchievements.get(id) ?? null;
      if (userAchievement) {
        this.logger.info(`Found user achievement: ${id}.`);
      } else {
        this.logger.warn(`User achievement with id ${id} not found.`);
      }
      return userAchievement;
    } catch (error) {
      this.logger.error(`Error finding user achievement by id ${id}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async findUserAchievementsByUserId(userId: string): Promise<UserAchievement[]> {
    this.logger.debug(`Finding user achievements by user id: ${userId}`);
    try {
      const userAchievements = Array.from(this.userAchievements.values())
        .filter(ua => ua.userId === userId);
      this.logger.info(`Found ${userAchievements.length} user achievements for user id: ${userId}.`);
      return userAchievements;
    } catch (error) {
      this.logger.error(`Error finding user achievements by user id ${userId}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async findUserAchievementByUserAndAchievement(
    userId: string,
    achievementId: string
  ): Promise<UserAchievement | null> {
    this.logger.debug(`Finding user achievement for user: ${userId}, achievement: ${achievementId}`);
    try {
      for (const ua of this.userAchievements.values()) {
        if (ua.userId === userId && ua.achievementId === achievementId) {
          this.logger.info(`Found user achievement for user: ${userId}, achievement: ${achievementId}.`);
          return ua;
        }
      }
      this.logger.warn(`User achievement for user ${userId}, achievement ${achievementId} not found.`);
      return null;
    } catch (error) {
      this.logger.error(`Error finding user achievement for user ${userId}, achievement ${achievementId}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async hasUserEarnedAchievement(userId: string, achievementId: string): Promise<boolean> {
    this.logger.debug(`Checking if user ${userId} earned achievement ${achievementId}`);
    try {
      const ua = await this.findUserAchievementByUserAndAchievement(userId, achievementId);
      const hasEarned = ua !== null && ua.isComplete();
      this.logger.debug(`User ${userId} earned achievement ${achievementId}: ${hasEarned}.`);
      return hasEarned;
    } catch (error) {
      this.logger.error(`Error checking if user ${userId} earned achievement ${achievementId}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async createUserAchievement(userAchievement: UserAchievement): Promise<UserAchievement> {
    this.logger.debug(`Creating user achievement: ${userAchievement.id}`);
    try {
      if (this.userAchievements.has(userAchievement.id)) {
        this.logger.warn(`UserAchievement with ID ${userAchievement.id} already exists.`);
        throw new Error('UserAchievement with this ID already exists');
      }
      this.userAchievements.set(userAchievement.id, userAchievement);
      this.logger.info(`UserAchievement ${userAchievement.id} created successfully.`);
      return userAchievement;
    } catch (error) {
      this.logger.error(`Error creating user achievement ${userAchievement.id}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async updateUserAchievement(userAchievement: UserAchievement): Promise<UserAchievement> {
    this.logger.debug(`Updating user achievement: ${userAchievement.id}`);
    try {
      if (!this.userAchievements.has(userAchievement.id)) {
        this.logger.warn(`UserAchievement with ID ${userAchievement.id} not found for update.`);
        throw new Error('UserAchievement not found');
      }
      this.userAchievements.set(userAchievement.id, userAchievement);
      this.logger.info(`UserAchievement ${userAchievement.id} updated successfully.`);
      return userAchievement;
    } catch (error) {
      this.logger.error(`Error updating user achievement ${userAchievement.id}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  // Stats
  async getAchievementLeaderboard(limit: number): Promise<{ userId: string; points: number; count: number }[]> {
    this.logger.debug(`Getting achievement leaderboard with limit: ${limit}`);
    try {
      const userStats = new Map<string, { points: number; count: number }>();

      for (const ua of this.userAchievements.values()) {
        if (!ua.isComplete()) continue;

        const achievement = this.achievements.get(ua.achievementId);
        if (!achievement) {
          this.logger.warn(`Achievement ${ua.achievementId} not found while building leaderboard.`);
          continue;
        }

        const existing = userStats.get(ua.userId) ?? { points: 0, count: 0 };
        userStats.set(ua.userId, {
          points: existing.points + achievement.points,
          count: existing.count + 1,
        });
      }

      const leaderboard = Array.from(userStats.entries())
        .map(([userId, stats]) => ({ userId, ...stats }))
        .sort((a, b) => b.points - a.points)
        .slice(0, limit);
      this.logger.info(`Generated achievement leaderboard with ${leaderboard.length} entries.`);
      return leaderboard;
    } catch (error) {
      this.logger.error(`Error getting achievement leaderboard:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async getUserAchievementStats(userId: string): Promise<{
    total: number;
    points: number;
    byCategory: Record<AchievementCategory, number>
  }> {
    this.logger.debug(`Getting achievement stats for user: ${userId}`);
    try {
      const userAchievements = await this.findUserAchievementsByUserId(userId);
      const completedAchievements = userAchievements.filter(ua => ua.isComplete());
      this.logger.debug(`Found ${completedAchievements.length} completed achievements for user ${userId}.`);

      const byCategory: Record<AchievementCategory, number> = {
        driver: 0,
        steward: 0,
        admin: 0,
        community: 0,
      };

      let points = 0;

      for (const ua of completedAchievements) {
        const achievement = this.achievements.get(ua.achievementId);
        if (achievement) {
          points += achievement.points;
          byCategory[achievement.category]++;
        } else {
          this.logger.warn(`Achievement ${ua.achievementId} not found while calculating user stats for user ${userId}.`);
        }
      }

      const stats = {
        total: completedAchievements.length,
        points,
        byCategory,
      };
      this.logger.info(`Generated achievement stats for user ${userId}:`, stats);
      return stats;
    } catch (error) {
      this.logger.error(`Error getting user achievement stats for user ${userId}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  // Test helpers
  clearUserAchievements(): void {
    this.logger.debug('Clearing all user achievements.');
    this.userAchievements.clear();
    this.logger.info('All user achievements cleared.');
  }

  clear(): void {
    this.logger.debug('Clearing all achievement data.');
    this.achievements.clear();
    this.userAchievements.clear();
    this.logger.info('All achievement data cleared.');
  }
}
