import type { DataSource } from 'typeorm';

import type { AchievementCategory } from '@core/identity/domain/entities/Achievement';
import { Achievement } from '@core/identity/domain/entities/Achievement';
import { UserAchievement } from '@core/identity/domain/entities/UserAchievement';
import type { AchievementRepository } from '@core/identity/domain/repositories/AchievementRepository';

import { AchievementOrmEntity } from '../entities/AchievementOrmEntity';
import { UserAchievementOrmEntity } from '../entities/UserAchievementOrmEntity';
import { AchievementOrmMapper } from '../mappers/AchievementOrmMapper';

export class TypeOrmAchievementRepository implements AchievementRepository {
  constructor(
    private readonly dataSource: DataSource,
    private readonly mapper: AchievementOrmMapper,
  ) {}

  // Achievement operations
  async findAchievementById(id: string): Promise<Achievement | null> {
    const repo = this.dataSource.getRepository(AchievementOrmEntity);
    const entity = await repo.findOne({ where: { id } });
    return entity ? this.mapper.toDomain(entity) : null;
  }

  async findAllAchievements(): Promise<Achievement[]> {
    const repo = this.dataSource.getRepository(AchievementOrmEntity);
    const entities = await repo.find();
    return entities.map((e) => this.mapper.toDomain(e));
  }

  async findAchievementsByCategory(category: AchievementCategory): Promise<Achievement[]> {
    const repo = this.dataSource.getRepository(AchievementOrmEntity);
    const entities = await repo.find({ where: { category } });
    return entities.map((e) => this.mapper.toDomain(e));
  }

  async createAchievement(achievement: Achievement): Promise<Achievement> {
    const repo = this.dataSource.getRepository(AchievementOrmEntity);
    const entity = this.mapper.toOrmEntity(achievement);
    await repo.save(entity);
    return achievement;
  }

  // UserAchievement operations
  async findUserAchievementById(id: string): Promise<UserAchievement | null> {
    const repo = this.dataSource.getRepository(UserAchievementOrmEntity);
    const entity = await repo.findOne({ where: { id } });
    return entity ? this.mapper.toUserAchievementDomain(entity) : null;
  }

  async findUserAchievementsByUserId(userId: string): Promise<UserAchievement[]> {
    const repo = this.dataSource.getRepository(UserAchievementOrmEntity);
    const entities = await repo.find({ where: { userId } });
    return entities.map((e) => this.mapper.toUserAchievementDomain(e));
  }

  async findUserAchievementByUserAndAchievement(userId: string, achievementId: string): Promise<UserAchievement | null> {
    const repo = this.dataSource.getRepository(UserAchievementOrmEntity);
    const entity = await repo.findOne({ where: { userId, achievementId } });
    return entity ? this.mapper.toUserAchievementDomain(entity) : null;
  }

  async hasUserEarnedAchievement(userId: string, achievementId: string): Promise<boolean> {
    const ua = await this.findUserAchievementByUserAndAchievement(userId, achievementId);
    return ua !== null && ua.isComplete();
  }

  async createUserAchievement(userAchievement: UserAchievement): Promise<UserAchievement> {
    const repo = this.dataSource.getRepository(UserAchievementOrmEntity);
    const entity = this.mapper.toUserAchievementOrmEntity(userAchievement);
    await repo.save(entity);
    return userAchievement;
  }

  async updateUserAchievement(userAchievement: UserAchievement): Promise<UserAchievement> {
    const repo = this.dataSource.getRepository(UserAchievementOrmEntity);
    const entity = this.mapper.toUserAchievementOrmEntity(userAchievement);
    await repo.save(entity);
    return userAchievement;
  }

  // Stats
  async getAchievementLeaderboard(limit: number): Promise<{ userId: string; points: number; count: number }[]> {
    const userAchievementRepo = this.dataSource.getRepository(UserAchievementOrmEntity);
    const achievementRepo = this.dataSource.getRepository(AchievementOrmEntity);

    // Get all completed user achievements
    const userAchievements = await userAchievementRepo.find({
      where: { progress: 100 },
    });

    // Build stats map
    const userStats = new Map<string, { points: number; count: number }>();

    for (const ua of userAchievements) {
      const achievement = await achievementRepo.findOne({ where: { id: ua.achievementId } });
      if (!achievement) continue;

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

    // Sort and return top N
    return Array.from(userStats.entries())
      .map(([userId, stats]) => ({ userId, ...stats }))
      .sort((a, b) => b.points - a.points || b.count - a.count)
      .slice(0, limit);
  }

  async getUserAchievementStats(userId: string): Promise<{
    total: number;
    points: number;
    byCategory: Record<AchievementCategory, number>;
  }> {
    const userAchievementRepo = this.dataSource.getRepository(UserAchievementOrmEntity);
    const achievementRepo = this.dataSource.getRepository(AchievementOrmEntity);

    const userAchievements = await userAchievementRepo.find({
      where: { userId, progress: 100 },
    });

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

    let points = 0;

    for (const ua of userAchievements) {
      const achievement = await achievementRepo.findOne({ where: { id: ua.achievementId } });
      if (achievement) {
        points += achievement.points;
        if (achievement.category in byCategory) {
          byCategory[achievement.category as AchievementCategory]++;
        }
      }
    }

    return {
      total: userAchievements.length,
      points,
      byCategory,
    };
  }
}