import { Injectable } from '@nestjs/common';
import { PrismaService } from '../database/prisma.service';
import { ILeaderboardRankingRepository } from '../../domain/repositories/leaderboard-ranking.repository.interface';
import { LeaderboardRanking } from '../../domain/aggregates/leaderboard-ranking/leaderboard-ranking.aggregate';
import { LeaderboardType } from '../../domain/value-objects/leaderboard-type.enum';
import { LeaderboardPeriod } from '../../domain/value-objects/leaderboard-period.vo';

@Injectable()
export class LeaderboardRankingRepositoryImpl implements ILeaderboardRankingRepository {
  constructor(private readonly prisma: PrismaService) {}

  async save(ranking: LeaderboardRanking): Promise<void> {
    const data = {
      leaderboardType: ranking.leaderboardType,
      periodKey: ranking.periodKey,
      userId: ranking.userId,
      isVirtual: ranking.isVirtual,
      rankPosition: ranking.rankPosition.value,
      displayPosition: ranking.displayPosition.value,
      previousRank: ranking.previousRank?.value || null,
      totalTeamPlanting: ranking.score.totalTeamPlanting,
      maxDirectTeamPlanting: ranking.score.maxDirectTeamPlanting,
      effectiveScore: ranking.score.effectiveScore,
      userSnapshot: ranking.userSnapshot.toJson(),
      periodStartAt: ranking.period.startAt,
      periodEndAt: ranking.period.endAt,
      calculatedAt: ranking.calculatedAt,
    };

    if (ranking.id) {
      await this.prisma.leaderboardRanking.update({
        where: { id: ranking.id },
        data,
      });
    } else {
      const result = await this.prisma.leaderboardRanking.upsert({
        where: {
          uk_type_period_user: {
            leaderboardType: ranking.leaderboardType,
            periodKey: ranking.periodKey,
            userId: ranking.userId,
          },
        },
        update: data,
        create: data,
      });
      ranking.setId(result.id);
    }
  }

  async saveAll(rankings: LeaderboardRanking[]): Promise<void> {
    // 使用事务批量保存
    await this.prisma.$transaction(
      rankings.map((ranking) =>
        this.prisma.leaderboardRanking.upsert({
          where: {
            uk_type_period_user: {
              leaderboardType: ranking.leaderboardType,
              periodKey: ranking.periodKey,
              userId: ranking.userId,
            },
          },
          update: {
            rankPosition: ranking.rankPosition.value,
            displayPosition: ranking.displayPosition.value,
            previousRank: ranking.previousRank?.value || null,
            totalTeamPlanting: ranking.score.totalTeamPlanting,
            maxDirectTeamPlanting: ranking.score.maxDirectTeamPlanting,
            effectiveScore: ranking.score.effectiveScore,
            userSnapshot: ranking.userSnapshot.toJson(),
            calculatedAt: ranking.calculatedAt,
          },
          create: {
            leaderboardType: ranking.leaderboardType,
            periodKey: ranking.periodKey,
            userId: ranking.userId,
            isVirtual: ranking.isVirtual,
            rankPosition: ranking.rankPosition.value,
            displayPosition: ranking.displayPosition.value,
            previousRank: ranking.previousRank?.value || null,
            totalTeamPlanting: ranking.score.totalTeamPlanting,
            maxDirectTeamPlanting: ranking.score.maxDirectTeamPlanting,
            effectiveScore: ranking.score.effectiveScore,
            userSnapshot: ranking.userSnapshot.toJson(),
            periodStartAt: ranking.period.startAt,
            periodEndAt: ranking.period.endAt,
            calculatedAt: ranking.calculatedAt,
          },
        })
      )
    );
  }

  async findById(id: bigint): Promise<LeaderboardRanking | null> {
    const record = await this.prisma.leaderboardRanking.findUnique({
      where: { id },
    });

    if (!record) return null;
    return this.toDomain(record);
  }

  async findByTypeAndPeriod(
    type: LeaderboardType,
    periodKey: string,
    options?: {
      limit?: number;
      includeVirtual?: boolean;
    },
  ): Promise<LeaderboardRanking[]> {
    const records = await this.prisma.leaderboardRanking.findMany({
      where: {
        leaderboardType: type,
        periodKey,
        ...(options?.includeVirtual === false ? { isVirtual: false } : {}),
      },
      orderBy: { displayPosition: 'asc' },
      take: options?.limit,
    });

    return records.map((r) => this.toDomain(r));
  }

  async findUserRanking(
    type: LeaderboardType,
    periodKey: string,
    userId: bigint,
  ): Promise<LeaderboardRanking | null> {
    const record = await this.prisma.leaderboardRanking.findUnique({
      where: {
        uk_type_period_user: {
          leaderboardType: type,
          periodKey,
          userId,
        },
      },
    });

    if (!record) return null;
    return this.toDomain(record);
  }

  async findUserPreviousRanking(
    type: LeaderboardType,
    userId: bigint,
  ): Promise<LeaderboardRanking | null> {
    const currentPeriod = LeaderboardPeriod.current(type);
    const previousPeriod = currentPeriod.getPreviousPeriod();

    return this.findUserRanking(type, previousPeriod.key, userId);
  }

  async deleteByTypeAndPeriod(
    type: LeaderboardType,
    periodKey: string,
  ): Promise<void> {
    await this.prisma.leaderboardRanking.deleteMany({
      where: {
        leaderboardType: type,
        periodKey,
      },
    });
  }

  async countByTypeAndPeriod(
    type: LeaderboardType,
    periodKey: string,
  ): Promise<number> {
    return this.prisma.leaderboardRanking.count({
      where: {
        leaderboardType: type,
        periodKey,
        isVirtual: false,
      },
    });
  }

  async getTopScore(
    type: LeaderboardType,
    periodKey: string,
  ): Promise<number> {
    const result = await this.prisma.leaderboardRanking.findFirst({
      where: {
        leaderboardType: type,
        periodKey,
        isVirtual: false,
      },
      orderBy: { effectiveScore: 'desc' },
      select: { effectiveScore: true },
    });

    return result?.effectiveScore || 0;
  }

  private toDomain(record: any): LeaderboardRanking {
    return LeaderboardRanking.reconstitute({
      id: record.id,
      leaderboardType: record.leaderboardType as LeaderboardType,
      periodKey: record.periodKey,
      periodStartAt: record.periodStartAt,
      periodEndAt: record.periodEndAt,
      userId: record.userId,
      isVirtual: record.isVirtual,
      rankPosition: record.rankPosition,
      displayPosition: record.displayPosition,
      previousRank: record.previousRank,
      totalTeamPlanting: record.totalTeamPlanting,
      maxDirectTeamPlanting: record.maxDirectTeamPlanting,
      effectiveScore: record.effectiveScore,
      userSnapshot: record.userSnapshot as Record<string, any>,
      calculatedAt: record.calculatedAt,
    });
  }
}
