/**
 * EventPublisher — fire-and-forget event bus for agent lifecycle and credential events.
 *
 * On each publishEvent call the service:
 *   1. Builds the IWebhookPayload envelope.
 *   2. Queries for active webhook subscriptions that match the event type.
 *   3. Creates a delivery record for each matching subscription and enqueues a Bull job.
 *   4. Optionally produces the event to the 'agentidp-events' Kafka topic (when configured).
 *
 * Kafka errors are caught and logged but never propagate — event publishing must not
 * block or fail the primary business operation.
 */

import { Pool } from 'pg';
import crypto from 'crypto';
import { Producer as KafkaProducer } from 'kafkajs';
import { WebhookDeliveryWorker } from '../workers/WebhookDeliveryWorker.js';
import { IWebhookPayload, WebhookEventType } from '../types/webhook.js';

/** Minimal subscription row shape needed for event fanout. */
interface ActiveSubscriptionRow {
  id: string;
  organization_id: string;
}

/**
 * Fire-and-forget event publisher.
 * Fanout to webhooks and optionally to Kafka on each publishEvent call.
 */
export class EventPublisher {
  /**
   * @param webhookWorker - Worker that enqueues outbound HTTP delivery jobs.
   * @param pool - PostgreSQL connection pool (for subscription lookup and delivery inserts).
   * @param kafkaProducer - Optional connected KafkaJS producer. Null when Kafka is not configured.
   */
  constructor(
    private readonly webhookWorker: WebhookDeliveryWorker,
    private readonly pool: Pool,
    private readonly kafkaProducer: KafkaProducer | null,
  ) {}

  /**
   * Publishes an event to all active matching webhook subscriptions and optionally to Kafka.
   *
   * This method is fire-and-forget. Individual delivery failures are logged but not
   * thrown. Callers should not await this method — use `void this.eventPublisher?.publishEvent(...)`.
   *
   * @param orgId - The organization UUID that owns the resource that changed.
   * @param eventType - The event type identifier.
   * @param data - Event-specific payload data.
   */
  async publishEvent(
    orgId: string,
    eventType: WebhookEventType,
    data: Record<string, unknown>,
  ): Promise<void> {
    const id = crypto.randomUUID();
    const payload: IWebhookPayload = {
      id,
      event: eventType,
      timestamp: new Date().toISOString(),
      organization_id: orgId,
      data,
    };

    // ── Webhook fanout ─────────────────────────────────────────────────────
    try {
      // Find all active subscriptions for this org that include this event type.
      // The events column is a JSONB array; we check containment with @>.
      const subResult = await this.pool.query<ActiveSubscriptionRow>(
        `SELECT id, organization_id
         FROM webhook_subscriptions
         WHERE organization_id = $1
           AND active = true
           AND events @> $2::jsonb`,
        [orgId, JSON.stringify([eventType])],
      );

      for (const sub of subResult.rows) {
        try {
          // Insert a delivery record
          const deliveryResult = await this.pool.query<{ id: string }>(
            `INSERT INTO webhook_deliveries
               (subscription_id, event_type, payload, status, attempt_count)
             VALUES ($1, $2, $3::jsonb, 'pending', 0)
             RETURNING id`,
            [sub.id, eventType, JSON.stringify(payload)],
          );

          const deliveryId = deliveryResult.rows[0].id;

          // Enqueue the Bull delivery job
          await this.webhookWorker.enqueue({
            deliveryId,
            subscriptionId: sub.id,
            organizationId: orgId,
            payload,
          });
        } catch (subErr) {
          console.error(
            `[EventPublisher] Failed to enqueue delivery for subscription ${sub.id}: ${(subErr as Error).message}`,
          );
        }
      }
    } catch (webhookErr) {
      console.error(
        `[EventPublisher] Webhook fanout error for event ${eventType} (org ${orgId}): ${(webhookErr as Error).message}`,
      );
    }

    // ── Kafka fanout ───────────────────────────────────────────────────────
    if (this.kafkaProducer !== null) {
      try {
        await this.kafkaProducer.send({
          topic: 'agentidp-events',
          messages: [
            {
              key: orgId,
              value: JSON.stringify(payload),
            },
          ],
        });
      } catch (kafkaErr) {
        console.error(
          `[EventPublisher] Kafka produce error for event ${eventType} (org ${orgId}): ${(kafkaErr as Error).message}`,
        );
      }
    }
  }
}
