// src/server/shared/plugins/socket.ts
// Socket.io plugin for real-time notifications with rooms

import { FastifyInstance } from 'fastify';
import { Server as HTTPServer } from 'http';
import { Server as SocketIOServer } from 'socket.io';
import { loggerService } from '../lib/logger';
import { db } from '../db/client';
import { pendingNotifications } from '@features/notifications/db/pending_notifications.table';
import { employees } from '@features/personnel/db/employees.table';
import { eq, and, isNull } from 'drizzle-orm';
import { USER_ROLES } from '@shared/constants/roles';
import { env } from '../lib/env';

// Initialize logger for socket operations
const socketLogger = loggerService.get('SOCKET');

// Socket.io types
interface SocketToServerEvents {
  joinRoom: (roomId: string) => void;
  leaveRoom: (roomId: string) => void;
  markAsRead: (notificationId: string) => void;
}

interface ServerToClientEvents {
  'approval:created': (data: any) => void;
  'approval:resolved': (data: any) => void;
  'system:permissions_updated': (data: { roles: string[] }) => void;
  'blacklist:created': (data: any) => void;
  'blacklist:updated': (data: any) => void;
  'blacklist:deleted': (data: { id: string }) => void;
  'note:created': (data: any) => void;
  'note:updated': (data: { id: string; updates: any }) => void;
  'note:deleted': (data: { id: string }) => void;
  'note:status_changed': (data: any) => void;
  'shift:update': (data: any) => void;
  'session:kill': (data: { reason: string }) => void;
  'session:terminated': (data: { reason: string }) => void;
  'notification:new': (data: any) => void;
  'tasks:created': (data: any) => void;
  'tasks:updated': (data: any) => void;
  'tasks:deleted': (data: { id: string }) => void;
  'tasks:toggled': (data: any) => void;
  'tasks:reordered': (data: any) => void;
}

// Room names
export const ADMIN_ROOM = 'room:admins';
export const MANAGER_ROOM = 'room:managers';

// Helper to get user room name
export function getUserRoom(userId: string): string {
  return `room:user_${userId}`;
}

// Socket.io instance
let io: SocketIOServer<SocketToServerEvents, ServerToClientEvents> | null = null;

// User to socket mapping for targeted notifications
const userSocketMap = new Map<string, Set<string>>();

/**
 * Socket.io plugin for Fastify
 * Creates rooms for admins, managers, and individual users
 * Provides notification methods
 */
export async function socketPlugin(fastify: FastifyInstance) {
  try {
    // Initialize Socket.io server
    io = new SocketIOServer<SocketToServerEvents, ServerToClientEvents>(
      fastify.server as HTTPServer,
      {
        cors: {
          origin: env.corsOrigin,
          credentials: true,
        },
        transports: ['websocket', 'polling'],
      }
    );

    // Connection handler with authentication
    io.on('connection', async (socket) => {
      socketLogger.info(`Socket connected: ${socket.id}`);

      // Authenticate socket using JWT from cookie
      try {
        const cookieHeader = socket.handshake.headers.cookie;
        if (!cookieHeader) {
          socketLogger.warn(`Socket ${socket.id} connected without auth cookie`);
          socket.disconnect();
          return;
        }

        // Parse auth_token from cookie
        const tokenMatch = cookieHeader.match(/auth_token=([^;]+)/);
        if (!tokenMatch) {
          socketLogger.warn(`Socket ${socket.id} connected without auth_token`);
          socket.disconnect();
          return;
        }

        const token = tokenMatch[1];

        // Decode JWT token (base64url decode)
        const payload = token.split('.')[1];
        // Replace base64url characters with base64
        const base64Payload = payload.replace(/-/g, '+').replace(/_/g, '/');
        const decoded = JSON.parse(
          Buffer.from(base64Payload, 'base64').toString('utf-8')
        ) as any;

        if (!decoded || !decoded.id) {
          socketLogger.warn(`Socket ${socket.id} failed JWT verification`);
          socket.disconnect();
          return;
        }

        const userId = decoded.id;

        // Get user data from database to check role
        const userData = await db
          .select({
            id: employees.id,
            role: employees.role,
            isFired: employees.isFired,
          })
          .from(employees)
          .where(eq(employees.id, userId))
          .limit(1);

        if (!userData || userData.length === 0) {
          socketLogger.warn(`Socket ${socket.id} user not found in database`);
          socket.disconnect();
          return;
        }

        const user = userData[0];

        // Check if user is fired
        if (user.isFired) {
          socketLogger.warn(`Socket ${socket.id} user ${userId} is fired, disconnecting`);
          socket.disconnect();
          return;
        }

        // Join user's personal room
        const userRoom = getUserRoom(userId);
        socket.join(userRoom);
        socketLogger.info(`Socket ${socket.id} user ${userId} joined room: ${userRoom}`);

        // Join role-based room if user is admin or manager
        if (user.role === USER_ROLES.ADMIN) {
          socket.join(ADMIN_ROOM);
          socketLogger.info(`Socket ${socket.id} user ${userId} joined admin room: ${ADMIN_ROOM}`);
        } else if (user.role === USER_ROLES.MANAGER) {
          socket.join(MANAGER_ROOM);
          socketLogger.info(`Socket ${socket.id} user ${userId} joined manager room: ${MANAGER_ROOM}`);
        }

        // Store userId in socket data for later use
        (socket as any).userId = userId;
        (socket as any).userRole = user.role;

      } catch (error) {
        socketLogger.error(`Socket ${socket.id} authentication error:`, error);
        socket.disconnect();
        return;
      }

      // Handle room join (manual)
      socket.on('joinRoom', (roomId: string) => {
        socket.join(roomId);
        socketLogger.info(`Socket ${socket.id} joined room: ${roomId}`);
      });

      // Handle room leave
      socket.on('leaveRoom', (roomId: string) => {
        socket.leave(roomId);
        socketLogger.info(`Socket ${socket.id} left room: ${roomId}`);
      });

      // Handle mark as read
      socket.on('markAsRead', async (notificationId: string) => {
        try {
          await db
            .update(pendingNotifications)
            .set({ readAt: new Date() })
            .where(eq(pendingNotifications.id, notificationId));
          socketLogger.info(`Notification ${notificationId} marked as read`);
        } catch (error) {
          socketLogger.error('Error marking notification as read:', error);
        }
      });

      // Disconnect handler
      socket.on('disconnect', (reason) => {
        const userId = (socket as any).userId;
        socketLogger.info(`Socket disconnected: ${socket.id}, userId: ${userId}, reason: ${reason}`);

        // Remove socket from user mapping
        for (const [uid, sockets] of userSocketMap.entries()) {
          sockets.delete(socket.id);
          if (sockets.size === 0) {
            userSocketMap.delete(uid);
          }
        }
      });
    });

    // Expose io instance to fastify for use in routes
    fastify.decorate('io', io);

    socketLogger.info('Socket.io plugin initialized successfully');
  } catch (error) {
    socketLogger.error('Failed to initialize Socket.io plugin:', error);
    throw error;
  }
}

/**
 * Send notification to specific room
 * @param roomId - Room name (room:admins, room:managers, room:user_{id})
 * @param event - Event name
 * @param data - Event data
 */
export async function notifyRoom(
  roomId: string,
  event: keyof ServerToClientEvents,
  data: any
) {
  if (!io) {
    socketLogger.error(`[CRITICAL] Socket.io not initialized!`);
    return;
  }
  const clients = await io.in(roomId).fetchSockets();
  socketLogger.debug(`Attempting send to ${roomId}, event=${event}. Clients online: ${clients.length}`);

  io.to(roomId).emit(event, data);
}

/**
 * Send notification to admins
 */
export async function notifyAdmins(event: keyof ServerToClientEvents, data: any) {
  await notifyRoom(ADMIN_ROOM, event, data);
}

/**
 * Send notification to managers
 */
export async function notifyManagers(event: keyof ServerToClientEvents, data: any) {
  await notifyRoom(MANAGER_ROOM, event, data);
}

/**
 * Send notification to specific user
 */
export async function notifyUser(
  userId: string,
  event: keyof ServerToClientEvents,
  data: any
) {
  const roomId = getUserRoom(userId);
  await notifyRoom(roomId, event, data);
}

/**
 * Terminate user session
 * @param userId - User ID to terminate
 * @param reason - Termination reason
 */
export function terminateUserSession(userId: string, reason: string) {
  const roomId = getUserRoom(userId);
  notifyRoom(roomId, 'session:kill', { reason });
}

// Type declaration for fastify
declare module 'fastify' {
  interface FastifyInstance {
    io: SocketIOServer<SocketToServerEvents, ServerToClientEvents>;
  }
}
