import { Injectable, Logger } from '@nestjs/common';
import { BlockchainRepository } from '../../infrastructure/persistence/repositories/blockchain.repository';
import { UserWalletRepository } from '../../infrastructure/persistence/repositories/user-wallet.repository';
import { OutboxRepository } from '../../infrastructure/persistence/repositories/outbox.repository';
import { KavaBlockchainService } from '../../infrastructure/blockchain/kava-blockchain.service';
import { WithdrawRequest, DepositRecord, DexSwapRecord, AssetType, WithdrawStatus } from '@prisma/client';
import Decimal from 'decimal.js';
import { DomainException } from '../../shared/filters/domain-exception.filter';

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

  constructor(
    private readonly blockchainRepo: BlockchainRepository,
    private readonly userWalletRepo: UserWalletRepository,
    private readonly outboxRepo: OutboxRepository,
    private readonly kavaService: KavaBlockchainService,
  ) {}

  /**
   * 创建提现请求
   */
  async createWithdrawRequest(
    accountSequence: string,
    assetType: AssetType,
    amount: Decimal,
    toAddress: string,
    feeRate: Decimal = new Decimal('0.001'),
  ): Promise<WithdrawRequest> {
    // 验证地址格式
    if (!this.kavaService.isValidAddress(toAddress)) {
      throw new DomainException('Invalid blockchain address', 'INVALID_ADDRESS');
    }

    // 检查用户余额
    const wallet = await this.userWalletRepo.findByAccountAndType(accountSequence, 'TOKEN_STORAGE');
    if (!wallet) {
      throw new DomainException('User wallet not found', 'WALLET_NOT_FOUND');
    }

    const balance = new Decimal(wallet.balance.toString());
    if (balance.lessThan(amount)) {
      throw new DomainException(`Insufficient balance: ${balance} < ${amount}`, 'INSUFFICIENT_BALANCE');
    }

    // 计算手续费
    const fee = amount.mul(feeRate);
    if (fee.greaterThan(amount)) {
      throw new DomainException('Fee exceeds amount', 'FEE_TOO_HIGH');
    }

    // 生成请求号
    const requestNo = `WD${Date.now()}${Math.random().toString(36).substring(2, 8).toUpperCase()}`;

    // 创建提现请求
    const request = await this.blockchainRepo.createWithdrawRequest({
      requestNo,
      accountSequence,
      assetType,
      amount,
      fee,
      toAddress,
    });

    // 冻结用户余额
    await this.userWalletRepo.freezeBalance(
      accountSequence,
      'TOKEN_STORAGE',
      'SHARE',
      amount,
      true,
      {
        referenceId: request.id,
        referenceType: 'WITHDRAW_REQUEST',
        memo: `提现冻结, 目标地址${toAddress}, 数量${amount.toFixed(8)}`,
      },
    );

    await this.outboxRepo.create({
      aggregateType: 'WithdrawRequest',
      aggregateId: request.id,
      eventType: 'WITHDRAW_REQUEST_CREATED',
      payload: {
        requestId: request.id,
        requestNo,
        accountSequence,
        assetType,
        amount: amount.toString(),
        fee: fee.toString(),
        toAddress,
      },
    });

    this.logger.log(`Withdraw request created: ${requestNo}`);
    return request;
  }

  /**
   * 审批提现请求
   */
  async approveWithdrawRequest(
    requestId: string,
    approvedBy: string,
  ): Promise<WithdrawRequest> {
    const request = await this.blockchainRepo.findWithdrawById(requestId);
    if (!request) {
      throw new DomainException('Withdraw request not found', 'REQUEST_NOT_FOUND');
    }

    if (request.status !== 'PENDING') {
      throw new DomainException(`Cannot approve request in status: ${request.status}`, 'INVALID_STATUS');
    }

    const updated = await this.blockchainRepo.updateWithdrawStatus(requestId, 'PROCESSING', {
      approvedBy,
    });

    await this.outboxRepo.create({
      aggregateType: 'WithdrawRequest',
      aggregateId: requestId,
      eventType: 'WITHDRAW_REQUEST_APPROVED',
      payload: {
        requestId,
        requestNo: request.requestNo,
        approvedBy,
        approvedAt: updated.approvedAt?.toISOString(),
      },
    });

    return updated;
  }

  /**
   * 执行链上提现
   */
  async executeWithdraw(requestId: string): Promise<WithdrawRequest> {
    const request = await this.blockchainRepo.findWithdrawById(requestId);
    if (!request) {
      throw new DomainException('Withdraw request not found', 'REQUEST_NOT_FOUND');
    }

    if (request.status !== 'PROCESSING') {
      throw new DomainException(`Cannot execute request in status: ${request.status}`, 'INVALID_STATUS');
    }

    if (!this.kavaService.isReady()) {
      throw new DomainException('Blockchain service not ready', 'BLOCKCHAIN_NOT_READY');
    }

    try {
      // 执行链上转账
      const netAmount = new Decimal(request.netAmount.toString());
      const result = await this.kavaService.sendNative(request.toAddress, netAmount);

      // 更新状态为确认中
      const updated = await this.blockchainRepo.updateWithdrawStatus(requestId, 'CONFIRMING', {
        txHash: result.txHash,
        blockNumber: BigInt(result.blockNumber),
      });

      await this.outboxRepo.create({
        aggregateType: 'WithdrawRequest',
        aggregateId: requestId,
        eventType: 'WITHDRAW_TX_SENT',
        payload: {
          requestId,
          txHash: result.txHash,
          blockNumber: result.blockNumber,
        },
      });

      return updated;
    } catch (error) {
      // 标记失败
      await this.blockchainRepo.updateWithdrawStatus(requestId, 'FAILED', {
        errorMessage: error instanceof Error ? error.message : 'Unknown error',
      });

      // 解冻用户余额
      await this.userWalletRepo.freezeBalance(
        request.accountSequence,
        'TOKEN_STORAGE',
        'SHARE',
        new Decimal(request.amount.toString()),
        false,
        {
          referenceId: requestId,
          referenceType: 'WITHDRAW_REQUEST',
          memo: `提现失败解冻, 原因: ${error instanceof Error ? error.message : 'Unknown error'}`,
        },
      );

      throw error;
    }
  }

  /**
   * 确认提现完成
   */
  async confirmWithdrawComplete(requestId: string): Promise<WithdrawRequest> {
    const request = await this.blockchainRepo.findWithdrawById(requestId);
    if (!request) {
      throw new DomainException('Withdraw request not found', 'REQUEST_NOT_FOUND');
    }

    if (request.status !== 'CONFIRMING' || !request.txHash) {
      throw new DomainException(`Cannot confirm request in status: ${request.status}`, 'INVALID_STATUS');
    }

    // 检查链上确认
    const txStatus = await this.kavaService.getTransactionStatus(request.txHash);
    if (!txStatus.confirmed || txStatus.status !== 'success') {
      throw new DomainException('Transaction not confirmed yet', 'TX_NOT_CONFIRMED');
    }

    // 更新为完成
    const updated = await this.blockchainRepo.updateWithdrawStatus(requestId, 'COMPLETED', {
      confirmations: txStatus.confirmations,
    });

    // 从冻结余额扣除（实际扣款）
    await this.userWalletRepo.updateBalanceWithTransaction(
      request.accountSequence,
      'TOKEN_STORAGE',
      'SHARE',
      new Decimal(request.amount.toString()).negated(),
      {
        transactionType: 'WITHDRAW',
        counterpartyType: 'BLOCKCHAIN',
        counterpartyAddress: request.toAddress,
        referenceId: requestId,
        referenceType: 'WITHDRAW_REQUEST',
        txHash: request.txHash,
        memo: `提现成功, 目标地址${request.toAddress}, 数量${request.netAmount}, 手续费${request.fee}`,
      },
    );

    await this.outboxRepo.create({
      aggregateType: 'WithdrawRequest',
      aggregateId: requestId,
      eventType: 'WITHDRAW_COMPLETED',
      payload: {
        requestId,
        txHash: request.txHash,
        confirmations: txStatus.confirmations,
        completedAt: updated.completedAt?.toISOString(),
      },
    });

    this.logger.log(`Withdraw completed: ${request.requestNo}`);
    return updated;
  }

  /**
   * 绑定用户区块链地址
   */
  async bindUserAddress(
    accountSequence: string,
    kavaAddress: string,
  ): Promise<void> {
    if (!this.kavaService.isValidAddress(kavaAddress)) {
      throw new DomainException('Invalid KAVA address', 'INVALID_ADDRESS');
    }

    await this.blockchainRepo.bindAddress({
      accountSequence,
      kavaAddress,
    });

    await this.outboxRepo.create({
      aggregateType: 'BlockchainAddress',
      aggregateId: accountSequence,
      eventType: 'ADDRESS_BOUND',
      payload: {
        accountSequence,
        kavaAddress,
      },
    });

    this.logger.log(`Address bound for user ${accountSequence}: ${kavaAddress}`);
  }

  /**
   * 创建 DEX Swap 请求
   */
  async createSwapRequest(
    accountSequence: string,
    fromAsset: AssetType,
    toAsset: AssetType,
    fromAmount: Decimal,
    minToAmount: Decimal,
  ): Promise<DexSwapRecord> {
    const swapNo = `SW${Date.now()}${Math.random().toString(36).substring(2, 8).toUpperCase()}`;

    // 这里应该调用 DEX 获取实际汇率
    // 简化处理：假设 1:1 汇率
    const exchangeRate = new Decimal(1);
    const toAmount = fromAmount.mul(exchangeRate);

    if (toAmount.lessThan(minToAmount)) {
      throw new DomainException('Slippage too high', 'SLIPPAGE_EXCEEDED');
    }

    const slippage = toAmount.minus(minToAmount).div(minToAmount).mul(100);
    const fee = fromAmount.mul(new Decimal('0.003')); // 0.3% 手续费

    const swap = await this.blockchainRepo.createSwapRecord({
      swapNo,
      accountSequence,
      fromAsset,
      toAsset,
      fromAmount,
      toAmount,
      exchangeRate,
      slippage,
      fee,
    });

    await this.outboxRepo.create({
      aggregateType: 'DexSwap',
      aggregateId: swap.id,
      eventType: 'SWAP_REQUEST_CREATED',
      payload: {
        swapId: swap.id,
        swapNo,
        accountSequence,
        fromAsset,
        toAsset,
        fromAmount: fromAmount.toString(),
        toAmount: toAmount.toString(),
        exchangeRate: exchangeRate.toString(),
      },
    });

    return swap;
  }

  async getWithdrawRequest(requestId: string): Promise<WithdrawRequest | null> {
    return this.blockchainRepo.findWithdrawById(requestId);
  }

  async getUserWithdrawRequests(
    accountSequence: string,
    options?: { status?: WithdrawStatus; limit?: number; offset?: number },
  ) {
    return this.blockchainRepo.findWithdrawsByAccount(accountSequence, options);
  }

  async getUserAddressBinding(accountSequence: string) {
    return this.blockchainRepo.findAddressByAccount(accountSequence);
  }
}
