/**
 * Event bus using Redis pub/sub
 */

import IORedis from 'ioredis';
import { getEnv } from '@the-order/shared';

export interface EventData {
  [key: string]: unknown;
}

export type EventHandler<T = EventData> = (data: T, event: string) => Promise<void> | void;

export interface EventBusConfig {
  connection?: {
    host?: string;
    port?: number;
    password?: string;
    db?: number;
  };
  channelPrefix?: string;
}

/**
 * Event Bus for pub/sub messaging
 */
export class EventBus {
  private publisher: IORedis;
  private subscriber: IORedis;
  private handlers: Map<string, Set<EventHandler>> = new Map();
  private channelPrefix: string;

  constructor(config?: EventBusConfig) {
    const env = getEnv();
    const redisUrl = env.REDIS_URL;
    this.channelPrefix = config?.channelPrefix || 'the-order:events:';

    // Create publisher connection
    if (redisUrl) {
      this.publisher = new IORedis(redisUrl, {
        maxRetriesPerRequest: null,
        enableReadyCheck: false,
      });
      this.subscriber = new IORedis(redisUrl, {
        maxRetriesPerRequest: null,
        enableReadyCheck: false,
      });
    } else {
      // Use connection config or defaults
      const connectionConfig = {
        host: config?.connection?.host || 'localhost',
        port: config?.connection?.port || 6379,
        password: config?.connection?.password,
        db: config?.connection?.db || 0,
        maxRetriesPerRequest: null,
        enableReadyCheck: false,
      };
      this.publisher = new IORedis(connectionConfig);
      this.subscriber = new IORedis(connectionConfig);
    }

    // Set up subscriber message handler
    this.subscriber.on('message', (channel, message) => {
      const eventName = channel.replace(this.channelPrefix, '');
      const handlers = this.handlers.get(eventName);
      if (handlers) {
        try {
          const data = JSON.parse(message) as EventData;
          handlers.forEach((handler) => {
            try {
              const result = handler(data, eventName);
              if (result instanceof Promise) {
                result.catch((error) => {
                  console.error(`Error in event handler for ${eventName}:`, error);
                });
              }
            } catch (error) {
              console.error(`Error in event handler for ${eventName}:`, error);
            }
          });
        } catch (error) {
          console.error(`Failed to parse event message for ${eventName}:`, error);
        }
      }
    });
  }

  /**
   * Publish an event
   */
  async publish<T = EventData>(eventName: string, data: T): Promise<void> {
    const channel = `${this.channelPrefix}${eventName}`;
    const message = JSON.stringify(data);
    await this.publisher.publish(channel, message);
  }

  /**
   * Subscribe to an event
   */
  async subscribe<T = EventData>(
    eventName: string,
    handler: EventHandler<T>
  ): Promise<void> {
    const channel = `${this.channelPrefix}${eventName}`;

    // Add handler to map
    if (!this.handlers.has(eventName)) {
      this.handlers.set(eventName, new Set());
      // Subscribe to channel
      await this.subscriber.subscribe(channel);
    }

    this.handlers.get(eventName)!.add(handler as EventHandler);
  }

  /**
   * Unsubscribe from an event
   */
  async unsubscribe<T = EventData>(
    eventName: string,
    handler?: EventHandler<T>
  ): Promise<void> {
    const channel = `${this.channelPrefix}${eventName}`;
    const handlers = this.handlers.get(eventName);

    if (!handlers) {
      return;
    }

    if (handler) {
      handlers.delete(handler as EventHandler);
      // If no more handlers, unsubscribe from channel
      if (handlers.size === 0) {
        await this.subscriber.unsubscribe(channel);
        this.handlers.delete(eventName);
      }
    } else {
      // Remove all handlers
      await this.subscriber.unsubscribe(channel);
      this.handlers.delete(eventName);
    }
  }

  /**
   * Subscribe to multiple events
   */
  async subscribeMany<T = EventData>(
    subscriptions: Array<{ event: string; handler: EventHandler<T> }>
  ): Promise<void> {
    await Promise.all(
      subscriptions.map(({ event, handler }) => this.subscribe(event, handler))
    );
  }

  /**
   * Close connections
   */
  async close(): Promise<void> {
    await this.subscriber.quit();
    await this.publisher.quit();
  }
}

/**
 * Default event bus instance
 */
let defaultEventBus: EventBus | null = null;

/**
 * Get or create default event bus
 */
export function getEventBus(config?: EventBusConfig): EventBus {
  if (!defaultEventBus) {
    defaultEventBus = new EventBus(config);
  }
  return defaultEventBus;
}

/**
 * Predefined event types for credential issuance
 */
export const CredentialEvents = {
  ISSUED: 'credential.issued',
  REVOKED: 'credential.revoked',
  VERIFIED: 'credential.verified',
  RENEWED: 'credential.renewed',
  EXPIRING: 'credential.expiring',
  EXPIRED: 'credential.expired',
} as const;

/**
 * Predefined event types for user actions
 */
export const UserEvents = {
  REGISTERED: 'user.registered',
  VERIFIED: 'user.verified',
  SUSPENDED: 'user.suspended',
} as const;

/**
 * Predefined event types for appointments
 */
export const AppointmentEvents = {
  CREATED: 'appointment.created',
  CONFIRMED: 'appointment.confirmed',
  CANCELLED: 'appointment.cancelled',
} as const;

/**
 * Predefined event types for documents
 */
export const DocumentEvents = {
  UPLOADED: 'document.uploaded',
  APPROVED: 'document.approved',
  REJECTED: 'document.rejected',
} as const;

/**
 * Predefined event types for payments
 */
export const PaymentEvents = {
  COMPLETED: 'payment.completed',
  FAILED: 'payment.failed',
} as const;

