import { Injectable, Logger } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { Nack, RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
import { IsPublic } from '@tria-plc/api-common/modules/auth/decorators/public.decorator';
import {
  PAYMENT_EVENTS_DLX,
  PAYMENT_EVENTS_EXCHANGE,
  PAYMENT_QUEUES,
  PaymentEvent,
  PaymentService,
  paymentServiceBindingPattern,
} from '@edr/types';
import { PaymentEventDto } from './internal-payments.dto';
import { PaymentsService } from './payments.service';

const PASSENGER_QUEUE = PAYMENT_QUEUES[PaymentService.PASSENGER];

@Injectable()
export class PaymentEventsConsumer {
  private readonly logger = new Logger(PaymentEventsConsumer.name);

  // IMPORTANT: do NOT constructor-inject PaymentsService here. It is a REQUEST/TRANSIENT-scoped
  // provider (its scope bubbles up from a scoped dependency), so it has no singleton instance at
  // bootstrap. Constructor-injecting it makes THIS consumer scoped too — and golevelup binds the
  // @RabbitSubscribe handler to the singleton instance it discovers at bootstrap. With no such
  // instance, the subscription still registers but delivered messages are never dispatched to
  // handle(): they pile up unacked and the booking never confirms. Injecting only the lightweight
  // (singleton) ModuleRef keeps this consumer a clean singleton; PaymentsService is resolved per
  // message via resolve() (get() throws for scoped providers).
  constructor(private readonly moduleRef: ModuleRef) {}

  @IsPublic()
  @RabbitSubscribe({
    exchange: PAYMENT_EVENTS_EXCHANGE,
    routingKey: paymentServiceBindingPattern(PaymentService.PASSENGER), // payment.passenger.*
    queue: PASSENGER_QUEUE.main,
    queueOptions: {
      durable: true,
      deadLetterExchange: PAYMENT_EVENTS_DLX,
    },
  })
  async handle(event: PaymentEvent): Promise<Nack | void> {
    // Logged the instant RabbitMQ delivers the message, before any DB work — proves the
    // payment -> passenger broker connection works even if processing later fails/hangs.
    this.logger.log(
      `RECEIVED ${event.eventType} (${event.eventId}) ref=${event.referenceId} via RabbitMQ`,
    );
    try {
      // resolve() (not get()) because PaymentsService is scoped — get() throws for scoped providers.
      const paymentsService = await this.moduleRef.resolve(
        PaymentsService,
        undefined,
        { strict: false },
      );
      const result = await paymentsService.handlePaymentEvent(
        event as unknown as PaymentEventDto,
      );
      this.logger.log(
        `processed ${event.eventType} (${event.eventId}) ref=${event.referenceId}: ${JSON.stringify(result)}`,
      );
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      this.logger.error(
        `DEAD-LETTERING ${event.eventType} (${event.eventId}) ref=${event.referenceId}: ${message}`,
      );
      return new Nack(false);
    }
  }
}
