// apps/web-next/server.ts
// SERVER-ONLY entry. Boots Next.js + Socket.IO on the same Node http server.
import "reflect-metadata";
import { createServer } from "node:http";
import next from "next";
import { Server as IOServer } from "socket.io";
import {
  RealtimeHandlerRegistry,
  SocketIORealtimeBroadcaster,
  SocketIORealtimeServer,
  type IRealtimeAuthenticator,
} from "@repo/core-realtime";
import { SESSION_COOKIE } from "@repo/auth";
import { authContainer } from "@repo/auth/di/container";
import { AUTH_SYMBOLS } from "@repo/auth/di/symbols";
import { bindAll } from "./src/server/bind-production.js";

// Real shape of IAuthenticationService.validateSession: returns non-nullable
// on success and throws UnauthenticatedError on missing/invalid sessions.
// Kept as an inline structural type to avoid leaking auth's internal interface.
type AuthService = {
  validateSession: (id: string) => Promise<{ user: { id: string }; session: unknown }>;
};

const dev = process.env.NODE_ENV !== "production";
const port = Number(process.env.PORT ?? 3000);

const app = next({ dev });
const handle = app.getRequestHandler();

await app.prepare();

const httpServer = createServer((req, res) => handle(req, res));
const io = new IOServer(httpServer);

const broadcaster = new SocketIORealtimeBroadcaster(io);
const registry = new RealtimeHandlerRegistry();

await bindAll({ realtime: broadcaster, realtimeRegistry: registry });

const authenticator: IRealtimeAuthenticator = {
  authenticate: async ({ cookies }) => {
    const sessionId = cookies[SESSION_COOKIE];
    if (!sessionId) return null;
    const authService = authContainer.get<AuthService>(AUTH_SYMBOLS.IAuthenticationService);
    try {
      const { user } = await authService.validateSession(sessionId);
      // Roles are not yet in the session shape; extend here when DB-backed roles ship.
      return { userId: user.id, roles: (user as { roles?: string[] }).roles ?? [] };
    } catch {
      // Invalid/expired session → reject the connection. Real auth-service errors
      // (DB outages etc.) intentionally collapse to "unauthenticated" here too,
      // which is the conservative choice for a public-facing socket.
      return null;
    }
  },
};

const realtimeServer = new SocketIORealtimeServer({
  httpServer,
  io,
  authenticator,
  registry,
});
await realtimeServer.start();

httpServer.listen(port, () => {
  console.log(`> Ready on http://localhost:${port}`);
});
