import { Inject, Logger } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
import { createId } from '@paralleldrive/cuid2';
import { ConflictException, ValidationException } from '@modules/shared/domain/domain-exception';
import { PaymentEntity } from '../../../domain/entities/payment.entity';
import {
  PAYMENT_REPOSITORY,
  type IPaymentRepository,
} from '../../../domain/repositories/payment.repository';
import { Money } from '../../../domain/value-objects/money.vo';
import {
  PAYMENT_GATEWAY_FACTORY,
  type IPaymentGatewayFactory,
} from '../../../infrastructure/services/payment-gateway.interface';
import { CreatePaymentCommand } from './create-payment.command';

export interface CreatePaymentResult {
  paymentId: string;
  paymentUrl: string;
  providerTxId: string;
}

@CommandHandler(CreatePaymentCommand)
export class CreatePaymentHandler implements ICommandHandler<CreatePaymentCommand> {
  private readonly logger = new Logger(CreatePaymentHandler.name);

  constructor(
    @Inject(PAYMENT_REPOSITORY)
    private readonly paymentRepo: IPaymentRepository,
    @Inject(PAYMENT_GATEWAY_FACTORY)
    private readonly gatewayFactory: IPaymentGatewayFactory,
    private readonly eventBus: EventBus,
  ) {}

  async execute(command: CreatePaymentCommand): Promise<CreatePaymentResult> {
    // Idempotency check
    if (command.idempotencyKey) {
      const existing = await this.paymentRepo.findByIdempotencyKey(command.idempotencyKey);
      if (existing) {
        if (existing.status === 'PENDING' || existing.status === 'PROCESSING') {
          throw new ConflictException('Thanh toán với idempotency key này đã tồn tại');
        }
        throw new ConflictException('Thanh toán đã được xử lý');
      }
    }

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

    const money = moneyResult.unwrap();
    const paymentId = createId();

    // Create domain entity
    const payment = PaymentEntity.createNew(
      paymentId,
      command.userId,
      command.provider,
      command.type,
      money,
      command.transactionId,
      command.idempotencyKey,
    );

    // Get payment gateway and create URL
    const gateway = this.gatewayFactory.getGateway(command.provider);
    const { paymentUrl, providerTxId } = await gateway.createPaymentUrl({
      orderId: paymentId,
      amountVND: command.amountVND,
      description: command.description,
      returnUrl: command.returnUrl,
      ipAddress: command.ipAddress,
    });

    // Mark processing and save
    payment.markProcessing(providerTxId);
    await this.paymentRepo.save(payment);

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

    this.logger.log(
      `Payment created: id=${paymentId}, provider=${command.provider}, amount=${command.amountVND}`,
    );

    return { paymentId, paymentUrl, providerTxId };
  }
}
