import { Server as HttpServer } from 'http';
import { Server } from 'socket.io';

import config from '@/shared/config/config';
import { verifyToken } from '@/modules/token/token.service';
import { getUserRoom } from './socket.helpers';

let io: Server | null = null;

/**
 * Extract Bearer token from Socket.IO handshake (same as API: Authorization Bearer <token>).
 * Supports handshake.auth.token and handshake.headers.authorization.
 */
function extractTokenFromHandshake(handshake: {
  auth?: { token?: string };
  headers?: { authorization?: string };
}): string | null {
  const authToken = handshake.auth?.token?.trim();
  if (authToken) {
    const stripped = authToken.replace(/^Bearer\s+/i, '');
    return stripped || null;
  }
  const authHeader = handshake.headers?.authorization?.trim();
  if (authHeader?.toLowerCase().startsWith('bearer ')) {
    return authHeader.slice(7).trim() || null;
  }
  return null;
}

/**
 * Initialize Socket.IO and attach to the HTTP server.
 * Uses Bearer token validation (same as API) and joins each socket to user:<userId> room.
 */
export function initSocket(httpServer: HttpServer): void {
  if (io) {
    return;
  }

  io = new Server(httpServer, {
    path: '/socket.io',
    cors: {
      origin:
        config.env === 'development'
          ? true
          : config.corsOrigin?.split(',').map((o) => o.trim()) || true,
      credentials: true,
    },
  });

  // Auth middleware: verify Bearer token before connection is established
  io.use(async (socket, next) => {
    try {
      const token = extractTokenFromHandshake(socket.handshake);
      if (!token) {
        return next(new Error('Unauthorized'));
      }
      const payload = await verifyToken(token);
      socket.data.userId = payload.sub;
      next();
    } catch {
      next(new Error('Unauthorized'));
    }
  });

  io.on('connection', (socket) => {
    const userId = socket.data.userId as string | undefined;
    if (userId) {
      socket.join(getUserRoom(userId));
    }
  });
}

/**
 * Get the Socket.IO server instance. Returns null if initSocket has not been called.
 */
export function getIO(): Server | null {
  return io;
}

/**
 * Emit an event to a specific user's room (user:<userId>).
 * No-op if Socket.IO is not initialized.
 */
export function emitToUser(
  userId: string,
  event: string,
  data: Record<string, unknown>,
): void {
  const server = getIO();
  if (!server) return;
  server.to(getUserRoom(userId)).emit(event, data);
}

export { getUserRoom } from './socket.helpers';
