import { Injectable } from '@nestjs/common';
import { IContributionRecordRepository } from '../../../domain/repositories/contribution-record.repository.interface';
import { ContributionRecordAggregate } from '../../../domain/aggregates/contribution-record.aggregate';
import { ContributionSourceType } from '../../../domain/aggregates/contribution-account.aggregate';
import { ContributionAmount } from '../../../domain/value-objects/contribution-amount.vo';
import { DistributionRate } from '../../../domain/value-objects/distribution-rate.vo';
import { UnitOfWork, TransactionClient } from '../unit-of-work/unit-of-work';

@Injectable()
export class ContributionRecordRepository implements IContributionRecordRepository {
  constructor(private readonly unitOfWork: UnitOfWork) {}

  private get client(): TransactionClient {
    return this.unitOfWork.getClient();
  }

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

    if (!record) {
      return null;
    }

    return this.toDomain(record);
  }

  async findByAccountSequence(
    accountSequence: string,
    options?: {
      sourceType?: ContributionSourceType;
      includeExpired?: boolean;
      page?: number;
      limit?: number;
    },
  ): Promise<{ items: ContributionRecordAggregate[]; total: number }> {
    const where: any = { accountSequence };

    if (options?.sourceType) {
      where.sourceType = options.sourceType;
    }

    if (!options?.includeExpired) {
      where.isExpired = false;
    }

    const page = options?.page ?? 1;
    const limit = options?.limit ?? 50;

    // 根据 sourceType 动态排序：同伴下按 L 升序，同伴上按 T 升序，全部按 L/T 升序
    let orderBy: any;
    if (options?.sourceType === 'TEAM_LEVEL') {
      orderBy = [{ levelDepth: 'asc' }, { createdAt: 'desc' }];
    } else if (options?.sourceType === 'TEAM_BONUS') {
      orderBy = [{ bonusTier: 'asc' }, { createdAt: 'desc' }];
    } else {
      orderBy = [{ levelDepth: 'asc' }, { bonusTier: 'asc' }, { createdAt: 'desc' }];
    }

    const [records, total] = await Promise.all([
      this.client.contributionRecord.findMany({
        where,
        skip: (page - 1) * limit,
        take: limit,
        orderBy,
      }),
      this.client.contributionRecord.count({ where }),
    ]);

    return {
      items: records.map((r) => this.toDomain(r)),
      total,
    };
  }

  async findBySourceAdoptionId(sourceAdoptionId: bigint): Promise<ContributionRecordAggregate[]> {
    const records = await this.client.contributionRecord.findMany({
      where: { sourceAdoptionId },
      orderBy: { createdAt: 'asc' },
    });

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

  async existsBySourceAdoptionId(sourceAdoptionId: bigint): Promise<boolean> {
    const count = await this.client.contributionRecord.count({
      where: { sourceAdoptionId },
    });
    return count > 0;
  }

  async save(aggregate: ContributionRecordAggregate, tx?: any): Promise<ContributionRecordAggregate> {
    const client = tx ?? this.client;
    const data = aggregate.toPersistence();

    let result;
    if (aggregate.id) {
      result = await client.contributionRecord.update({
        where: { id: aggregate.id },
        data,
      });
    } else {
      result = await client.contributionRecord.create({
        data,
      });
    }

    return this.toDomain(result);
  }

  async saveMany(aggregates: ContributionRecordAggregate[], tx?: any): Promise<ContributionRecordAggregate[]> {
    if (aggregates.length === 0) return [];

    const client = tx ?? this.client;

    // 逐个创建以获取返回的 ID
    const results: ContributionRecordAggregate[] = [];
    for (const aggregate of aggregates) {
      const data = aggregate.toPersistence();
      const result = await client.contributionRecord.create({
        data,
      });
      results.push(this.toDomain(result));
    }

    return results;
  }

  async findExpiring(beforeDate: Date, limit?: number): Promise<ContributionRecordAggregate[]> {
    const records = await this.client.contributionRecord.findMany({
      where: {
        expireDate: { lt: beforeDate },
        isExpired: false,
      },
      take: limit ?? 1000,
      orderBy: { expireDate: 'asc' },
    });

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

  async findExpiredRecords(beforeDate: Date, limit: number): Promise<ContributionRecordAggregate[]> {
    return this.findExpiring(beforeDate, limit);
  }

  async markExpiredBatch(ids: bigint[], tx?: any): Promise<number> {
    const client = tx ?? this.client;
    const result = await client.contributionRecord.updateMany({
      where: { id: { in: ids } },
      data: { isExpired: true, expiredAt: new Date() },
    });
    return result.count;
  }

  async markAsExpired(ids: bigint[]): Promise<void> {
    await this.markExpiredBatch(ids);
  }

  async getEffectiveContributionSum(accountSequence: string): Promise<string> {
    const now = new Date();

    const result = await this.client.contributionRecord.aggregate({
      where: {
        accountSequence,
        isExpired: false,
        effectiveDate: { lte: now },
        expireDate: { gt: now },
      },
      _sum: { amount: true },
    });

    return (result._sum.amount || 0).toString();
  }

  async getActiveContributionByAccount(accountSequence: string): Promise<{
    personal: ContributionAmount;
    teamLevel: ContributionAmount;
    teamBonus: ContributionAmount;
    total: ContributionAmount;
  }> {
    const now = new Date();

    const result = await this.client.contributionRecord.groupBy({
      by: ['sourceType'],
      where: {
        accountSequence,
        isExpired: false,
        effectiveDate: { lte: now },
        expireDate: { gt: now },
      },
      _sum: { amount: true },
    });

    let personal = ContributionAmount.zero();
    let teamLevel = ContributionAmount.zero();
    let teamBonus = ContributionAmount.zero();

    for (const item of result) {
      const amount = new ContributionAmount(item._sum.amount || 0);
      switch (item.sourceType) {
        case 'PERSONAL':
          personal = amount;
          break;
        case 'TEAM_LEVEL':
          teamLevel = teamLevel.add(amount);
          break;
        case 'TEAM_BONUS':
          teamBonus = teamBonus.add(amount);
          break;
      }
    }

    return {
      personal,
      teamLevel,
      teamBonus,
      total: personal.add(teamLevel).add(teamBonus),
    };
  }

  async getContributionSummaryBySourceType(): Promise<Map<ContributionSourceType, ContributionAmount>> {
    const now = new Date();

    const result = await this.client.contributionRecord.groupBy({
      by: ['sourceType'],
      where: {
        isExpired: false,
        effectiveDate: { lte: now },
        expireDate: { gt: now },
      },
      _sum: { amount: true },
    });

    const summary = new Map<ContributionSourceType, ContributionAmount>();

    for (const item of result) {
      summary.set(
        item.sourceType as ContributionSourceType,
        new ContributionAmount(item._sum.amount || 0),
      );
    }

    return summary;
  }

  private toDomain(record: any): ContributionRecordAggregate {
    return ContributionRecordAggregate.fromPersistence({
      id: record.id,
      accountSequence: record.accountSequence,
      sourceType: record.sourceType,
      sourceAdoptionId: record.sourceAdoptionId,
      sourceAccountSequence: record.sourceAccountSequence,
      treeCount: record.treeCount,
      baseContribution: record.baseContribution,
      distributionRate: record.distributionRate,
      levelDepth: record.levelDepth,
      bonusTier: record.bonusTier,
      amount: record.amount,
      effectiveDate: record.effectiveDate,
      expireDate: record.expireDate,
      isExpired: record.isExpired,
      expiredAt: record.expiredAt,
      createdAt: record.createdAt,
    });
  }
}
