import { Server as HttpServer } from "http";
import { Server as HttpsServer } from "https";
import { Server, Socket } from "socket.io";
import jwt from "jsonwebtoken";
import AuthConfig from "../config/authConfig";
import { jwtPayload } from "../interfaces/auth";
import { registerChatSocketHandlers } from "./chatSocket";
import { setIO } from "./presence";

export type AppHttpServer = HttpServer | HttpsServer;
export { getIO, isUserInConversation } from "./presence";

function extractToken(socket: Socket): string | null {
  const authToken = socket.handshake.auth?.token;
  if (typeof authToken === "string" && authToken.trim()) {
    return authToken.trim();
  }

  const header = socket.handshake.headers.authorization;
  if (typeof header === "string" && header.startsWith("Bearer ")) {
    return header.slice(7).trim();
  }

  const queryToken = socket.handshake.query?.token;
  if (typeof queryToken === "string" && queryToken.trim()) {
    return queryToken.trim();
  }

  return null;
}

/**
 * Attach Socket.IO to the Node HTTP(S) server (chat DMs).
 */
export function initSockets(httpServer: AppHttpServer): Server {
  const io = new Server(httpServer, {
    cors: {
      origin: true,
      credentials: true,
    },
    path: "/socket.io",
  });
  setIO(io);

  io.use((socket, next) => {
    try {
      const token = extractToken(socket);
      if (!token) {
        return next(new Error("Unauthorized"));
      }

      jwt.verify(token, String(AuthConfig.JWT_SECRET), (err, decoded) => {
        if (err || !decoded) {
          return next(new Error("Invalid Token"));
        }
        const payload = decoded as jwtPayload;
        if (!payload?.id) {
          return next(new Error("Invalid Token"));
        }
        socket.data.userId = String(payload.id);
        next();
      });
    } catch {
      next(new Error("Unauthorized"));
    }
  });

  io.on("connection", (socket) => {
    const userId = String(socket.data.userId ?? "");
    if (!userId) {
      socket.disconnect(true);
      return;
    }
    console.log(`[socket] connected userId=${userId.slice(0, 8)}…`);
    registerChatSocketHandlers(io, socket as Socket & { data: { userId: string } });

    socket.on("disconnect", (reason) => {
      console.log(
        `[socket] disconnected userId=${userId.slice(0, 8)}… reason=${reason}`,
      );
    });
  });

  return io;
}
