import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service';
import { IPendingRewardRepository } from '@/domain/repositories';
import { PendingReward, PendingRewardStatus } from '@/domain/aggregates';
import Decimal from 'decimal.js';

@Injectable()
export class PendingRewardRepositoryImpl implements IPendingRewardRepository {
  constructor(private readonly prisma: PrismaService) {}

  async save(reward: PendingReward): Promise<PendingReward> {
    const created = await this.prisma.pendingReward.create({
      data: {
        accountSequence: reward.accountSequence,
        userId: reward.userId.value,
        usdtAmount: reward.usdtAmount.toDecimal(),
        hashpowerAmount: reward.hashpowerAmount.decimal,
        sourceOrderId: reward.sourceOrderId,
        allocationType: reward.allocationType,
        expireAt: reward.expireAt,
        status: reward.status,
        settledAt: reward.settledAt,
        expiredAt: reward.expiredAt,
      },
    });
    return this.toDomain(created);
  }

  async saveAll(rewards: PendingReward[]): Promise<void> {
    await this.prisma.pendingReward.createMany({
      data: rewards.map(reward => ({
        accountSequence: reward.accountSequence,
        userId: reward.userId.value,
        usdtAmount: reward.usdtAmount.toDecimal(),
        hashpowerAmount: reward.hashpowerAmount.decimal,
        sourceOrderId: reward.sourceOrderId,
        allocationType: reward.allocationType,
        expireAt: reward.expireAt,
        status: reward.status,
        settledAt: reward.settledAt,
        expiredAt: reward.expiredAt,
      })),
    });
  }

  async update(reward: PendingReward): Promise<void> {
    await this.prisma.pendingReward.update({
      where: { id: reward.id },
      data: {
        status: reward.status,
        settledAt: reward.settledAt,
        expiredAt: reward.expiredAt,
      },
    });
  }

  async updateAll(rewards: PendingReward[]): Promise<void> {
    await this.prisma.$transaction(
      rewards.map(reward =>
        this.prisma.pendingReward.update({
          where: { id: reward.id },
          data: {
            status: reward.status,
            settledAt: reward.settledAt,
            expiredAt: reward.expiredAt,
          },
        }),
      ),
    );
  }

  async findById(id: bigint): Promise<PendingReward | null> {
    const record = await this.prisma.pendingReward.findUnique({
      where: { id },
    });
    return record ? this.toDomain(record) : null;
  }

  async findByAccountSequence(accountSequence: string, status?: PendingRewardStatus): Promise<PendingReward[]> {
    const records = await this.prisma.pendingReward.findMany({
      where: {
        accountSequence,
        ...(status && { status }),
      },
      orderBy: { createdAt: 'desc' },
    });
    return records.map(r => this.toDomain(r));
  }

  async findByUserId(userId: bigint, status?: PendingRewardStatus): Promise<PendingReward[]> {
    const records = await this.prisma.pendingReward.findMany({
      where: {
        userId,
        ...(status && { status }),
      },
      orderBy: { createdAt: 'desc' },
    });
    return records.map(r => this.toDomain(r));
  }

  async findBySourceOrderId(sourceOrderId: string): Promise<PendingReward[]> {
    const records = await this.prisma.pendingReward.findMany({
      where: { sourceOrderId },
      orderBy: { createdAt: 'desc' },
    });
    return records.map(r => this.toDomain(r));
  }

  async findExpiredPending(now: Date, limit = 100): Promise<PendingReward[]> {
    const records = await this.prisma.pendingReward.findMany({
      where: {
        status: PendingRewardStatus.PENDING,
        expireAt: { lt: now },
      },
      orderBy: { expireAt: 'asc' },
      take: limit,
    });
    return records.map(r => this.toDomain(r));
  }

  async sumPendingByAccountSequence(accountSequence: string): Promise<{ usdtTotal: number; hashpowerTotal: number }> {
    const result = await this.prisma.pendingReward.aggregate({
      where: {
        accountSequence,
        status: PendingRewardStatus.PENDING,
      },
      _sum: {
        usdtAmount: true,
        hashpowerAmount: true,
      },
    });
    return {
      usdtTotal: result._sum.usdtAmount?.toNumber() ?? 0,
      hashpowerTotal: result._sum.hashpowerAmount?.toNumber() ?? 0,
    };
  }

  private toDomain(record: {
    id: bigint;
    accountSequence: string;
    userId: bigint;
    usdtAmount: Decimal;
    hashpowerAmount: Decimal;
    sourceOrderId: string;
    allocationType: string;
    expireAt: Date;
    status: string;
    settledAt: Date | null;
    expiredAt: Date | null;
    createdAt: Date;
  }): PendingReward {
    return PendingReward.reconstruct({
      id: record.id,
      accountSequence: record.accountSequence,
      userId: record.userId,
      usdtAmount: new Decimal(record.usdtAmount.toString()),
      hashpowerAmount: new Decimal(record.hashpowerAmount.toString()),
      sourceOrderId: record.sourceOrderId,
      allocationType: record.allocationType,
      expireAt: record.expireAt,
      status: record.status,
      settledAt: record.settledAt,
      expiredAt: record.expiredAt,
      createdAt: record.createdAt,
    });
  }
}
