import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { AmqpConnection } from "@golevelup/nestjs-rabbitmq";
import {
  PAYMENT_EVENTS_EXCHANGE,
  PaymentEvent,
  paymentRoutingKey,
} from "@edr/types";
import { PaymentEventPublisher } from "./payment-event-publisher";

@Injectable()
export class RabbitMqPaymentEventPublisher implements PaymentEventPublisher {
  private readonly logger = new Logger(RabbitMqPaymentEventPublisher.name);
  private readonly publishTimeoutMs: number;

  constructor(
    private readonly amqp: AmqpConnection,
    config: ConfigService,
  ) {
    this.publishTimeoutMs =
      config.get<number>("rabbitmq.publishTimeoutMs") ?? 10_000;
  }

  async publish(event: PaymentEvent): Promise<void> {
    const routingKey = paymentRoutingKey(event.service, event.eventType);

    const confirmed = await this.withTimeout(
      this.amqp.publish(PAYMENT_EVENTS_EXCHANGE, routingKey, event, {
        persistent: true,
        messageId: event.eventId,
        contentType: "application/json",
        type: event.eventType,
        headers: { "x-event-version": event.version },
      }),
    );

    if (!confirmed) {
      throw new Error(
        `broker did not confirm publish of ${event.eventType} (${event.eventId})`,
      );
    }

    this.logger.log(
      `published ${event.eventType} (${event.eventId}) -> ${routingKey}`,
    );
  }

  private withTimeout(p: Promise<boolean>): Promise<boolean> {
    let timer: NodeJS.Timeout;
    const timeout = new Promise<boolean>((_, reject) => {
      timer = setTimeout(
        () =>
          reject(
            new Error(`publish confirm timed out after ${this.publishTimeoutMs}ms`),
          ),
        this.publishTimeoutMs,
      );
    });
    return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
  }
}
