import { createServer } from "http";
import { Server as SocketIOServer } from "socket.io";
import type { IncomingMessage, ServerResponse } from "http";
import app, { sessionMiddleware } from "./app";
import { logger } from "./lib/logger";
import { db } from "@workspace/db";
import {
  conversationParticipantsTable,
} from "@workspace/db";
import { eq, and } from "drizzle-orm";

const rawPort = process.env["PORT"];

if (!rawPort) {
  throw new Error(
    "PORT environment variable is required but was not provided.",
  );
}

const port = Number(rawPort);

if (Number.isNaN(port) || port <= 0) {
  throw new Error(`Invalid PORT value: "${rawPort}"`);
}

const httpServer = createServer(app);

export const io = new SocketIOServer(httpServer, {
  path: "/api/socket.io",
  cors: {
    origin: process.env.ALLOWED_ORIGINS
      ? process.env.ALLOWED_ORIGINS.split(",").map((o) => o.trim())
      : true,
    methods: ["GET", "POST"],
    credentials: true,
  },
});

io.engine.use(
  (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => {
    sessionMiddleware(req as Parameters<typeof sessionMiddleware>[0], res as Parameters<typeof sessionMiddleware>[1], next);
  },
);

io.use((socket, next) => {
  const req = socket.request as IncomingMessage & { session?: { userId?: number } };
  const userId = req.session?.userId;
  if (!userId) {
    return next(new Error("Unauthorized: no session"));
  }
  socket.data.userId = userId;
  next();
});

io.on("connection", (socket) => {
  const userId = socket.data.userId as number;
  logger.info({ socketId: socket.id, userId }, "Socket connected");

  socket.join(`user:${userId}`);

  socket.on(
    "join_conversation",
    async (conversationId: number) => {
      const [participant] = await db
        .select()
        .from(conversationParticipantsTable)
        .where(
          and(
            eq(conversationParticipantsTable.conversationId, conversationId),
            eq(conversationParticipantsTable.userId, userId),
          ),
        );

      if (participant) {
        socket.join(`conversation:${conversationId}`);
      }
    },
  );

  socket.on("leave_conversation", (conversationId: number) => {
    socket.leave(`conversation:${conversationId}`);
  });

  socket.on("disconnect", () => {
    logger.info({ socketId: socket.id }, "Socket disconnected");
  });
});

httpServer.listen(port, () => {
  logger.info({ port }, "Server listening");
});
