// src/inquiry-notifications/inquiry-notifications.gateway.ts

import {
  WebSocketGateway,
  WebSocketServer,
  OnGatewayConnection,
  OnGatewayDisconnect,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { InquiryNotificationsService } from './inquiry-notifications.service';

interface AuthenticatedSocket extends Socket {
  userId?: number;
}

@Injectable()
@WebSocketGateway({
  cors: {
    origin: '*', // ⚠️ In production, set specific origins
    credentials: true,
  },
  namespace: '/inquiry-notifications',
  path: '/socket.io',
})
export class InquiryNotificationsGateway
  implements OnGatewayConnection, OnGatewayDisconnect, OnModuleInit
{
  @WebSocketServer()
  server!: Server;

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

  // Map of userId -> Set<socketId> (a user can have multiple tabs open)
  private readonly userSockets = new Map<number, Set<string>>();

  constructor(
    private readonly notificationsService: InquiryNotificationsService,
  ) {}

  // ── Subscribe to backend events on startup ─────────────────────
  onModuleInit() {
    // Listen to ALL emitted notifications and broadcast to relevant users
    (this.notificationsService as any).events$.subscribe((event: any) => {
      this.broadcastToUser(event.targetUserId, 'notification', event);
      this.logger.log(
        `📡 WS Broadcast: type=${event.type} → user ${event.targetUserId}`,
      );
    });
  }

  // ── User connects ──────────────────────────────────────────────
  handleConnection(@ConnectedSocket() socket: AuthenticatedSocket) {
    try {
      // Extract user ID from JWT in handshake (sent during connect)
      const token = (socket.handshake.auth?.token ||
        socket.handshake.query?.token) as string;

      if (!token) {
        this.logger.warn(`Socket ${socket.id} connected WITHOUT token`);
        socket.disconnect();
        return;
      }

      const userId = this.extractUserIdFromToken(token);
      if (!userId) {
        this.logger.warn(`Socket ${socket.id} invalid token`);
        socket.disconnect();
        return;
      }

      // Store the connection
      socket.userId = userId;
      if (!this.userSockets.has(userId)) {
        this.userSockets.set(userId, new Set());
      }
      this.userSockets.get(userId)!.add(socket.id);

      this.logger.log(
        `✅ User ${userId} connected (socket: ${socket.id}, total connections: ${this.userSockets.get(userId)!.size})`,
      );

      // Send welcome message
      socket.emit('connected', {
        message: 'Real-time notifications active',
        userId,
        connectedAt: new Date().toISOString(),
      });

      // Send unread count immediately
      this.notificationsService
        .getUnreadCount(userId)
        .then((count) => socket.emit('unread-count', { count }))
        .catch(() => {});
    } catch (err) {
      this.logger.error(`Connection error: ${(err as Error).message}`);
      socket.disconnect();
    }
  }

  // ── User disconnects ──────────────────────────────────────────
  handleDisconnect(@ConnectedSocket() socket: AuthenticatedSocket) {
    if (!socket.userId) return;

    const sockets = this.userSockets.get(socket.userId);
    if (sockets) {
      sockets.delete(socket.id);
      if (sockets.size === 0) {
        this.userSockets.delete(socket.userId);
      }
    }

    this.logger.log(
      `❌ User ${socket.userId} disconnected (socket: ${socket.id})`,
    );
  }

  // ── Client requests current unread count ──────────────────────
  @SubscribeMessage('get-unread-count')
  async handleGetUnreadCount(@ConnectedSocket() socket: AuthenticatedSocket) {
    if (!socket.userId) return { count: 0 };
    const count = await this.notificationsService.getUnreadCount(socket.userId);
    socket.emit('unread-count', { count });
    return { count };
  }

  // ── Client marks notification as read ─────────────────────────
  @SubscribeMessage('mark-read')
  async handleMarkRead(
    @ConnectedSocket() socket: AuthenticatedSocket,
    @MessageBody() data: { id: number },
  ) {
    if (!socket.userId || !data?.id) return { success: false };
    await this.notificationsService.markAsRead(data.id, socket.userId);
    const count = await this.notificationsService.getUnreadCount(socket.userId);
    socket.emit('unread-count', { count });
    return { success: true };
  }

  // ── Client marks all as read ──────────────────────────────────
  @SubscribeMessage('mark-all-read')
  async handleMarkAllRead(@ConnectedSocket() socket: AuthenticatedSocket) {
    if (!socket.userId) return { success: false };
    await this.notificationsService.markAllAsRead(socket.userId);
    socket.emit('unread-count', { count: 0 });
    return { success: true };
  }

  // ── Heartbeat / ping ──────────────────────────────────────────
  @SubscribeMessage('ping')
  handlePing() {
    return { pong: Date.now() };
  }

  // ═══════════════════════════════════════════════════════════════
  // Public methods to broadcast events
  // ═══════════════════════════════════════════════════════════════

  // Send to a specific user (all their connections)
  broadcastToUser(userId: number, event: string, data: any) {
    const sockets = this.userSockets.get(userId);
    if (!sockets || sockets.size === 0) {
      this.logger.debug(`User ${userId} not connected — skipping WS push`);
      return;
    }

    sockets.forEach((socketId) => {
      this.server.to(socketId).emit(event, data);
    });
  }

  // Broadcast to ALL connected users (e.g., system-wide announcements)
  broadcastToAll(event: string, data: any) {
    this.server.emit(event, data);
  }

  // Broadcast to all users with a specific role (must pass role-specific user IDs)
  broadcastToUsers(userIds: number[], event: string, data: any) {
    userIds.forEach((id) => this.broadcastToUser(id, event, data));
  }

  // Get count of currently connected users
  getConnectedUsersCount(): number {
    return this.userSockets.size;
  }

  // Check if a specific user is online
  isUserOnline(userId: number): boolean {
    return this.userSockets.has(userId);
  }

  // ── Extract userId from JWT token ─────────────────────────────
  private extractUserIdFromToken(token: string): number | null {
    try {
      const parts = token.split('.');
      if (parts.length !== 3) return null;
      const payload = JSON.parse(
        Buffer.from(parts[1], 'base64').toString('utf-8'),
      );
      const id = payload?.sub ?? payload?.id ?? payload?.userId;
      return id ? Number(id) : null;
    } catch {
      return null;
    }
  }
}