import { Namespace } from "socket.io";
import { io, prisma } from "../../constants";
import { sessions } from "../../managers/sessions";
import { socketAuthMiddleware } from "./middleware/authorization";
import { registerLiveQuotePreviewHandlers } from "./events/liveQuotePreview";

const SESSION_ENFORCEMENT_INTERVAL_MS = 60 * 1000;
const MAX_TIMEOUT_MS = 2_147_483_647;

const AUTH_HEADER_REGEX =
  /^(Bearer|Key)\s([a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+)$/;

const resolveAuthorization = (handshake: {
  auth?: Record<string, unknown>;
  headers?: Record<string, unknown>;
}): string | null => {
  const headerAuth = handshake.headers?.authorization;
  if (typeof headerAuth === "string" && headerAuth.length > 0)
    return headerAuth;

  const authAuthorization = handshake.auth?.authorization;
  if (typeof authAuthorization === "string" && authAuthorization.length > 0)
    return authAuthorization;

  const authToken = handshake.auth?.token;
  if (typeof authToken === "string" && authToken.length > 0)
    return `Bearer ${authToken}`;

  return null;
};

export const setupSecureNamespace = (): Namespace => {
  const secureNamespace = io.of("/secure");

  secureNamespace.use(async (socket, next) => {
    try {
      const authorization = resolveAuthorization(socket.handshake as any);
      if (!authorization)
        return next(new Error("Unauthorized: missing authorization"));

      const components = authorization.match(AUTH_HEADER_REGEX);
      if (!components)
        return next(new Error("Unauthorized: invalid authorization format"));

      const authValue = components[2] ?? "";
      const session = await sessions.fetch({ accessToken: authValue });
      const user = await session.fetchUser();

      socket.data.user = user;
      socket.data.session = session;

      return next();
    } catch {
      return next(new Error("Unauthorized"));
    }
  });

  secureNamespace.use(socketAuthMiddleware());

  secureNamespace.on("connection", (socket) => {
    const sessionId = socket.data.session?.id as string | undefined;
    const sessionExpiresAt = socket.data.session?.expires
      ? new Date(socket.data.session.expires).getTime()
      : null;

    const disconnectForSession = () => {
      if (socket.disconnected) return;
      socket.emit("secure:session:expired");
      socket.disconnect(true);
    };

    let expiryTimeout: ReturnType<typeof setTimeout> | null = null;

    const scheduleExpiryDisconnect = () => {
      if (sessionExpiresAt === null) return;

      const remainingMs = sessionExpiresAt - Date.now();
      if (remainingMs <= 0) {
        disconnectForSession();
        return;
      }

      const delayMs = Math.min(remainingMs, MAX_TIMEOUT_MS);
      expiryTimeout = setTimeout(scheduleExpiryDisconnect, delayMs);
    };

    scheduleExpiryDisconnect();

    const interval = setInterval(async () => {
      if (!sessionId) {
        disconnectForSession();
        return;
      }

      const session = await prisma.session.findFirst({
        where: { id: sessionId },
        select: { id: true, expires: true, invalidatedAt: true },
      });

      if (!session) {
        disconnectForSession();
        return;
      }

      if (session.invalidatedAt) {
        disconnectForSession();
        return;
      }

      if (session.expires.getTime() <= Date.now()) {
        disconnectForSession();
      }
    }, SESSION_ENFORCEMENT_INTERVAL_MS);

    socket.on("disconnect", () => {
      clearInterval(interval);
      if (expiryTimeout) clearTimeout(expiryTimeout);
    });

    registerLiveQuotePreviewHandlers(socket);

    socket.emit("secure:connected", {
      userId: socket.data.user?.id ?? null,
    });
  });

  return secureNamespace;
};
