import {
  WebSocketGateway,
  WebSocketServer,
  OnGatewayInit,
  OnGatewayConnection,
  OnGatewayDisconnect,
  SubscribeMessage,
  MessageBody,
} from '@nestjs/websockets';
import { Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { Server, Socket } from 'socket.io';
import Redis from 'ioredis';
import { redisClient } from '../redis/redis.provider';

/** Cross-instance socket relay so notifications reach browsers on any API process. */
export const SOCKET_EMIT_CHANNEL = 'connector:socket:emit';

@WebSocketGateway({
  cors: {
    origin: process.env.SOCKET_URL,
    credentials: true,
  },
})
export class SocketGateway
  implements
  OnGatewayInit,
  OnGatewayConnection,
  OnGatewayDisconnect,
  OnModuleInit,
  OnModuleDestroy {
  @WebSocketServer()
  server: Server;

  private readonly logger = new Logger(SocketGateway.name);

  /** Shared across duplicate SocketGateway providers (API + modules). */
  private static activeServer: Server | null = null;
  private static subscriber: Redis | null = null;
  private static relayStarted = false;

  afterInit(server: Server) {
    SocketGateway.activeServer = server;
  }

  async onModuleInit() {
    if (SocketGateway.relayStarted) {
      return;
    }
    SocketGateway.relayStarted = true;

    try {
      SocketGateway.subscriber = redisClient.duplicate();
      await SocketGateway.subscriber.subscribe(SOCKET_EMIT_CHANNEL);
      SocketGateway.subscriber.on('message', (channel, raw) => {
        if (channel !== SOCKET_EMIT_CHANNEL) {
          return;
        }
        const io = SocketGateway.activeServer;
        if (!io) {
          return;
        }
        try {
          const { id, data } = JSON.parse(raw);
          io.to(String(id)).emit('data', data);
        } catch (error: any) {
          console.warn(`Failed to relay socket event: ${error?.message || error}`);
        }
      });
      this.logger.log(`Listening for socket relay on Redis "${SOCKET_EMIT_CHANNEL}"`);
    } catch (error: any) {
      SocketGateway.relayStarted = false;
      this.logger.error(`Socket Redis subscribe failed: ${error?.message || error}`);
    }
  }

  async onModuleDestroy() {
    if (!SocketGateway.subscriber) {
      return;
    }
    try {
      await SocketGateway.subscriber.unsubscribe(SOCKET_EMIT_CHANNEL);
      await SocketGateway.subscriber.quit();
    } catch {
      // ignore shutdown errors
    }
    SocketGateway.subscriber = null;
    SocketGateway.relayStarted = false;
  }

  @SubscribeMessage('message')
  handleMessage(@MessageBody() data: any, id: any): void {
    const roomId = String(id);
    console.log(`📬 Message received on channel ${roomId}:`, data);

    const io = this.server || SocketGateway.activeServer;
    if (io) {
      io.to(roomId).emit('data', data);
      return;
    }

    // No local WebSocket server — publish for another API instance to emit.
    void redisClient
      .publish(SOCKET_EMIT_CHANNEL, JSON.stringify({ id: roomId, data }))
      .catch((error: any) => {
        console.warn(`Socket Redis publish failed: ${error?.message || error}`);
      });
  }

  handleConnection(client: Socket) {
    const userId = client.handshake.query.userId as string;
    if (userId) {
      client.join(userId);
      console.log(`🔌 Client ${client.id} joined room: ${userId}`);
    } else {
      console.warn(`⚠️ No userId provided for client ${client.id}`);
    }
  }

  handleDisconnect(client: Socket) {
    console.log(`❌ Client disconnected: ${client.id}`);
  }
}
