import { Injectable } from '@nestjs/common';
import { ContributionAmount } from '../../../domain/value-objects/contribution-amount.vo';
import { UnitOfWork, TransactionClient } from '../unit-of-work/unit-of-work';

export interface UnallocatedContribution {
  id: bigint;
  unallocType: string;
  wouldBeAccountSequence: string | null;
  levelDepth: number | null;
  amount: ContributionAmount;
  reason: string | null;
  sourceAdoptionId: bigint;
  sourceAccountSequence: string;
  effectiveDate: Date;
  expireDate: Date;
  allocatedToHeadquarters: boolean;
  allocatedAt: Date | null;
  createdAt: Date;
}

@Injectable()
export class UnallocatedContributionRepository {
  constructor(private readonly unitOfWork: UnitOfWork) {}

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

  async save(contribution: {
    type: string;
    wouldBeAccountSequence: string | null;
    levelDepth: number | null;
    amount: ContributionAmount;
    reason: string;
    sourceAdoptionId: bigint;
    sourceAccountSequence: string;
    effectiveDate: Date;
    expireDate: Date;
  }): Promise<void> {
    await this.client.unallocatedContribution.create({
      data: {
        unallocType: contribution.type,
        wouldBeAccountSequence: contribution.wouldBeAccountSequence,
        levelDepth: contribution.levelDepth,
        amount: contribution.amount.value,
        reason: contribution.reason,
        sourceAdoptionId: contribution.sourceAdoptionId,
        sourceAccountSequence: contribution.sourceAccountSequence,
        effectiveDate: contribution.effectiveDate,
        expireDate: contribution.expireDate,
      },
    });
  }

  async saveMany(contributions: {
    type: string;
    wouldBeAccountSequence: string | null;
    levelDepth: number | null;
    amount: ContributionAmount;
    reason: string;
    sourceAdoptionId: bigint;
    sourceAccountSequence: string;
    effectiveDate: Date;
    expireDate: Date;
  }[]): Promise<void> {
    if (contributions.length === 0) return;

    await this.client.unallocatedContribution.createMany({
      data: contributions.map((c) => ({
        unallocType: c.type,
        wouldBeAccountSequence: c.wouldBeAccountSequence,
        levelDepth: c.levelDepth,
        amount: c.amount.value,
        reason: c.reason,
        sourceAdoptionId: c.sourceAdoptionId,
        sourceAccountSequence: c.sourceAccountSequence,
        effectiveDate: c.effectiveDate,
        expireDate: c.expireDate,
      })),
    });
  }

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

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

  async getTotalUnallocated(): Promise<ContributionAmount> {
    const result = await this.client.unallocatedContribution.aggregate({
      _sum: { amount: true },
    });

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

  async getTotalUnallocatedByType(): Promise<Map<string, ContributionAmount>> {
    const result = await this.client.unallocatedContribution.groupBy({
      by: ['unallocType'],
      _sum: { amount: true },
    });

    const map = new Map<string, ContributionAmount>();
    for (const item of result) {
      map.set(item.unallocType, new ContributionAmount(item._sum.amount || 0));
    }

    return map;
  }

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

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

  private toDomain(record: any): UnallocatedContribution {
    return {
      id: record.id,
      unallocType: record.unallocType,
      wouldBeAccountSequence: record.wouldBeAccountSequence,
      levelDepth: record.levelDepth,
      amount: new ContributionAmount(record.amount),
      reason: record.reason,
      sourceAdoptionId: record.sourceAdoptionId,
      sourceAccountSequence: record.sourceAccountSequence,
      effectiveDate: record.effectiveDate,
      expireDate: record.expireDate,
      allocatedToHeadquarters: record.allocatedToHeadquarters,
      allocatedAt: record.allocatedAt,
      createdAt: record.createdAt,
    };
  }
}
