import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
import { createId } from '@paralleldrive/cuid2';
import { ConflictException, DomainException, ValidationException, LoggerService } from '@modules/shared';
import { EscrowEntity } from '../../../domain/entities/escrow.entity';
import { OrderEntity } from '../../../domain/entities/order.entity';
import { ESCROW_REPOSITORY, type IEscrowRepository } from '../../../domain/repositories/escrow.repository';
import { ORDER_REPOSITORY, type IOrderRepository } from '../../../domain/repositories/order.repository';
import { Money } from '../../../domain/value-objects/money.vo';
import { PlatformFee } from '../../../domain/value-objects/platform-fee.vo';
import { CreateOrderCommand } from './create-order.command';

export interface CreateOrderResult {
  orderId: string;
  escrowId: string;
  amountVND: string;
  platformFeeVND: string;
  sellerPayoutVND: string;
}

@CommandHandler(CreateOrderCommand)
export class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {
  constructor(
    @Inject(ORDER_REPOSITORY) private readonly orderRepo: IOrderRepository,
    @Inject(ESCROW_REPOSITORY) private readonly escrowRepo: IEscrowRepository,
    private readonly eventBus: EventBus,
    private readonly logger: LoggerService,
  ) {}

  async execute(command: CreateOrderCommand): Promise<CreateOrderResult> {
    try {
      // Idempotency check
      if (command.idempotencyKey) {
        const existing = await this.orderRepo.findByIdempotencyKey(command.idempotencyKey);
        if (existing) {
          throw new ConflictException('Đơn hàng với idempotency key này đã tồn tại');
        }
      }

      // Validate amount
      const amountResult = Money.create(command.amountVND);
      if (amountResult.isErr) {
        throw new ValidationException(amountResult.unwrapErr());
      }
      const amount = amountResult.unwrap();

      // Calculate platform fee (5%)
      const feeResult = PlatformFee.fromOrderAmount(command.amountVND);
      if (feeResult.isErr) {
        throw new ValidationException(feeResult.unwrapErr());
      }
      const platformFee = feeResult.unwrap();

      // Calculate seller payout
      const sellerPayoutVND = command.amountVND - platformFee.value;
      const payoutResult = Money.create(sellerPayoutVND);
      if (payoutResult.isErr) {
        throw new ValidationException(payoutResult.unwrapErr());
      }
      const sellerPayout = payoutResult.unwrap();

      const orderId = createId();
      const escrowId = createId();

      // Create order aggregate
      const order = OrderEntity.createNew(
        orderId,
        command.buyerId,
        command.sellerId,
        command.listingId,
        amount,
        platformFee,
        sellerPayout,
        command.idempotencyKey,
      );

      // Create escrow (pending until payment confirmed)
      const feeAsMoney = Money.create(platformFee.value).unwrap();
      const escrow = EscrowEntity.createNew(escrowId, orderId, amount, feeAsMoney);

      // Persist both
      await this.orderRepo.save(order);
      await this.escrowRepo.save(escrow);

      // Publish domain events
      for (const event of order.clearDomainEvents()) {
        this.eventBus.publish(event);
      }

      this.logger.log(
        `Order created: id=${orderId}, buyer=${command.buyerId}, amount=${command.amountVND}`,
        'CreateOrderHandler',
      );

      return {
        orderId,
        escrowId,
        amountVND: command.amountVND.toString(),
        platformFeeVND: platformFee.value.toString(),
        sellerPayoutVND: sellerPayout.value.toString(),
      };
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to create order: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Không thể tạo đơn hàng. Vui lòng thử lại sau');
    }
  }
}
