import { Injectable, Logger } from "@nestjs/common";
import type { MessageEvent } from "@nestjs/common";
import { Observable } from "rxjs";
import type { OrderUpdateEventPayload } from "@customer-portal/domain/orders";

interface InternalObserver {
  next: (event: MessageEvent) => void;
  complete: () => void;
  error: (error: unknown) => void;
}

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

  private readonly observers = new Map<string, Set<InternalObserver>>();

  subscribe(orderId: string): Observable<MessageEvent> {
    return new Observable<MessageEvent>(subscriber => {
      const wrappedObserver: InternalObserver = {
        next: value => subscriber.next(value),
        complete: () => subscriber.complete(),
        error: error => subscriber.error(error),
      };

      const orderObservers = this.observers.get(orderId) ?? new Set<InternalObserver>();
      orderObservers.add(wrappedObserver);
      this.observers.set(orderId, orderObservers);

      this.logger.debug(`Order stream connected`, { orderId, listeners: orderObservers.size });

      // Immediately notify client that stream is ready
      wrappedObserver.next(
        this.buildEvent("order.stream.ready", {
          orderId,
          timestamp: new Date().toISOString(),
        })
      );

      const heartbeat = setInterval(() => {
        wrappedObserver.next(
          this.buildEvent("order.stream.heartbeat", {
            orderId,
            timestamp: new Date().toISOString(),
          })
        );
      }, 30000);

      return () => {
        clearInterval(heartbeat);
        const currentObservers = this.observers.get(orderId);
        if (currentObservers) {
          currentObservers.delete(wrappedObserver);
          if (currentObservers.size === 0) {
            this.observers.delete(orderId);
          }
        }
        this.logger.debug(`Order stream disconnected`, {
          orderId,
          listeners: currentObservers?.size ?? 0,
        });
      };
    });
  }

  publish(orderId: string, update: OrderUpdateEventPayload): void {
    const currentObservers = this.observers.get(orderId);
    if (!currentObservers || currentObservers.size === 0) {
      this.logger.debug("No active listeners for order update", { orderId });
      return;
    }

    const event = this.buildEvent("order.update", update);

    currentObservers.forEach(observer => {
      try {
        observer.next(event);
      } catch (error) {
        this.logger.warn("Failed to notify order update listener", {
          orderId,
          error: error instanceof Error ? error.message : String(error),
        });
      }
    });
  }

  private buildEvent<T extends object>(event: string, data: T): MessageEvent {
    return {
      data: {
        event,
        data,
      },
    } satisfies MessageEvent;
  }
}
