import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
import { type PaymentStatus } from '@prisma/client';
import { DomainException, NotFoundException, ValidationException, LoggerService } from '@modules/shared';
import {
  PAYMENT_REPOSITORY,
  type IPaymentRepository,
} from '../../../domain/repositories/payment.repository';
import {
  PAYMENT_GATEWAY_FACTORY,
  type IPaymentGatewayFactory,
} from '../../../infrastructure/services/payment-gateway.interface';
import { HandleCallbackCommand } from './handle-callback.command';

export interface HandleCallbackResult {
  paymentId: string;
  status: string;
  isSuccess: boolean;
}

@CommandHandler(HandleCallbackCommand)
export class HandleCallbackHandler implements ICommandHandler<HandleCallbackCommand> {
  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: HandleCallbackCommand): Promise<HandleCallbackResult> {
    try {
      const gateway = this.gatewayFactory.getGateway(command.provider);
      const result = gateway.verifyCallback(command.callbackData);

      if (!result.isValid) {
        this.logger.warn(
          `Invalid callback signature for provider=${command.provider}`,
          'HandleCallbackHandler',
        );
        throw new ValidationException('Chữ ký callback không hợp lệ');
      }

      // Atomically transition payment status to prevent race conditions
      // on concurrent callbacks. Only PENDING/PROCESSING payments can be updated.
      const targetStatus = result.isSuccess ? 'COMPLETED' : 'FAILED';
      const updated = await this.paymentRepo.updateIfStatus(
        result.orderId,
        ['PENDING', 'PROCESSING'],
        {
          status: targetStatus as PaymentStatus,
          callbackData: result.rawData,
        },
      );

      if (!updated) {
        // Either payment doesn't exist or is already in a terminal state
        const existing = await this.paymentRepo.findById(result.orderId);
        if (!existing) {
          this.logger.warn(`Payment not found for orderId=${result.orderId}`, 'HandleCallbackHandler');
          throw new NotFoundException('Payment', result.orderId);
        }

        // Already processed — return idempotent response
        this.logger.log(
          `Payment ${existing.id} already in terminal state: ${existing.status}`,
          'HandleCallbackHandler',
        );
        return {
          paymentId: existing.id,
          status: existing.status,
          isSuccess: existing.status === 'COMPLETED',
        };
      }

      // Reconstruct domain entity and publish events
      if (result.isSuccess) {
        updated.emitCompleted();
      } else {
        updated.emitFailed();
      }

      const events = updated.clearDomainEvents();
      for (const event of events) {
        this.eventBus.publish(event);
      }

      this.logger.log(
        `Payment ${updated.id} callback processed: status=${updated.status}`,
        'HandleCallbackHandler',
      );

      return {
        paymentId: updated.id,
        status: updated.status,
        isSuccess: result.isSuccess,
      };
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to handle payment callback: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Không thể xử lý callback thanh toán. Vui lòng thử lại sau');
    }
  }
}
