import { Injectable } from '@nestjs/common';
import { Decimal } from 'decimal.js';
import { IContributionAccountRepository } from '../../../domain/repositories/contribution-account.repository.interface';
import { ContributionAccountAggregate, ContributionSourceType } from '../../../domain/aggregates/contribution-account.aggregate';
import { ContributionAmount } from '../../../domain/value-objects/contribution-amount.vo';
import { UnitOfWork, TransactionClient } from '../unit-of-work/unit-of-work';

@Injectable()
export class ContributionAccountRepository implements IContributionAccountRepository {
  constructor(private readonly unitOfWork: UnitOfWork) {}

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

  async findByAccountSequence(accountSequence: string): Promise<ContributionAccountAggregate | null> {
    const record = await this.client.contributionAccount.findUnique({
      where: { accountSequence },
    });

    if (!record) {
      return null;
    }

    return this.toDomain(record);
  }

  async findByAccountSequenceForUpdate(
    accountSequence: string,
    tx?: any,
  ): Promise<ContributionAccountAggregate | null> {
    const client = tx ?? this.client;
    // In Prisma, we use a raw query with FOR UPDATE or rely on transaction isolation
    // For now, we'll use a regular findUnique within the transaction context
    const record = await client.contributionAccount.findUnique({
      where: { accountSequence },
    });

    if (!record) {
      return null;
    }

    return this.toDomain(record);
  }

  async findByAccountSequences(accountSequences: string[]): Promise<Map<string, ContributionAccountAggregate>> {
    const records = await this.client.contributionAccount.findMany({
      where: { accountSequence: { in: accountSequences } },
    });

    const result = new Map<string, ContributionAccountAggregate>();
    for (const record of records) {
      result.set(record.accountSequence, this.toDomain(record));
    }

    return result;
  }

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

    const result = await client.contributionAccount.upsert({
      where: { accountSequence: aggregate.accountSequence },
      create: data,
      update: data,
    });

    return this.toDomain(result);
  }

  async saveMany(aggregates: ContributionAccountAggregate[], tx?: any): Promise<void> {
    for (const aggregate of aggregates) {
      await this.save(aggregate, tx);
    }
  }

  async updateContribution(
    accountSequence: string,
    sourceType: ContributionSourceType,
    amount: ContributionAmount,
  ): Promise<void> {
    const fieldMap: Record<ContributionSourceType, string> = {
      [ContributionSourceType.PERSONAL]: 'personalContribution',
      [ContributionSourceType.TEAM_LEVEL]: 'teamLevelContribution',
      [ContributionSourceType.TEAM_BONUS]: 'teamBonusContribution',
    };

    const field = fieldMap[sourceType];

    await this.client.contributionAccount.update({
      where: { accountSequence },
      data: {
        [field]: { increment: amount.value },
        totalContribution: { increment: amount.value },
        updatedAt: new Date(),
      },
    });
  }

  async findAllWithPagination(page: number, pageSize: number): Promise<{
    data: ContributionAccountAggregate[];
    total: number;
  }> {
    const [records, total] = await Promise.all([
      this.client.contributionAccount.findMany({
        skip: (page - 1) * pageSize,
        take: pageSize,
        orderBy: { totalContribution: 'desc' },
      }),
      this.client.contributionAccount.count(),
    ]);

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

  async findMany(options: {
    page?: number;
    limit?: number;
    orderBy?: 'totalContribution' | 'effectiveContribution';
    order?: 'asc' | 'desc';
  }): Promise<{
    items: ContributionAccountAggregate[];
    total: number;
  }> {
    const page = options.page ?? 1;
    const limit = options.limit ?? 50;
    const orderBy = options.orderBy ?? 'totalContribution';
    const order = options.order ?? 'desc';

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

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

  async findAllWithEffectiveContribution(): Promise<ContributionAccountAggregate[]> {
    const records = await this.client.contributionAccount.findMany({
      where: { effectiveContribution: { gt: 0 } },
      orderBy: { effectiveContribution: 'desc' },
    });

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

  async getNetworkTotalEffectiveContribution(): Promise<string> {
    const result = await this.client.contributionAccount.aggregate({
      _sum: { effectiveContribution: true },
    });

    return (result._sum.effectiveContribution || new Decimal(0)).toString();
  }

  async findTopContributors(limit: number): Promise<ContributionAccountAggregate[]> {
    const records = await this.client.contributionAccount.findMany({
      where: { totalContribution: { gt: 0 } },
      orderBy: { totalContribution: 'desc' },
      take: limit,
    });

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

  async getTotalContribution(): Promise<ContributionAmount> {
    const result = await this.client.contributionAccount.aggregate({
      _sum: { totalContribution: true },
    });

    return new ContributionAmount(result._sum.totalContribution || 0);
  }

  async countAccounts(): Promise<number> {
    return this.client.contributionAccount.count();
  }

  async countAccountsWithContribution(): Promise<number> {
    return this.client.contributionAccount.count({
      where: { totalContribution: { gt: 0 } },
    });
  }

  private toDomain(record: any): ContributionAccountAggregate {
    return ContributionAccountAggregate.fromPersistence({
      id: record.id,
      accountSequence: record.accountSequence,
      personalContribution: record.personalContribution,
      teamLevelContribution: record.teamLevelContribution,
      teamBonusContribution: record.teamBonusContribution,
      totalContribution: record.totalContribution,
      effectiveContribution: record.effectiveContribution,
      hasAdopted: record.hasAdopted,
      directReferralAdoptedCount: record.directReferralAdoptedCount,
      unlockedLevelDepth: record.unlockedLevelDepth,
      unlockedBonusTiers: record.unlockedBonusTiers,
      version: record.version,
      createdAt: record.createdAt,
      updatedAt: record.updatedAt,
    });
  }

  private toPersistence(aggregate: ContributionAccountAggregate): any {
    return aggregate.toPersistence();
  }
}
