/**
 * Event Service

 * The service supports two types of events:
 * 1. Global events (broadcast to all clients)
 * 2. Lobby-specific events (broadcast only to clients in a specific lobby)
 * 
 * Each server instance has a unique ID to prevent event echo/duplication
 * when events are published through Redis.
 */
import EventEmitter from 'events';
import { Response } from 'express';
import { createLogger } from '../utils/logger';
import redisService from './redisService';
import os from 'os';

const logger = createLogger('EventService');
const GLOBAL_EVENTS_CHANNEL = 'global-lobbies-events';

class EventService {
  private eventEmitter: EventEmitter;
  private lobbyClients: Response[] = [];
  private clients: { [key: string]: Response[] } = {};
  private serverId: string;

  /**
   * Initializes the EventService with unique server ID and Redis subscriptions
   */
  constructor() {
    this.eventEmitter = new EventEmitter();
    // Increase max listeners to avoid memory leak warnings
    this.eventEmitter.setMaxListeners(100);
    // Generate a unique ID for this server instance
    this.serverId = os.hostname();
    logger.info(`EventService initialized with serverId: ${this.serverId}`);
    
    // Subscribe to global events from Redis
    this.setupRedisSubscriptions();
  }

  private setupRedisSubscriptions(): void {
    // Subscribe to global lobby events
    redisService.subscribe(GLOBAL_EVENTS_CHANNEL, (message: string) => {
      try {
        const data = JSON.parse(message);
        // Only process messages from other servers
        if (data.serverId !== this.serverId) {
          logger.info(`Received global event from another server: ${data.type}`);
          // Forward the event to local subscribers without re-publishing
          this.eventEmitter.emit('global-lobbies', JSON.stringify(data.payload));
        }
      } catch (err) {
        logger.error('Error processing Redis message:', err);
      }
    });
    
    // Track subscribed lobby channels to avoid duplicate subscriptions
    const subscribedLobbies = new Set<string>();
    
    // Helper method to subscribe to a lobby's Redis channel
    const subscribeLobbyChannel = (lobbyCode: string) => {
      const lobbyChannel = `lobby-events-${lobbyCode}`;
      
      if (subscribedLobbies.has(lobbyChannel)) {
        return; // Already subscribed
      }
      
      redisService.subscribe(lobbyChannel, (message: string) => {
        try {
          const data = JSON.parse(message);
          // Only process messages from other servers
          if (data.serverId !== this.serverId) {
            logger.info(`Received lobby event from another server for lobby ${lobbyCode}: ${data.payload.type}`);
            // Forward the event to local subscribers
            this.eventEmitter.emit(`lobby-${lobbyCode}`, JSON.stringify(data.payload));
          }
        } catch (err) {
          logger.error(`Error processing Redis message for lobby ${lobbyCode}:`, err);
        }
      });
      
      subscribedLobbies.add(lobbyChannel);
      logger.info(`Subscribed to Redis channel for lobby ${lobbyCode}`);
    };
    
    // Subscribe to existing lobbies if any
    Object.keys(this.clients).forEach(lobbyCode => {
      subscribeLobbyChannel(lobbyCode);
    });
    
    // Expose method to subscribe to new lobbies
    this.subscribeLobbyChannel = subscribeLobbyChannel;
  }
  
  private subscribeLobbyChannel: (lobbyCode: string) => void = () => {};

  // Global events
  public addGlobalClient(res: Response): () => void {
    this.lobbyClients.push(res);
    logger.info(`Client connected to global events. Total clients: ${this.lobbyClients.length}`);

    return () => {
      const index = this.lobbyClients.indexOf(res);
      if (index !== -1) {
        this.lobbyClients.splice(index, 1);
        logger.info(
          `Client removed from global events. Total clients: ${this.lobbyClients.length}`
        );
      }
    };
  }

  /**
   * Broadcasts an event to all global event subscribers across all servers
   * @param eventData Event data to broadcast
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  public emitGlobalEvent(eventData: any): void {
    logger.info(`Emitting global event: ${JSON.stringify(eventData.type || 'unknown')}`);
    
    // Wrap the event data with server ID
    const wrappedData = {
      serverId: this.serverId,
      payload: eventData
    };
    
    // Publish to Redis for other servers
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    redisService.publish(GLOBAL_EVENTS_CHANNEL, JSON.stringify(wrappedData)).catch((err: any) => logger.error('Failed to publish to Redis:', err));
    
    // Emit locally
    this.eventEmitter.emit('global-lobbies', JSON.stringify(eventData));
  }

  public onGlobalEvent(callback: (event: string) => void): () => void {
    this.eventEmitter.on('global-lobbies', callback);
    return () => this.eventEmitter.removeListener('global-lobbies', callback);
  }

  // Lobby-specific events
  public addLobbyClient(lobbyCode: string, res: Response): () => void {
    if (!this.clients[lobbyCode]) {
      this.clients[lobbyCode] = [];
      // Subscribe to Redis channel for this lobby
      this.subscribeLobbyChannel(lobbyCode);
    }

    this.clients[lobbyCode].push(res);
    logger.info(
      `Client connected to lobby ${lobbyCode}. Total clients: ${this.clients[lobbyCode].length}`
    );

    return () => {
      if (this.clients[lobbyCode]) {
        this.clients[lobbyCode] = this.clients[lobbyCode].filter((client) => client !== res);
        logger.info(
          `Client removed from lobby ${lobbyCode}. Total clients: ${this.clients[lobbyCode].length || 0}`
        );
      }
    };
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  public emitLobbyEvent(lobbyCode: string, eventData: any): void {
    logger.info(
      `Emitting event to lobby ${lobbyCode}: ${JSON.stringify(eventData.type || 'unknown')}`
    );
    
    // Wrap the event data with server ID
    const wrappedData = {
      serverId: this.serverId,
      payload: eventData
    };
    
    // Publish to Redis for other servers
    const lobbyChannel = `lobby-events-${lobbyCode}`;

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    redisService.publish(lobbyChannel, JSON.stringify(wrappedData)).catch((err: any) => logger.error(`Failed to publish to Redis for lobby ${lobbyCode}:`, err));
    
    // Emit locally
    this.eventEmitter.emit(`lobby-${lobbyCode}`, JSON.stringify(eventData));
  }

  public onLobbyEvent(lobbyCode: string, callback: (event: string) => void): () => void {
    const eventName = `lobby-${lobbyCode}`;
    this.eventEmitter.on(eventName, callback);
    return () => this.eventEmitter.removeListener(eventName, callback);
  }

  // Access to internal state (if needed)
  public getLobbyClientCount(lobbyCode?: string): number {
    if (lobbyCode && this.clients[lobbyCode]) {
      return this.clients[lobbyCode].length;
    }
    return this.lobbyClients.length;
  }
}

// Create a singleton instance
const eventService = new EventService();

export default eventService;
