"use client";

import { useAuth } from "@clerk/nextjs";
import { useEffect, useMemo, useRef, useState } from "react";
import { io, type Socket } from "socket.io-client";
import axios from "axios";
import { PageHeader } from "@/components/ui/PageHeader";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";

interface ChatThread {
  id: string;
  type: "gym" | "dm";
  unreadCount: number;
  lastMessageBody: string | null;
  lastMessageAt: string | null;
}

interface ChatMessage {
  id: string;
  threadId: string;
  senderUserId: string;
  body: string;
  createdAt: string;
  clientMessageId: string | null;
  kind?: "text" | "system";
  attachments?: Array<{ url: string; type: string; name?: string }>;
  editedAt?: string | null;
  deletedAt?: string | null;
}

interface ReadEvent {
  threadId?: string;
  userId?: string;
  lastReadMessageId?: string | null;
}

export default function AdminChatPage() {
  const { getToken, userId } = useAuth();

  const [threads, setThreads] = useState<ChatThread[]>([]);
  const [activeThreadId, setActiveThreadId] = useState<string | null>(null);
  const [messagesByThreadId, setMessagesByThreadId] = useState<
    Record<string, ChatMessage[]>
  >({});
  const [draft, setDraft] = useState("");
  const [loadingThreads, setLoadingThreads] = useState(false);
  const [loadingMessages, setLoadingMessages] = useState(false);
  const [socketConnected, setSocketConnected] = useState(false);
  const [typingByThreadId, setTypingByThreadId] = useState<
    Record<string, string[]>
  >({});
  const [readByThreadId, setReadByThreadId] = useState<
    Record<string, Record<string, string>>
  >({});

  const typingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const socketRef = useRef<Socket | null>(null);
  const getTokenRef = useRef(getToken);
  const activeThreadIdRef = useRef<string | null>(null);
  const subscribedThreadIdsRef = useRef<Set<string>>(new Set());
  const loadingThreadsRef = useRef(false);
  const loadingMessagesRef = useRef<Record<string, boolean>>({});

  const activeMessages = useMemo(
    () => (activeThreadId ? (messagesByThreadId[activeThreadId] ?? []) : []),
    [activeThreadId, messagesByThreadId],
  );

  const typingUsers = useMemo(
    () => (activeThreadId ? (typingByThreadId[activeThreadId] ?? []) : []),
    [activeThreadId, typingByThreadId],
  );

  useEffect(() => {
    getTokenRef.current = getToken;
  }, [getToken]);

  useEffect(() => {
    activeThreadIdRef.current = activeThreadId;
  }, [activeThreadId]);

  const loadThreads = async (showLoader = true) => {
    if (loadingThreadsRef.current) {
      return;
    }

    const token = await getTokenRef.current();
    if (!token) {
      return;
    }

    loadingThreadsRef.current = true;
    try {
      if (showLoader) {
        setLoadingThreads(true);
      }
      const response = await axios.get<{ threads: ChatThread[] }>(
        "/api/chat/threads",
        {
          headers: { Authorization: `Bearer ${token}` },
        },
      );

      setThreads(response.data.threads);
      setActiveThreadId((prev) => prev ?? response.data.threads[0]?.id ?? null);
      setReadByThreadId({});
    } finally {
      if (showLoader) {
        setLoadingThreads(false);
      }
      loadingThreadsRef.current = false;
    }
  };

  const loadMessages = async (threadId: string, showLoader = true) => {
    if (loadingMessagesRef.current[threadId]) {
      return;
    }

    const token = await getTokenRef.current();
    if (!token) {
      return;
    }

    loadingMessagesRef.current[threadId] = true;
    try {
      if (showLoader) {
        setLoadingMessages(true);
      }
      const response = await axios.get<{ messages: ChatMessage[] }>(
        `/api/chat/threads/${threadId}/messages`,
        {
          headers: { Authorization: `Bearer ${token}` },
        },
      );

      setMessagesByThreadId((prev) => ({
        ...prev,
        [threadId]: response.data.messages,
      }));
    } finally {
      if (showLoader) {
        setLoadingMessages(false);
      }
      loadingMessagesRef.current[threadId] = false;
    }
  };

  useEffect(() => {
    void loadThreads(true);
  }, []);

  useEffect(() => {
    if (!activeThreadId) {
      return;
    }

    void loadMessages(activeThreadId, true);
  }, [activeThreadId]);

  useEffect(() => {
    let mounted = true;

    const setupSocket = async () => {
      const token = await getTokenRef.current();
      if (!token || !mounted) {
        return;
      }

      const realtimeBase =
        process.env.NEXT_PUBLIC_REALTIME_URL ?? window.location.origin;

      const socket = io(realtimeBase, {
        path: process.env.NEXT_PUBLIC_REALTIME_PATH ?? "/socket.io",
        transports: ["websocket"],
        auth: { token },
      });

      socketRef.current = socket;

      socket.on("connect", () => {
        setSocketConnected(true);
        subscribedThreadIdsRef.current.clear();
        void loadThreads(false);
      });

      socket.on("disconnect", () => {
        setSocketConnected(false);
        setTypingByThreadId({});
        subscribedThreadIdsRef.current.clear();
      });

      socket.on(
        "chat:message:new",
        (event: { threadId: string; message: ChatMessage }) => {
          setMessagesByThreadId((prev) => {
            const current = prev[event.threadId] ?? [];
            if (current.some((message) => message.id === event.message.id)) {
              return prev;
            }

            return {
              ...prev,
              [event.threadId]: [...current, event.message],
            };
          });

          setThreads((prev) =>
            prev
              .map((thread) =>
                thread.id === event.threadId
                  ? {
                      ...thread,
                      lastMessageAt: event.message.createdAt,
                      lastMessageBody: event.message.body,
                      unreadCount:
                        event.threadId === activeThreadIdRef.current ||
                        event.message.senderUserId === userId
                          ? thread.unreadCount
                          : thread.unreadCount + 1,
                    }
                  : thread,
              )
              .sort((a, b) => {
                const aTime = a.lastMessageAt ?? "1970-01-01T00:00:00.000Z";
                const bTime = b.lastMessageAt ?? "1970-01-01T00:00:00.000Z";
                return new Date(bTime).getTime() - new Date(aTime).getTime();
              }),
          );
        },
      );

      socket.on(
        "chat:message:ack",
        (event: {
          threadId: string;
          message: ChatMessage;
          clientMessageId?: string | null;
        }) => {
          setMessagesByThreadId((prev) => ({
            ...prev,
            [event.threadId]: (prev[event.threadId] ?? []).map((message) =>
              event.clientMessageId
                ? message.clientMessageId === event.clientMessageId
                  ? event.message
                  : message
                : message.id === event.message.id
                  ? event.message
                  : message,
            ),
          }));
        },
      );

      socket.on(
        "chat:typing",
        (event: { threadId?: string; userId?: string; isTyping?: boolean }) => {
          if (!event.threadId || !event.userId || event.userId === userId) {
            return;
          }

          setTypingByThreadId((prev) => {
            const current = prev[event.threadId!] ?? [];
            if (event.isTyping) {
              if (current.includes(event.userId!)) {
                return prev;
              }

              return {
                ...prev,
                [event.threadId!]: [...current, event.userId!],
              };
            }

            return {
              ...prev,
              [event.threadId!]: current.filter((id) => id !== event.userId),
            };
          });
        },
      );

      socket.on("chat:read:update", (event: ReadEvent) => {
        if (!event.threadId || !event.userId || !event.lastReadMessageId) {
          return;
        }

        setReadByThreadId((prev) => ({
          ...prev,
          [event.threadId!]: {
            ...(prev[event.threadId!] ?? {}),
            [event.userId!]: event.lastReadMessageId!,
          },
        }));

        void loadThreads(false);
      });

      socket.on("chat:error", (event: { code?: string; message?: string }) => {
        if (event.code === "rate_limited") {
          console.warn(event.message ?? "Chat action rate-limited");
        }
      });
    };

    void setupSocket();

    return () => {
      mounted = false;
      if (typingTimeoutRef.current) {
        clearTimeout(typingTimeoutRef.current);
      }
      if (socketRef.current) {
        socketRef.current.disconnect();
        socketRef.current = null;
      }
    };
  }, [userId]);

  useEffect(() => {
    if (!socketRef.current || !socketConnected) {
      return;
    }

    const socket = socketRef.current;
    const nextIds = new Set(threads.map((thread) => thread.id));

    threads.forEach((thread) => {
      if (!subscribedThreadIdsRef.current.has(thread.id)) {
        socket.emit("chat:subscribe", { threadId: thread.id });
        subscribedThreadIdsRef.current.add(thread.id);
      }
    });

    Array.from(subscribedThreadIdsRef.current).forEach((threadId) => {
      if (!nextIds.has(threadId)) {
        socket.emit("chat:unsubscribe", { threadId });
        subscribedThreadIdsRef.current.delete(threadId);
      }
    });
  }, [socketConnected, threads]);

  useEffect(() => {
    if (!activeThreadId) {
      return;
    }

    setThreads((prev) =>
      prev.map((thread) =>
        thread.id === activeThreadId ? { ...thread, unreadCount: 0 } : thread,
      ),
    );
  }, [activeThreadId]);

  useEffect(() => {
    if (!socketConnected) {
      const interval = setInterval(() => {
        void loadThreads(false);
        if (activeThreadIdRef.current) {
          void loadMessages(activeThreadIdRef.current, false);
        }
      }, 5000);

      return () => {
        clearInterval(interval);
      };
    }
  }, [socketConnected]);

  useEffect(() => {
    if (!activeThreadId) {
      return;
    }

    const messages = messagesByThreadId[activeThreadId] ?? [];
    const lastMessage = messages[messages.length - 1];
    if (!lastMessage || lastMessage.senderUserId === userId) {
      return;
    }

    const markRead = async () => {
      const token = await getTokenRef.current();
      if (!token) {
        return;
      }

      await axios.post(
        `/api/chat/threads/${activeThreadId}/read`,
        { lastReadMessageId: lastMessage.id },
        {
          headers: { Authorization: `Bearer ${token}` },
        },
      );

      if (socketRef.current && socketConnected) {
        socketRef.current.emit("chat:read", {
          threadId: activeThreadId,
          lastReadMessageId: lastMessage.id,
        });
      }

      if (userId) {
        setReadByThreadId((prev) => ({
          ...prev,
          [activeThreadId]: {
            ...(prev[activeThreadId] ?? {}),
            [userId]: lastMessage.id,
          },
        }));
      }
    };

    void markRead();
  }, [activeThreadId, messagesByThreadId, socketConnected, userId]);

  const onChangeDraft = (value: string) => {
    setDraft(value);

    if (!activeThreadId || !socketRef.current || !socketConnected) {
      return;
    }

    const isTyping = value.trim().length > 0;
    socketRef.current.emit("chat:typing", {
      threadId: activeThreadId,
      isTyping,
    });

    if (typingTimeoutRef.current) {
      clearTimeout(typingTimeoutRef.current);
    }

    typingTimeoutRef.current = setTimeout(() => {
      if (activeThreadId && socketRef.current) {
        socketRef.current.emit("chat:typing", {
          threadId: activeThreadId,
          isTyping: false,
        });
      }
    }, 1200);
  };

  const onSend = async () => {
    if (!activeThreadId) {
      return;
    }

    const body = draft.trim();
    if (!body) {
      return;
    }

    const clientMessageId = `admin_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
    const optimistic: ChatMessage = {
      id: clientMessageId,
      threadId: activeThreadId,
      senderUserId: userId ?? "",
      body,
      createdAt: new Date().toISOString(),
      clientMessageId,
      kind: "text",
      attachments: [],
      editedAt: null,
      deletedAt: null,
    };

    setMessagesByThreadId((prev) => ({
      ...prev,
      [activeThreadId]: [...(prev[activeThreadId] ?? []), optimistic],
    }));
    setDraft("");

    if (socketRef.current && socketConnected) {
      socketRef.current.emit("chat:send", {
        threadId: activeThreadId,
        body,
        clientMessageId,
      });
      return;
    }

    const token = await getTokenRef.current();
    if (!token) {
      return;
    }

    try {
      const response = await axios.post<{ message: ChatMessage }>(
        `/api/chat/threads/${activeThreadId}/messages`,
        { body, clientMessageId },
        {
          headers: { Authorization: `Bearer ${token}` },
        },
      );

      setMessagesByThreadId((prev) => ({
        ...prev,
        [activeThreadId]: (prev[activeThreadId] ?? []).map((message) =>
          message.id === clientMessageId ? response.data.message : message,
        ),
      }));
    } catch {
      setMessagesByThreadId((prev) => ({
        ...prev,
        [activeThreadId]: (prev[activeThreadId] ?? []).filter(
          (message) => message.id !== clientMessageId,
        ),
      }));
    }
  };

  return (
    <div className="space-y-6">
      <PageHeader
        title="Chat"
        description="Gym room and private trainer-client messaging"
        breadcrumbs={[{ label: "Chat" }]}
      />

      <div className="text-sm text-slate-500">
        {socketConnected ? "Realtime connected" : "Realtime disconnected"}
      </div>

      <div className="grid grid-cols-1 gap-6 lg:grid-cols-[320px_1fr]">
        <Card>
          <CardHeader>
            <CardTitle>Threads</CardTitle>
          </CardHeader>
          <CardContent className="space-y-2">
            {loadingThreads ? (
              <div className="text-sm text-slate-500">Loading threads...</div>
            ) : (
              threads.map((thread) => (
                <button
                  key={thread.id}
                  type="button"
                  onClick={() => setActiveThreadId(thread.id)}
                  className={`w-full rounded-lg border p-3 text-left transition ${
                    thread.id === activeThreadId
                      ? "border-blue-500 bg-blue-50"
                      : "border-slate-200 hover:bg-slate-50"
                  }`}
                >
                  <div className="flex items-center justify-between">
                    <p className="font-medium text-slate-900">
                      {thread.type === "gym" ? "Gym Room" : "Trainer DM"}
                    </p>
                    {thread.unreadCount > 0 && (
                      <span className="rounded-full bg-red-500 px-2 py-0.5 text-xs font-semibold text-white">
                        {thread.unreadCount > 99 ? "99+" : thread.unreadCount}
                      </span>
                    )}
                  </div>
                  <p className="mt-1 truncate text-sm text-slate-500">
                    {thread.lastMessageBody ?? "No messages yet"}
                  </p>
                </button>
              ))
            )}
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>Messages</CardTitle>
          </CardHeader>
          <CardContent>
            {!activeThreadId ? (
              <p className="text-sm text-slate-500">
                Select a thread to start chatting.
              </p>
            ) : (
              <div className="space-y-3">
                <div className="max-h-[460px] space-y-2 overflow-y-auto rounded-md border border-slate-200 p-3">
                  {loadingMessages ? (
                    <p className="text-sm text-slate-500">
                      Loading messages...
                    </p>
                  ) : (
                    activeMessages.map((message) => {
                      const own = message.senderUserId === userId;
                      const threadReads = activeThreadId
                        ? (readByThreadId[activeThreadId] ?? {})
                        : {};
                      const seen = Object.entries(threadReads).some(
                        ([readerUserId, readMessageId]) =>
                          readerUserId !== userId &&
                          readMessageId === message.id,
                      );

                      return (
                        <div
                          key={message.id}
                          className={`max-w-[80%] rounded-lg px-3 py-2 text-sm ${
                            own
                              ? "ml-auto bg-blue-600 text-white"
                              : "bg-slate-100 text-slate-900"
                          }`}
                        >
                          <div>{message.body}</div>
                          {own && seen && (
                            <div className="mt-1 text-[10px] text-blue-100">
                              Seen
                            </div>
                          )}
                        </div>
                      );
                    })
                  )}
                </div>

                {typingUsers.length > 0 && (
                  <p className="text-xs text-slate-500">
                    {typingUsers.length === 1
                      ? "Someone is typing..."
                      : `${typingUsers.length} people are typing...`}
                  </p>
                )}

                <div className="flex items-center gap-2">
                  <Input
                    value={draft}
                    onChange={(event) => onChangeDraft(event.target.value)}
                    placeholder="Type a message"
                  />
                  <Button onClick={onSend}>Send</Button>
                </div>
              </div>
            )}
          </CardContent>
        </Card>
      </div>
    </div>
  );
}
