import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import {
  WithdrawRequest,
  DepositRecord,
  DexSwapRecord,
  BlockchainAddressBinding,
  BlackHoleContract,
  BurnToBlackHoleRecord,
  WithdrawStatus,
  AssetType,
  CounterpartyType,
  PoolAccountType,
  Prisma,
} from '@prisma/client';
import Decimal from 'decimal.js';

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

  constructor(private readonly prisma: PrismaService) {}

  // ==================== Withdraw Requests ====================

  async createWithdrawRequest(data: {
    requestNo: string;
    accountSequence: string;
    assetType: AssetType;
    amount: Decimal;
    fee: Decimal;
    toAddress: string;
  }): Promise<WithdrawRequest> {
    const netAmount = data.amount.minus(data.fee);

    return this.prisma.withdrawRequest.create({
      data: {
        requestNo: data.requestNo,
        accountSequence: data.accountSequence,
        assetType: data.assetType,
        amount: data.amount.toFixed(8),
        fee: data.fee.toFixed(8),
        netAmount: netAmount.toFixed(8),
        toAddress: data.toAddress,
        status: 'PENDING',
      },
    });
  }

  async findWithdrawById(id: string): Promise<WithdrawRequest | null> {
    return this.prisma.withdrawRequest.findUnique({
      where: { id },
    });
  }

  async findWithdrawByRequestNo(requestNo: string): Promise<WithdrawRequest | null> {
    return this.prisma.withdrawRequest.findUnique({
      where: { requestNo },
    });
  }

  async findWithdrawsByAccount(
    accountSequence: string,
    options?: {
      status?: WithdrawStatus;
      limit?: number;
      offset?: number;
    },
  ): Promise<{ requests: WithdrawRequest[]; total: number }> {
    const where: Prisma.WithdrawRequestWhereInput = {
      accountSequence,
    };

    if (options?.status) {
      where.status = options.status;
    }

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

    return { requests, total };
  }

  async updateWithdrawStatus(
    id: string,
    status: WithdrawStatus,
    data?: {
      txHash?: string;
      blockNumber?: bigint;
      confirmations?: number;
      errorMessage?: string;
      approvedBy?: string;
    },
  ): Promise<WithdrawRequest> {
    const updateData: Prisma.WithdrawRequestUpdateInput = {
      status,
    };

    if (data?.txHash) updateData.txHash = data.txHash;
    if (data?.blockNumber) updateData.blockNumber = data.blockNumber;
    if (data?.confirmations) updateData.confirmations = data.confirmations;
    if (data?.errorMessage) updateData.errorMessage = data.errorMessage;
    if (data?.approvedBy) {
      updateData.approvedBy = data.approvedBy;
      updateData.approvedAt = new Date();
    }
    if (status === 'COMPLETED') {
      updateData.completedAt = new Date();
    }

    return this.prisma.withdrawRequest.update({
      where: { id },
      data: updateData,
    });
  }

  // ==================== Deposit Records ====================

  async createDepositRecord(data: {
    txHash: string;
    fromAddress: string;
    toAddress: string;
    assetType: AssetType;
    amount: Decimal;
    blockNumber: bigint;
    confirmations?: number;
    matchedAccountSeq?: string;
  }): Promise<DepositRecord> {
    return this.prisma.depositRecord.create({
      data: {
        txHash: data.txHash,
        fromAddress: data.fromAddress,
        toAddress: data.toAddress,
        assetType: data.assetType,
        amount: data.amount.toFixed(8),
        blockNumber: data.blockNumber,
        confirmations: data.confirmations || 0,
        matchedAccountSeq: data.matchedAccountSeq,
      },
    });
  }

  async findDepositByTxHash(txHash: string): Promise<DepositRecord | null> {
    return this.prisma.depositRecord.findUnique({
      where: { txHash },
    });
  }

  async findUnprocessedDeposits(limit: number = 100): Promise<DepositRecord[]> {
    return this.prisma.depositRecord.findMany({
      where: {
        isProcessed: false,
        matchedAccountSeq: { not: null },
      },
      orderBy: { createdAt: 'asc' },
      take: limit,
    });
  }

  async markDepositProcessed(id: string): Promise<DepositRecord> {
    return this.prisma.depositRecord.update({
      where: { id },
      data: {
        isProcessed: true,
        processedAt: new Date(),
      },
    });
  }

  async updateDepositConfirmations(txHash: string, confirmations: number): Promise<DepositRecord> {
    return this.prisma.depositRecord.update({
      where: { txHash },
      data: { confirmations },
    });
  }

  // ==================== DEX Swap Records ====================

  async createSwapRecord(data: {
    swapNo: string;
    accountSequence: string;
    fromAsset: AssetType;
    toAsset: AssetType;
    fromAmount: Decimal;
    toAmount: Decimal;
    exchangeRate: Decimal;
    slippage?: Decimal;
    fee?: Decimal;
  }): Promise<DexSwapRecord> {
    return this.prisma.dexSwapRecord.create({
      data: {
        swapNo: data.swapNo,
        accountSequence: data.accountSequence,
        fromAsset: data.fromAsset,
        toAsset: data.toAsset,
        fromAmount: data.fromAmount.toFixed(8),
        toAmount: data.toAmount.toFixed(8),
        exchangeRate: data.exchangeRate.toFixed(18),
        slippage: data.slippage?.toFixed(4) || '0',
        fee: data.fee?.toFixed(8) || '0',
        status: 'PENDING',
      },
    });
  }

  async findSwapById(id: string): Promise<DexSwapRecord | null> {
    return this.prisma.dexSwapRecord.findUnique({
      where: { id },
    });
  }

  async findSwapBySwapNo(swapNo: string): Promise<DexSwapRecord | null> {
    return this.prisma.dexSwapRecord.findUnique({
      where: { swapNo },
    });
  }

  async updateSwapStatus(
    id: string,
    status: WithdrawStatus,
    data?: {
      txHash?: string;
      blockNumber?: bigint;
      errorMessage?: string;
    },
  ): Promise<DexSwapRecord> {
    const updateData: Prisma.DexSwapRecordUpdateInput = {
      status,
    };

    if (data?.txHash) updateData.txHash = data.txHash;
    if (data?.blockNumber) updateData.blockNumber = data.blockNumber;
    if (data?.errorMessage) updateData.errorMessage = data.errorMessage;
    if (status === 'COMPLETED') {
      updateData.completedAt = new Date();
    }

    return this.prisma.dexSwapRecord.update({
      where: { id },
      data: updateData,
    });
  }

  // ==================== Address Binding ====================

  async bindAddress(data: {
    accountSequence: string;
    kavaAddress: string;
  }): Promise<BlockchainAddressBinding> {
    return this.prisma.blockchainAddressBinding.upsert({
      where: { accountSequence: data.accountSequence },
      create: {
        accountSequence: data.accountSequence,
        kavaAddress: data.kavaAddress,
      },
      update: {
        kavaAddress: data.kavaAddress,
        isVerified: false,
        verifiedAt: null,
        verificationTxHash: null,
      },
    });
  }

  async findAddressByAccount(accountSequence: string): Promise<BlockchainAddressBinding | null> {
    return this.prisma.blockchainAddressBinding.findUnique({
      where: { accountSequence },
    });
  }

  async findAccountByAddress(kavaAddress: string): Promise<BlockchainAddressBinding | null> {
    return this.prisma.blockchainAddressBinding.findUnique({
      where: { kavaAddress },
    });
  }

  async verifyAddress(
    accountSequence: string,
    verificationTxHash: string,
  ): Promise<BlockchainAddressBinding> {
    return this.prisma.blockchainAddressBinding.update({
      where: { accountSequence },
      data: {
        isVerified: true,
        verifiedAt: new Date(),
        verificationTxHash,
      },
    });
  }

  // ==================== Black Hole Contract ====================

  async createBlackHoleContract(data: {
    contractAddress: string;
    name: string;
    targetBurn: Decimal;
  }): Promise<BlackHoleContract> {
    return this.prisma.blackHoleContract.create({
      data: {
        contractAddress: data.contractAddress,
        name: data.name,
        targetBurn: data.targetBurn.toFixed(8),
        remainingBurn: data.targetBurn.toFixed(8),
      },
    });
  }

  async findBlackHoleContract(contractAddress: string): Promise<BlackHoleContract | null> {
    return this.prisma.blackHoleContract.findUnique({
      where: { contractAddress },
    });
  }

  async getActiveBlackHoleContract(): Promise<BlackHoleContract | null> {
    return this.prisma.blackHoleContract.findFirst({
      where: { isActive: true },
    });
  }

  async recordBurnToBlackHole(data: {
    blackHoleId: string;
    amount: Decimal;
    sourceType: CounterpartyType;
    sourceAccountSeq?: string;
    sourceUserId?: string;
    sourcePoolType?: PoolAccountType;
    txHash?: string;
    blockNumber?: bigint;
    memo?: string;
  }): Promise<BurnToBlackHoleRecord> {
    return this.prisma.$transaction(async (tx) => {
      // 更新黑洞合约统计
      const blackHole = await tx.blackHoleContract.findUnique({
        where: { id: data.blackHoleId },
      });

      if (!blackHole) {
        throw new Error(`Black hole contract not found: ${data.blackHoleId}`);
      }

      const newTotalBurned = new Decimal(blackHole.totalBurned.toString()).plus(data.amount);
      const newRemainingBurn = new Decimal(blackHole.remainingBurn.toString()).minus(data.amount);

      await tx.blackHoleContract.update({
        where: { id: data.blackHoleId },
        data: {
          totalBurned: newTotalBurned.toFixed(8),
          remainingBurn: newRemainingBurn.greaterThan(0) ? newRemainingBurn.toFixed(8) : '0',
        },
      });

      // 创建销毁记录
      return tx.burnToBlackHoleRecord.create({
        data: {
          blackHoleId: data.blackHoleId,
          amount: data.amount.toFixed(8),
          sourceType: data.sourceType,
          sourceAccountSeq: data.sourceAccountSeq,
          sourceUserId: data.sourceUserId,
          sourcePoolType: data.sourcePoolType,
          txHash: data.txHash,
          blockNumber: data.blockNumber,
          memo: data.memo,
        },
      });
    });
  }

  async getBurnRecords(
    blackHoleId: string,
    options?: {
      limit?: number;
      offset?: number;
    },
  ): Promise<{ records: BurnToBlackHoleRecord[]; total: number }> {
    const where: Prisma.BurnToBlackHoleRecordWhereInput = {
      blackHoleId,
    };

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

    return { records, total };
  }
}
