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 type SystemAccountType = 'OPERATION' | 'PROVINCE' | 'CITY' | 'HEADQUARTERS';

export interface SystemAccount {
  id: bigint;
  accountType: SystemAccountType;
  name: string;
  contributionBalance: ContributionAmount;
  contributionNeverExpires: boolean;
  version: number;
  createdAt: Date;
  updatedAt: Date;
}

export interface SystemContributionRecord {
  id: bigint;
  systemAccountId: bigint;
  sourceAdoptionId: bigint;
  sourceAccountSequence: string;
  distributionRate: number;
  amount: ContributionAmount;
  effectiveDate: Date;
  expireDate: Date | null;
  isExpired: boolean;
  createdAt: Date;
}

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

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

  async findByType(accountType: SystemAccountType): Promise<SystemAccount | null> {
    const record = await this.client.systemAccount.findUnique({
      where: { accountType },
    });

    if (!record) {
      return null;
    }

    return this.toSystemAccount(record);
  }

  async findAll(): Promise<SystemAccount[]> {
    const records = await this.client.systemAccount.findMany({
      orderBy: { accountType: 'asc' },
    });

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

  async ensureSystemAccountsExist(): Promise<void> {
    const accounts: { accountType: SystemAccountType; name: string }[] = [
      { accountType: 'OPERATION', name: '运营账户' },
      { accountType: 'PROVINCE', name: '省公司账户' },
      { accountType: 'CITY', name: '市公司账户' },
      { accountType: 'HEADQUARTERS', name: '总部账户' },
    ];

    for (const account of accounts) {
      await this.client.systemAccount.upsert({
        where: { accountType: account.accountType },
        create: {
          accountType: account.accountType,
          name: account.name,
          contributionBalance: 0,
        },
        update: {},
      });
    }
  }

  async addContribution(
    accountType: SystemAccountType,
    amount: ContributionAmount,
  ): Promise<void> {
    await this.client.systemAccount.update({
      where: { accountType },
      data: {
        contributionBalance: { increment: amount.value },
      },
    });
  }

  async saveContributionRecord(record: {
    systemAccountType: SystemAccountType;
    sourceAdoptionId: bigint;
    sourceAccountSequence: string;
    distributionRate: number;
    amount: ContributionAmount;
    effectiveDate: Date;
    expireDate?: Date | null;
  }): Promise<void> {
    const systemAccount = await this.findByType(record.systemAccountType);
    if (!systemAccount) {
      throw new Error(`System account ${record.systemAccountType} not found`);
    }

    await this.client.systemContributionRecord.create({
      data: {
        systemAccountId: systemAccount.id,
        sourceAdoptionId: record.sourceAdoptionId,
        sourceAccountSequence: record.sourceAccountSequence,
        distributionRate: record.distributionRate,
        amount: record.amount.value,
        effectiveDate: record.effectiveDate,
        expireDate: record.expireDate ?? null,
      },
    });
  }

  async saveContributionRecords(records: {
    systemAccountType: SystemAccountType;
    sourceAdoptionId: bigint;
    sourceAccountSequence: string;
    distributionRate: number;
    amount: ContributionAmount;
    effectiveDate: Date;
    expireDate?: Date | null;
  }[]): Promise<void> {
    if (records.length === 0) return;

    const systemAccounts = await this.findAll();
    const accountMap = new Map<SystemAccountType, bigint>();
    for (const account of systemAccounts) {
      accountMap.set(account.accountType, account.id);
    }

    await this.client.systemContributionRecord.createMany({
      data: records.map((r) => ({
        systemAccountId: accountMap.get(r.systemAccountType)!,
        sourceAdoptionId: r.sourceAdoptionId,
        sourceAccountSequence: r.sourceAccountSequence,
        distributionRate: r.distributionRate,
        amount: r.amount.value,
        effectiveDate: r.effectiveDate,
        expireDate: r.expireDate ?? null,
      })),
    });
  }

  async findContributionRecords(
    systemAccountType: SystemAccountType,
    page: number,
    pageSize: number,
  ): Promise<{ data: SystemContributionRecord[]; total: number }> {
    const systemAccount = await this.findByType(systemAccountType);
    if (!systemAccount) {
      return { data: [], total: 0 };
    }

    const [records, total] = await Promise.all([
      this.client.systemContributionRecord.findMany({
        where: { systemAccountId: systemAccount.id },
        skip: (page - 1) * pageSize,
        take: pageSize,
        orderBy: { createdAt: 'desc' },
      }),
      this.client.systemContributionRecord.count({
        where: { systemAccountId: systemAccount.id },
      }),
    ]);

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

  private toSystemAccount(record: any): SystemAccount {
    return {
      id: record.id,
      accountType: record.accountType as SystemAccountType,
      name: record.name,
      contributionBalance: new ContributionAmount(record.contributionBalance),
      contributionNeverExpires: record.contributionNeverExpires,
      version: record.version,
      createdAt: record.createdAt,
      updatedAt: record.updatedAt,
    };
  }

  private toContributionRecord(record: any): SystemContributionRecord {
    return {
      id: record.id,
      systemAccountId: record.systemAccountId,
      sourceAdoptionId: record.sourceAdoptionId,
      sourceAccountSequence: record.sourceAccountSequence,
      distributionRate: record.distributionRate,
      amount: new ContributionAmount(record.amount),
      effectiveDate: record.effectiveDate,
      expireDate: record.expireDate,
      isExpired: record.isExpired,
      createdAt: record.createdAt,
    };
  }
}
