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 { 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> {
  constructor(
    @Inject(PAYMENT_REPOSITORY)
    private readonly paymentRepo: IPaymentRepository,
    @Inject(PAYMENT_GATEWAY_FACTORY)
    private readonly gatewayFactory: IPaymentGatewayFactory,
    private readonly eventBus: EventBus,
    private readonly logger: LoggerService,
  ) {}

  async execute(command: CreatePaymentCommand): Promise<CreatePaymentResult> {
    try {
      // 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);
      let paymentUrl: string;
      let providerTxId: string;
      try {
        ({ paymentUrl, providerTxId } = await gateway.createPaymentUrl({
          orderId: paymentId,
          amountVND: command.amountVND,
          description: command.description,
          returnUrl: command.returnUrl,
          ipAddress: command.ipAddress,
        }));
      } catch (error) {
        this.logger.error(
          `Payment gateway ${command.provider} failed for order ${paymentId}: ${error instanceof Error ? error.message : error}`,
          error instanceof Error ? error.stack : undefined,
          'CreatePaymentHandler',
        );
        throw new ValidationException('Không thể tạo liên kết thanh toán, vui lòng thử lại');
      }

      // 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}`,
        'CreatePaymentHandler',
      );

      return { paymentId, paymentUrl, providerTxId };
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to create payment: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Không thể tạo thanh toán. Vui lòng thử lại sau');
    }
  }
}
