import jwt from "jsonwebtoken";
import { Server } from "socket.io";
import { PrismaClient } from "../generated/client";
import { AuthModeService } from "../auth/authMode";
import { ACCESS_TOKEN_COOKIE_NAME, parseCookieHeader } from "../auth/cookies";
import { isAtLeastRole, resolveDrawingAccess } from "./drawingAccess";
import { CollabSessionManager } from "./collabSession";

interface User {
  id: string;
  name: string;
  initials: string;
  color: string;
  socketId: string;
  isActive: boolean;
}

type RegisterSocketHandlersDeps = {
  io: Server;
  prisma: PrismaClient;
  authModeService: AuthModeService;
  jwtSecret: string;
  collabSessionManager: CollabSessionManager;
};

type CursorGuardState = {
  lastAt: number;
  lastSignature: string;
};

const CURSOR_MIN_INTERVAL_MS = 45;
const CURSOR_DUPLICATE_WINDOW_MS = 180;

const toCursorSignature = (
  pointer: { x: number; y: number },
  button: string,
  selectedElementIds?: Record<string, boolean>
): string => {
  const roundedX = Math.round(pointer.x * 10) / 10;
  const roundedY = Math.round(pointer.y * 10) / 10;
  const selectionKeys = Object.keys(selectedElementIds || {}).sort();
  return `${roundedX},${roundedY}|${button}|${selectionKeys.join(",")}`;
};

export const registerSocketHandlers = ({
  io,
  prisma,
  authModeService,
  jwtSecret,
  collabSessionManager,
}: RegisterSocketHandlersDeps) => {
  const roomUsers = new Map<string, User[]>();
  const socketUserMap = new Map<string, string>();

  const toPresenceName = (value: unknown): string => {
    if (typeof value !== "string") return "User";
    const trimmed = value.trim().slice(0, 120);
    return trimmed.length > 0 ? trimmed : "User";
  };

  const toPresenceInitials = (name: string): string => {
    const words = name
      .split(/\s+/)
      .map((part) => part.trim())
      .filter((part) => part.length > 0);
    if (words.length === 0) return "U";
    const first = words[0]?.[0] ?? "";
    const second = words.length > 1 ? words[1]?.[0] ?? "" : "";
    const initials = `${first}${second}`.toUpperCase().slice(0, 2);
    return initials.length > 0 ? initials : "U";
  };

  const toPresenceColor = (value: unknown): string => {
    if (typeof value !== "string") return "#4f46e5";
    const trimmed = value.trim();
    if (/^#[0-9a-fA-F]{3,8}$/.test(trimmed)) {
      return trimmed;
    }
    return "#4f46e5";
  };

  const getSocketAuthUserId = async (token?: string): Promise<string | null> => {
    const authEnabled = await authModeService.getAuthEnabled();
    if (!authEnabled) {
      return "bootstrap-admin";
    }

    if (!token) return null;

    try {
      const decoded = jwt.verify(token, jwtSecret) as Record<string, unknown>;
      if (
        typeof decoded.userId !== "string" ||
        typeof decoded.email !== "string" ||
        decoded.type !== "access"
      ) {
        return null;
      }

      const user = await prisma.user.findUnique({
        where: { id: decoded.userId },
        select: { id: true, isActive: true },
      });

      if (!user || !user.isActive) return null;
      return user.id;
    } catch {
      return null;
    }
  };

  io.use(async (socket, next) => {
    try {
      const tokenFromAuth = socket.handshake.auth?.token as string | undefined;
      const tokenFromCookie = (() => {
        const cookies = parseCookieHeader(socket.handshake.headers.cookie);
        const value = cookies[ACCESS_TOKEN_COOKIE_NAME];
        return typeof value === "string" && value.trim().length > 0 ? value : undefined;
      })();
      const token = tokenFromAuth || tokenFromCookie;
      const userId = await getSocketAuthUserId(token);

      if (!userId) {
        return next(new Error("Authentication required"));
      }

      socketUserMap.set(socket.id, userId);
      next();
    } catch {
      next(new Error("Authentication failed"));
    }
  });

  io.on("connection", (socket) => {
    const authenticatedUserId = socketUserMap.get(socket.id);
    const authorizedDrawingRoles = new Map<string, "owner" | "editor" | "viewer">();
    const cursorGuardByDrawing = new Map<string, CursorGuardState>();

    socket.on(
      "join-room",
      async ({
        drawingId,
        user,
      }: {
        drawingId: string;
        user: Omit<User, "socketId" | "isActive">;
      }) => {
        try {
          if (authenticatedUserId) {
            const drawing = await resolveDrawingAccess({
              prisma,
              drawingId,
              userId: authenticatedUserId,
            });

            if (!drawing) {
              socket.emit("error", { message: "You do not have access to this drawing" });
              return;
            }

            authorizedDrawingRoles.set(drawingId, drawing.role);
          }

          const roomId = `drawing_${drawingId}`;
          socket.join(roomId);
          await collabSessionManager.joinSession(drawingId, socket.id);

          let trustedUserId =
            typeof user?.id === "string" && user.id.trim().length > 0
              ? user.id.trim().slice(0, 200)
              : socket.id;
          let trustedName = toPresenceName(user?.name);

          if (authenticatedUserId && authenticatedUserId !== "bootstrap-admin") {
            const account = await prisma.user.findUnique({
              where: { id: authenticatedUserId },
              select: { id: true, name: true },
            });
            if (account) {
              trustedUserId = account.id;
              trustedName = toPresenceName(account.name);
            }
          }

          const newUser: User = {
            id: trustedUserId,
            name: trustedName,
            initials: toPresenceInitials(trustedName),
            color: toPresenceColor(user?.color),
            socketId: socket.id,
            isActive: true,
          };

          const currentUsers = roomUsers.get(roomId) || [];
          const filteredUsers = currentUsers.filter((u) => u.id !== newUser.id);
          filteredUsers.push(newUser);
          roomUsers.set(roomId, filteredUsers);

          io.to(roomId).emit("presence-update", filteredUsers);

          const snapshot = await collabSessionManager.getSceneSnapshot(drawingId);
          if (snapshot) {
            socket.emit("scene-snapshot", snapshot);
          }
        } catch (err) {
          console.error("Error in join-room handler:", err);
          socket.emit("error", { message: "Failed to join room" });
        }
      }
    );

    socket.on("cursor-move", (data) => {
      const drawingId = typeof data?.drawingId === "string" ? data.drawingId : null;
      if (!drawingId || !authorizedDrawingRoles.has(drawingId)) {
        return;
      }
      const pointerX = Number(data?.pointer?.x);
      const pointerY = Number(data?.pointer?.y);
      if (!Number.isFinite(pointerX) || !Number.isFinite(pointerY)) {
        return;
      }
      const pointer = { x: pointerX, y: pointerY };
      const button = typeof data?.button === "string" ? data.button : "up";
      const selectedElementIds =
        data?.selectedElementIds && typeof data.selectedElementIds === "object"
          ? (data.selectedElementIds as Record<string, boolean>)
          : undefined;
      const signature = toCursorSignature(pointer, button, selectedElementIds);
      const now = Date.now();
      const guardState = cursorGuardByDrawing.get(drawingId);
      if (guardState) {
        if (now - guardState.lastAt < CURSOR_MIN_INTERVAL_MS) {
          return;
        }
        if (
          signature === guardState.lastSignature &&
          now - guardState.lastAt < CURSOR_DUPLICATE_WINDOW_MS
        ) {
          return;
        }
      }
      cursorGuardByDrawing.set(drawingId, { lastAt: now, lastSignature: signature });

      const payload: Record<string, unknown> = {
        drawingId,
        pointer,
        button,
        userId: data?.userId,
        username: data?.username,
        color: data?.color,
      };
      if (selectedElementIds) {
        payload.selectedElementIds = selectedElementIds;
      }
      const roomId = `drawing_${drawingId}`;
      socket.volatile.to(roomId).emit("cursor-move", payload);
    });

    socket.on("element-update", (data) => {
      const drawingId = typeof data?.drawingId === "string" ? data.drawingId : null;
      if (!drawingId) {
        return;
      }
      const role = authorizedDrawingRoles.get(drawingId);
      if (!role || !isAtLeastRole(role, "editor")) {
        return;
      }
      const roomId = `drawing_${drawingId}`;
      socket.to(roomId).emit("element-update", data);
    });

    socket.on("scene-op", async (data) => {
      const drawingId = typeof data?.drawingId === "string" ? data.drawingId : null;
      if (!drawingId) return;

      const role = authorizedDrawingRoles.get(drawingId);
      if (!role || !isAtLeastRole(role, "editor")) {
        return;
      }

      const baseSeq = typeof data?.baseSeq === "number" ? data.baseSeq : 0;
      const clientOpId = typeof data?.clientOpId === "string" ? data.clientOpId : undefined;
      const incomingOps = Array.isArray(data?.ops) ? data.ops : [];
      if (incomingOps.length === 0) return;

      const applied = await collabSessionManager.applyOps({
        drawingId,
        clientOpId,
        ops: incomingOps,
      });
      if (!applied) return;

      const roomId = `drawing_${drawingId}`;
      io.to(roomId).emit("scene-op-applied", {
        drawingId,
        seq: applied.seq,
        baseSeq,
        ops: incomingOps,
        authorUserId: authenticatedUserId ?? socket.id,
        clientOpId,
      });
    });

    socket.on("scene-flush", async (data) => {
      const drawingId = typeof data?.drawingId === "string" ? data.drawingId : null;
      if (!drawingId) return;
      if (!authorizedDrawingRoles.has(drawingId)) return;
      await collabSessionManager.flushSession(drawingId);
    });

    socket.on(
      "user-activity",
      ({ drawingId, isActive }: { drawingId: string; isActive: boolean }) => {
        if (!authorizedDrawingRoles.has(drawingId)) {
          return;
        }
        const roomId = `drawing_${drawingId}`;
        const users = roomUsers.get(roomId);
        if (users) {
          const user = users.find((u) => u.socketId === socket.id);
          if (user) {
            user.isActive = isActive;
            io.to(roomId).emit("presence-update", users);
          }
        }
      }
    );

    socket.on("disconnect", () => {
      socketUserMap.delete(socket.id);
      for (const drawingId of authorizedDrawingRoles.keys()) {
        void collabSessionManager.leaveSession(drawingId, socket.id);
      }
      roomUsers.forEach((users, roomId) => {
        const index = users.findIndex((u) => u.socketId === socket.id);
        if (index !== -1) {
          users.splice(index, 1);
          roomUsers.set(roomId, users);
          io.to(roomId).emit("presence-update", users);
        }
      });
    });
  });
};
