"use client";

import { useEffect, useRef, useCallback, useState } from "react";
import { io, Socket } from "socket.io-client";
import { useQueryClient } from "@tanstack/react-query";
import { useMessengerContext } from "@/contexts/MessengerContext";
import { toast } from "sonner";

const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8080";

export type UserStatus = "online" | "away" | "offline";

interface NewMessageEvent {
  room_id: number;
  sender_name: string;
  sender_id: string;
  sender_photo?: string | null;
  content: string;
}

interface TypingEvent {
  room_id: number;
  user_id: string;
  user_name: string;
}

export function useMessengerSocket() {
  const socketRef = useRef<Socket | null>(null);
  const { selectedRoomId, notificationEnabled, isOpen, openMessenger, mutedRooms } = useMessengerContext();
  const currentUserIdRef = useRef<string | null>(null);
  const selectedRoomIdRef = useRef(selectedRoomId);
  const notificationEnabledRef = useRef(notificationEnabled);
  const isOpenRef = useRef(isOpen);
  const mutedRoomsRef = useRef(mutedRooms);

  const qc = useQueryClient();
  const [userStatuses, setUserStatuses] = useState<Map<string, UserStatus>>(new Map());
  const [typingUsers, setTypingUsers] = useState<Map<string, string[]>>(new Map());

  // Keep refs in sync so socket handlers use latest values
  useEffect(() => {
    selectedRoomIdRef.current = selectedRoomId;
  }, [selectedRoomId]);

  useEffect(() => {
    notificationEnabledRef.current = notificationEnabled;
  }, [notificationEnabled]);

  useEffect(() => {
    isOpenRef.current = isOpen;
  }, [isOpen]);

  useEffect(() => {
    mutedRoomsRef.current = mutedRooms;
  }, [mutedRooms]);

  useEffect(() => {
    const token = localStorage.getItem("authToken");
    if (!token) return;

    try {
      const payload = JSON.parse(atob(token.split(".")[1]));
      currentUserIdRef.current = payload.userId ?? payload.user_id ?? null;
    } catch {}

    const socket = io(BACKEND_URL, {
      path: "/socket.io",
      auth: { token },
      transports: ["websocket", "polling"],
    });

    socketRef.current = socket;

    socket.on("connect", () => {
      socket.emit("join_rooms");
    });

    // Receive full presence list on connect
    socket.on("presence_list", (data: Record<string, string>) => {
      setUserStatuses((prev) => {
        const next = new Map(prev);
        for (const [uid, status] of Object.entries(data)) {
          next.set(uid, status as UserStatus);
        }
        return next;
      });
    });

    // Receive individual status updates
    socket.on("user_status", (data: { userId: string; status: UserStatus }) => {
      setUserStatuses((prev) => {
        const next = new Map(prev);
        if (data.status === "offline") {
          next.delete(data.userId);
        } else {
          next.set(data.userId, data.status);
        }
        return next;
      });
    });

    // Tab visibility → away/online
    const handleVisibilityChange = () => {
      const status = document.hidden ? "away" : "online";
      socket.emit("set_status", { status });
    };
    document.addEventListener("visibilitychange", handleVisibilityChange);

    // BUG-5 & BUG-8: Handle new_message with cache invalidation and toast
    socket.on("new_message", (data: NewMessageEvent) => {
      const roomIdStr = String(data.room_id);

      qc.invalidateQueries({ queryKey: ["messenger", "messages", roomIdStr] });
      qc.invalidateQueries({ queryKey: ["messenger", "rooms"] });
      qc.invalidateQueries({ queryKey: ["messenger", "unread"] });

      const isOwnMessage = data.sender_id === currentUserIdRef.current;
      const isRoomMuted = mutedRoomsRef.current.has(roomIdStr);
      if (notificationEnabledRef.current && !isOwnMessage && !isRoomMuted && (!isOpenRef.current || roomIdStr !== selectedRoomIdRef.current)) {
        const photoSrc = data.sender_photo
          ? `data:image/jpeg;base64,${data.sender_photo}`
          : null;
        toast(
          <div className="flex items-center gap-2 cursor-pointer" onClick={() => openMessenger(roomIdStr)}>
            {photoSrc ? (
              <img src={photoSrc} alt="" className="w-6 h-6 rounded-full object-cover shrink-0" />
            ) : (
              <div className="w-6 h-6 rounded-full bg-muted flex items-center justify-center text-xs font-medium shrink-0">
                {(data.sender_name || "?").charAt(0).toUpperCase()}
              </div>
            )}
            <div className="min-w-0">
              <div className="font-medium text-sm">{data.sender_name || "새 메시지"}</div>
              <div className="text-xs text-muted-foreground truncate">{data.content?.slice(0, 60) || ""}</div>
            </div>
          </div>
        );
      }
    });

    // BUG-7: Backend emits "user_typing" / "user_stop_typing"
    socket.on("user_typing", (data: TypingEvent) => {
      const roomIdStr = String(data.room_id);
      setTypingUsers((prev) => {
        const next = new Map(prev);
        const users = next.get(roomIdStr) || [];
        if (!users.includes(data.user_name)) {
          next.set(roomIdStr, [...users, data.user_name]);
        }
        return next;
      });
    });

    socket.on("user_stop_typing", (data: TypingEvent) => {
      const roomIdStr = String(data.room_id);
      setTypingUsers((prev) => {
        const next = new Map(prev);
        const users = next.get(roomIdStr) || [];
        next.set(roomIdStr, users.filter((u) => u !== data.user_name));
        return next;
      });
    });

    return () => {
      document.removeEventListener("visibilitychange", handleVisibilityChange);
      socket.disconnect();
      socketRef.current = null;
    };
  }, [toast, qc]);

  const emitTypingStart = useCallback(
    (roomId: string) => {
      socketRef.current?.emit("typing_start", { room_id: Number(roomId) });
    },
    []
  );

  const emitTypingStop = useCallback(
    (roomId: string) => {
      socketRef.current?.emit("typing_stop", { room_id: Number(roomId) });
    },
    []
  );

  return { socket: socketRef, userStatuses, typingUsers, emitTypingStart, emitTypingStop };
}
