import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import {
  SystemAccount,
  SystemAccountTransaction,
  SystemAccountType,
  TransactionType,
  AssetType,
  CounterpartyType,
  PoolAccountType,
  Prisma,
} from '@prisma/client';
import Decimal from 'decimal.js';

export interface CreateSystemAccountInput {
  accountType: SystemAccountType;
  name: string;
  code: string;
  provinceId?: string;
  cityId?: string;
  blockchainAddress?: string;
  description?: string;
}

export interface SystemAccountTransactionInput {
  systemAccountId: string;
  transactionType: TransactionType;
  assetType: AssetType;
  amount: Decimal;
  balanceBefore: Decimal;
  balanceAfter: Decimal;
  counterpartyType?: CounterpartyType;
  counterpartyAccountSeq?: string;
  counterpartyUserId?: string;
  counterpartySystemId?: string;
  counterpartyPoolType?: PoolAccountType;
  counterpartyAddress?: string;
  referenceId?: string;
  referenceType?: string;
  txHash?: string;
  memo?: string;
  metadata?: any;
}

@Injectable()
export class SystemAccountRepository {
  private readonly logger = new Logger(SystemAccountRepository.name);

  constructor(private readonly prisma: PrismaService) {}

  async create(input: CreateSystemAccountInput): Promise<SystemAccount> {
    return this.prisma.systemAccount.create({
      data: {
        accountType: input.accountType,
        name: input.name,
        code: input.code,
        provinceId: input.provinceId,
        cityId: input.cityId,
        blockchainAddress: input.blockchainAddress,
        description: input.description,
      },
    });
  }

  async findById(id: string): Promise<SystemAccount | null> {
    return this.prisma.systemAccount.findUnique({
      where: { id },
      include: {
        province: true,
        city: true,
      },
    });
  }

  async findByCode(code: string): Promise<SystemAccount | null> {
    return this.prisma.systemAccount.findUnique({
      where: { code },
    });
  }

  async findByType(accountType: SystemAccountType): Promise<SystemAccount[]> {
    return this.prisma.systemAccount.findMany({
      where: { accountType },
      include: {
        province: true,
        city: true,
      },
    });
  }

  async findAll(): Promise<SystemAccount[]> {
    return this.prisma.systemAccount.findMany({
      include: {
        province: true,
        city: true,
      },
      orderBy: { createdAt: 'asc' },
    });
  }

  async findByRegion(provinceId?: string, cityId?: string): Promise<SystemAccount[]> {
    const where: Prisma.SystemAccountWhereInput = {};
    if (provinceId) where.provinceId = provinceId;
    if (cityId) where.cityId = cityId;

    return this.prisma.systemAccount.findMany({
      where,
      include: {
        province: true,
        city: true,
      },
    });
  }

  /**
   * 更新余额并记录交易（原子操作）
   */
  async updateBalanceWithTransaction(
    systemAccountId: string,
    assetType: AssetType,
    amount: Decimal,
    transactionInput: Omit<SystemAccountTransactionInput, 'systemAccountId' | 'assetType' | 'amount' | 'balanceBefore' | 'balanceAfter'>,
  ): Promise<{ account: SystemAccount; transaction: SystemAccountTransaction }> {
    return this.prisma.$transaction(async (tx) => {
      // 获取当前账户（加锁）
      const account = await tx.systemAccount.findUnique({
        where: { id: systemAccountId },
      });

      if (!account) {
        throw new Error(`System account not found: ${systemAccountId}`);
      }

      // 根据资产类型获取当前余额
      let currentBalance: Decimal;
      let updateField: string;

      switch (assetType) {
        case 'SHARE':
          currentBalance = new Decimal(account.shareBalance.toString());
          updateField = 'shareBalance';
          break;
        case 'USDT':
          currentBalance = new Decimal(account.usdtBalance.toString());
          updateField = 'usdtBalance';
          break;
        case 'GREEN_POINT':
          currentBalance = new Decimal(account.greenPointBalance.toString());
          updateField = 'greenPointBalance';
          break;
        default:
          throw new Error(`Unsupported asset type: ${assetType}`);
      }

      const newBalance = currentBalance.plus(amount);

      if (newBalance.lessThan(0)) {
        throw new Error(`Insufficient balance for ${assetType}: ${currentBalance} + ${amount} < 0`);
      }

      // 更新余额
      const updatedAccount = await tx.systemAccount.update({
        where: { id: systemAccountId },
        data: {
          [updateField]: newBalance.toFixed(8),
          totalInflow: amount.greaterThan(0)
            ? new Decimal(account.totalInflow.toString()).plus(amount).toFixed(8)
            : account.totalInflow,
          totalOutflow: amount.lessThan(0)
            ? new Decimal(account.totalOutflow.toString()).plus(amount.abs()).toFixed(8)
            : account.totalOutflow,
        },
      });

      // 记录交易
      const transaction = await tx.systemAccountTransaction.create({
        data: {
          systemAccountId,
          transactionType: transactionInput.transactionType,
          assetType,
          amount: amount.toFixed(8),
          balanceBefore: currentBalance.toFixed(8),
          balanceAfter: newBalance.toFixed(8),
          counterpartyType: transactionInput.counterpartyType,
          counterpartyAccountSeq: transactionInput.counterpartyAccountSeq,
          counterpartyUserId: transactionInput.counterpartyUserId,
          counterpartySystemId: transactionInput.counterpartySystemId,
          counterpartyPoolType: transactionInput.counterpartyPoolType,
          counterpartyAddress: transactionInput.counterpartyAddress,
          referenceId: transactionInput.referenceId,
          referenceType: transactionInput.referenceType,
          txHash: transactionInput.txHash,
          memo: transactionInput.memo,
          metadata: transactionInput.metadata,
        },
      });

      return { account: updatedAccount, transaction };
    });
  }

  /**
   * 冻结/解冻余额
   */
  async freezeBalance(
    systemAccountId: string,
    assetType: AssetType,
    amount: Decimal,
    freeze: boolean,
    transactionInput: Omit<SystemAccountTransactionInput, 'systemAccountId' | 'assetType' | 'amount' | 'balanceBefore' | 'balanceAfter' | 'transactionType'>,
  ): Promise<{ account: SystemAccount; transaction: SystemAccountTransaction }> {
    return this.prisma.$transaction(async (tx) => {
      const account = await tx.systemAccount.findUnique({
        where: { id: systemAccountId },
      });

      if (!account) {
        throw new Error(`System account not found: ${systemAccountId}`);
      }

      let currentBalance: Decimal;
      let currentFrozen: Decimal;
      let balanceField: string;
      let frozenField: string;

      switch (assetType) {
        case 'SHARE':
          currentBalance = new Decimal(account.shareBalance.toString());
          currentFrozen = new Decimal(account.frozenShare.toString());
          balanceField = 'shareBalance';
          frozenField = 'frozenShare';
          break;
        case 'USDT':
          currentBalance = new Decimal(account.usdtBalance.toString());
          currentFrozen = new Decimal(account.frozenUsdt.toString());
          balanceField = 'usdtBalance';
          frozenField = 'frozenUsdt';
          break;
        default:
          throw new Error(`Freeze not supported for asset type: ${assetType}`);
      }

      let newBalance: Decimal;
      let newFrozen: Decimal;

      if (freeze) {
        if (currentBalance.lessThan(amount)) {
          throw new Error(`Insufficient balance to freeze: ${currentBalance} < ${amount}`);
        }
        newBalance = currentBalance.minus(amount);
        newFrozen = currentFrozen.plus(amount);
      } else {
        if (currentFrozen.lessThan(amount)) {
          throw new Error(`Insufficient frozen balance to unfreeze: ${currentFrozen} < ${amount}`);
        }
        newBalance = currentBalance.plus(amount);
        newFrozen = currentFrozen.minus(amount);
      }

      const updatedAccount = await tx.systemAccount.update({
        where: { id: systemAccountId },
        data: {
          [balanceField]: newBalance.toFixed(8),
          [frozenField]: newFrozen.toFixed(8),
        },
      });

      const transaction = await tx.systemAccountTransaction.create({
        data: {
          systemAccountId,
          transactionType: freeze ? 'FREEZE' : 'UNFREEZE',
          assetType,
          amount: amount.toFixed(8),
          balanceBefore: currentBalance.toFixed(8),
          balanceAfter: newBalance.toFixed(8),
          counterpartyType: transactionInput.counterpartyType,
          counterpartyAccountSeq: transactionInput.counterpartyAccountSeq,
          counterpartyUserId: transactionInput.counterpartyUserId,
          referenceId: transactionInput.referenceId,
          referenceType: transactionInput.referenceType,
          memo: transactionInput.memo,
          metadata: transactionInput.metadata,
        },
      });

      return { account: updatedAccount, transaction };
    });
  }

  async getTransactions(
    systemAccountId: string,
    options?: {
      transactionType?: TransactionType;
      assetType?: AssetType;
      startDate?: Date;
      endDate?: Date;
      limit?: number;
      offset?: number;
    },
  ): Promise<{ transactions: SystemAccountTransaction[]; total: number }> {
    const where: Prisma.SystemAccountTransactionWhereInput = {
      systemAccountId,
    };

    if (options?.transactionType) {
      where.transactionType = options.transactionType;
    }
    if (options?.assetType) {
      where.assetType = options.assetType;
    }
    if (options?.startDate || options?.endDate) {
      where.createdAt = {};
      if (options.startDate) where.createdAt.gte = options.startDate;
      if (options.endDate) where.createdAt.lte = options.endDate;
    }

    const [transactions, total] = await Promise.all([
      this.prisma.systemAccountTransaction.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        take: options?.limit || 50,
        skip: options?.offset || 0,
      }),
      this.prisma.systemAccountTransaction.count({ where }),
    ]);

    return { transactions, total };
  }
}
