import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service';
import { IWithdrawalOrderRepository } from '@/domain/repositories';
import { WithdrawalOrder } from '@/domain/aggregates';
import { WithdrawalStatus } from '@/domain/value-objects';
import { RepositorySaveOptions, PrismaTransactionClient } from '@/infrastructure/persistence/unit-of-work';
import Decimal from 'decimal.js';

@Injectable()
export class WithdrawalOrderRepositoryImpl implements IWithdrawalOrderRepository {
  constructor(private readonly prisma: PrismaService) {}

  private getClient(options?: RepositorySaveOptions): PrismaService | PrismaTransactionClient {
    return options?.tx || this.prisma;
  }

  async save(order: WithdrawalOrder, options?: RepositorySaveOptions): Promise<WithdrawalOrder> {
    const client = this.getClient(options);
    const data = {
      orderNo: order.orderNo,
      accountSequence: order.accountSequence,
      userId: order.userId.value,
      amount: order.amount.toDecimal(),
      fee: order.fee.toDecimal(),
      chainType: order.chainType,
      toAddress: order.toAddress,
      txHash: order.txHash,
      isInternalTransfer: order.isInternalTransfer,
      toAccountSequence: order.toAccountSequence,
      toUserId: order.toUserId?.value ?? null,
      status: order.status,
      errorMessage: order.errorMessage,
      frozenAt: order.frozenAt,
      broadcastedAt: order.broadcastedAt,
      confirmedAt: order.confirmedAt,
    };

    if (order.id === BigInt(0)) {
      const created = await client.withdrawalOrder.create({ data });
      return this.toDomain(created);
    } else {
      const updated = await client.withdrawalOrder.update({
        where: { id: order.id },
        data,
      });
      return this.toDomain(updated);
    }
  }

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

  async findByOrderNo(orderNo: string): Promise<WithdrawalOrder | null> {
    const record = await this.prisma.withdrawalOrder.findUnique({
      where: { orderNo },
    });
    return record ? this.toDomain(record) : null;
  }

  async findByUserId(userId: bigint, status?: WithdrawalStatus): Promise<WithdrawalOrder[]> {
    const where: Record<string, unknown> = { userId };
    if (status) {
      where.status = status;
    }

    const records = await this.prisma.withdrawalOrder.findMany({
      where,
      orderBy: { createdAt: 'desc' },
    });
    return records.map(r => this.toDomain(r));
  }

  async findByAccountSequence(accountSequence: string, status?: WithdrawalStatus): Promise<WithdrawalOrder[]> {
    const where: Record<string, unknown> = { accountSequence };
    if (status) {
      where.status = status;
    }

    const records = await this.prisma.withdrawalOrder.findMany({
      where,
      orderBy: { createdAt: 'desc' },
    });
    return records.map(r => this.toDomain(r));
  }

  async findPendingOrders(): Promise<WithdrawalOrder[]> {
    const records = await this.prisma.withdrawalOrder.findMany({
      where: { status: WithdrawalStatus.PENDING },
      orderBy: { createdAt: 'asc' },
    });
    return records.map(r => this.toDomain(r));
  }

  async findFrozenOrders(): Promise<WithdrawalOrder[]> {
    const records = await this.prisma.withdrawalOrder.findMany({
      where: { status: WithdrawalStatus.FROZEN },
      orderBy: { createdAt: 'asc' },
    });
    return records.map(r => this.toDomain(r));
  }

  async findBroadcastedOrders(): Promise<WithdrawalOrder[]> {
    const records = await this.prisma.withdrawalOrder.findMany({
      where: { status: WithdrawalStatus.BROADCASTED },
      orderBy: { createdAt: 'asc' },
    });
    return records.map(r => this.toDomain(r));
  }

  private toDomain(record: {
    id: bigint;
    orderNo: string;
    accountSequence: string;
    userId: bigint;
    amount: Decimal;
    fee: Decimal;
    chainType: string;
    toAddress: string;
    txHash: string | null;
    isInternalTransfer: boolean;
    toAccountSequence: string | null;
    toUserId: bigint | null;
    status: string;
    errorMessage: string | null;
    frozenAt: Date | null;
    broadcastedAt: Date | null;
    confirmedAt: Date | null;
    createdAt: Date;
  }): WithdrawalOrder {
    return WithdrawalOrder.reconstruct({
      id: record.id,
      orderNo: record.orderNo,
      accountSequence: record.accountSequence,
      userId: record.userId,
      amount: new Decimal(record.amount.toString()),
      fee: new Decimal(record.fee.toString()),
      chainType: record.chainType,
      toAddress: record.toAddress,
      txHash: record.txHash,
      isInternalTransfer: record.isInternalTransfer,
      toAccountSequence: record.toAccountSequence,
      toUserId: record.toUserId,
      status: record.status,
      errorMessage: record.errorMessage,
      frozenAt: record.frozenAt,
      broadcastedAt: record.broadcastedAt,
      confirmedAt: record.confirmedAt,
      createdAt: record.createdAt,
    });
  }
}
