import { Injectable } from '@nestjs/common'
import { PrismaService } from '../prisma/prisma.service'
import {
  ISystemAccountRepository,
  SYSTEM_ACCOUNT_REPOSITORY,
} from '@/domain/repositories'
import { SystemAccount, SystemAccountProps, SystemAccountLedgerEntryProps } from '@/domain/aggregates'
import {
  SystemAccountType,
  SystemLedgerEntryType,
  SystemAccountStatus,
} from '@/domain/enums'
import Decimal from 'decimal.js'

@Injectable()
export class SystemAccountRepositoryImpl implements ISystemAccountRepository {
  constructor(private readonly prisma: PrismaService) {}

  async save(account: SystemAccount): Promise<SystemAccount> {
    const data = account.toPersistence()

    const record = await this.prisma.systemAccount.upsert({
      where: {
        uk_account_region: {
          accountType: data.accountType,
          regionCode: data.regionCode,
        },
      },
      create: {
        accountType: data.accountType,
        regionCode: data.regionCode,
        regionName: data.regionName,
        walletAddress: data.walletAddress,
        mpcPublicKey: data.mpcPublicKey,
        usdtBalance: data.usdtBalance,
        hashpower: data.hashpower,
        totalReceived: data.totalReceived,
        totalTransferred: data.totalTransferred,
        status: data.status,
      },
      update: {
        regionName: data.regionName,
        walletAddress: data.walletAddress,
        mpcPublicKey: data.mpcPublicKey,
        usdtBalance: data.usdtBalance,
        hashpower: data.hashpower,
        totalReceived: data.totalReceived,
        totalTransferred: data.totalTransferred,
        status: data.status,
      },
    })

    return this.toDomain(record)
  }

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

  async findByType(accountType: SystemAccountType): Promise<SystemAccount | null> {
    const record = await this.prisma.systemAccount.findFirst({
      where: {
        accountType,
        regionCode: null,
      },
    })
    return record ? this.toDomain(record) : null
  }

  async findByTypeAndRegion(
    accountType: SystemAccountType,
    regionCode: string,
  ): Promise<SystemAccount | null> {
    const record = await this.prisma.systemAccount.findUnique({
      where: {
        uk_account_region: {
          accountType,
          regionCode,
        },
      },
    })
    return record ? this.toDomain(record) : null
  }

  async getOrCreate(
    accountType: SystemAccountType,
    regionCode?: string,
    regionName?: string,
  ): Promise<SystemAccount> {
    // 对于固定系统账户，regionCode 为 null
    const isRegionAccount =
      accountType === SystemAccountType.SYSTEM_PROVINCE ||
      accountType === SystemAccountType.SYSTEM_CITY

    if (isRegionAccount && !regionCode) {
      throw new Error('区域系统账户必须提供 regionCode')
    }

    // For region accounts, use the provided regionCode; otherwise null
    const effectiveRegionCode = isRegionAccount ? regionCode! : null

    // Find existing account - use findFirst for nullable regionCode
    const existing = await this.prisma.systemAccount.findFirst({
      where: {
        accountType,
        regionCode: effectiveRegionCode,
      },
    })

    if (existing) {
      return this.toDomain(existing)
    }

    // 创建新账户
    const record = await this.prisma.systemAccount.create({
      data: {
        accountType,
        regionCode: effectiveRegionCode,
        regionName: isRegionAccount ? (regionName || regionCode) : null,
        walletAddress: null,
        mpcPublicKey: null,
        usdtBalance: 0,
        hashpower: 0,
        totalReceived: 0,
        totalTransferred: 0,
        status: SystemAccountStatus.ACTIVE,
      },
    })

    return this.toDomain(record)
  }

  async findAllFixedAccounts(): Promise<SystemAccount[]> {
    const records = await this.prisma.systemAccount.findMany({
      where: {
        accountType: {
          in: [
            SystemAccountType.COST_ACCOUNT,
            SystemAccountType.OPERATION_ACCOUNT,
            SystemAccountType.HQ_COMMUNITY,
            SystemAccountType.RWAD_POOL_PENDING,
          ],
        },
      },
    })
    return records.map((record) => this.toDomain(record))
  }

  async findAllRegionAccounts(
    accountType: SystemAccountType.SYSTEM_PROVINCE | SystemAccountType.SYSTEM_CITY,
  ): Promise<SystemAccount[]> {
    const records = await this.prisma.systemAccount.findMany({
      where: { accountType },
    })
    return records.map((record) => this.toDomain(record))
  }

  async findByWalletAddress(walletAddress: string): Promise<SystemAccount | null> {
    const record = await this.prisma.systemAccount.findFirst({
      where: { walletAddress },
    })
    return record ? this.toDomain(record) : null
  }

  async updateWalletAddress(
    id: bigint,
    walletAddress: string,
    mpcPublicKey: string,
  ): Promise<void> {
    await this.prisma.systemAccount.update({
      where: { id },
      data: {
        walletAddress,
        mpcPublicKey,
      },
    })
  }

  async saveLedgerEntry(entry: SystemAccountLedgerEntryProps): Promise<bigint> {
    const record = await this.prisma.systemAccountLedger.create({
      data: {
        accountId: entry.accountId,
        entryType: entry.entryType,
        amount: entry.amount,
        balanceAfter: entry.balanceAfter,
        sourceOrderId: entry.sourceOrderId,
        sourceRewardId: entry.sourceRewardId,
        txHash: entry.txHash,
        memo: entry.memo,
      },
    })
    return record.id
  }

  async findLedgerEntriesByAccountId(
    accountId: bigint,
    limit = 50,
    offset = 0,
  ): Promise<SystemAccountLedgerEntryProps[]> {
    const records = await this.prisma.systemAccountLedger.findMany({
      where: { accountId },
      orderBy: { createdAt: 'desc' },
      take: limit,
      skip: offset,
    })

    return records.map((record) => ({
      id: record.id,
      accountId: record.accountId,
      entryType: record.entryType as SystemLedgerEntryType,
      amount: new Decimal(record.amount.toString()),
      balanceAfter: new Decimal(record.balanceAfter.toString()),
      sourceOrderId: record.sourceOrderId,
      sourceRewardId: record.sourceRewardId,
      txHash: record.txHash,
      memo: record.memo,
      createdAt: record.createdAt,
    }))
  }

  private toDomain(record: any): SystemAccount {
    const props: SystemAccountProps = {
      id: record.id,
      accountType: record.accountType as SystemAccountType,
      regionCode: record.regionCode,
      regionName: record.regionName,
      walletAddress: record.walletAddress,
      mpcPublicKey: record.mpcPublicKey,
      usdtBalance: new Decimal(record.usdtBalance.toString()),
      hashpower: new Decimal(record.hashpower.toString()),
      totalReceived: new Decimal(record.totalReceived.toString()),
      totalTransferred: new Decimal(record.totalTransferred.toString()),
      status: record.status as SystemAccountStatus,
      createdAt: record.createdAt,
      updatedAt: record.updatedAt,
    }
    return SystemAccount.fromPersistence(props)
  }
}

export { SYSTEM_ACCOUNT_REPOSITORY }
