import { Injectable, Logger } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';
import { OnEvent } from '@nestjs/event-emitter';
import { PrismaService } from '../../../prisma/prisma.service';
import { BookingEventPayload } from '../core/booking-events.service';
import { BookingWebhookJobData } from '../processors/webhook.processor';
import { randomUUID } from 'crypto';

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

  constructor(
    private readonly prisma: PrismaService,
    @InjectQueue('booking-webhooks') private readonly webhookQueue: Queue,
  ) {}

  /**
   * Catches ALL booking.* events and dispatches to matching webhook endpoints.
   */
  @OnEvent('booking.*', { async: true })
  async handleBookingEvent(payload: BookingEventPayload) {
    const { clubId, event } = payload;

    // Find active webhook endpoints for this club that subscribe to this event
    const endpoints = await this.prisma.bookingWebhookEndpoint.findMany({
      where: {
        clubId,
        isActive: true,
        OR: [
          { events: { has: event } },
          { events: { has: '*' } }, // wildcard — subscribe to all events
        ],
      },
    });

    if (endpoints.length === 0) return;

    this.logger.log(`Dispatching ${event} to ${endpoints.length} endpoint(s) for club ${clubId}`);

    for (const endpoint of endpoints) {
      const logId = randomUUID();

      // Create delivery log
      await this.prisma.bookingWebhookLog.create({
        data: {
          id: logId,
          endpointId: endpoint.id,
          event,
          payload: (payload.data ?? {}) as object,
          attempts: 0,
        },
      });

      // Enqueue delivery job
      const jobData: BookingWebhookJobData = {
        endpointId: endpoint.id,
        url: endpoint.url,
        secret: endpoint.secret,
        event,
        payload: {
          event,
          clubId,
          timestamp: payload.timestamp.toISOString(),
          data: payload.data,
        },
        logId,
      };

      await this.webhookQueue.add(`webhook:${event}`, jobData, {
        attempts: 3,
        backoff: { type: 'custom' },
        removeOnComplete: 100,
        removeOnFail: 500,
      });
    }
  }

  /**
   * Manual re-delivery of a failed webhook log entry.
   */
  async redeliver(logId: string) {
    const log = await this.prisma.bookingWebhookLog.findUniqueOrThrow({
      where: { id: logId },
      include: { endpoint: true },
    });

    await this.prisma.bookingWebhookLog.update({
      where: { id: logId },
      data: { attempts: 0, statusCode: null, response: null, nextRetry: null },
    });

    const jobData: BookingWebhookJobData = {
      endpointId: log.endpoint.id,
      url: log.endpoint.url,
      secret: log.endpoint.secret,
      event: log.event,
      payload: log.payload as Record<string, unknown>,
      logId,
    };

    await this.webhookQueue.add(`webhook:redeliver:${log.event}`, jobData, {
      attempts: 3,
      backoff: { type: 'custom' },
      removeOnComplete: 100,
      removeOnFail: 500,
    });

    return { success: true, logId };
  }
}
