import { gql, useApolloClient, useQuery, useSubscription } from "@apollo/client";
import axios from "axios";
import { useCallback, useEffect, useMemo, useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { CONVERSATION_SUBSCRIPTION_BY_PK, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ChatConversationComponent from "./chat-conversation.component";
import { useSocket } from "../../contexts/SocketIO/useSocket.js";

const mapStateToProps = createStructuredSelector({
  selectedConversation: selectSelectedConversation,
  bodyshop: selectBodyshop
});

function ChatConversationContainer({ bodyshop, selectedConversation }) {
  const client = useApolloClient();
  const { socket } = useSocket();
  const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false);
  const [markingAsUnreadInProgress, setMarkingAsUnreadInProgress] = useState(false);

  const {
    loading: convoLoading,
    error: convoError,
    data: convoData
  } = useQuery(GET_CONVERSATION_DETAILS, {
    variables: { conversationId: selectedConversation },
    fetchPolicy: "network-only",
    nextFetchPolicy: "network-only",
    skip: !selectedConversation
  });

  const conversation = convoData?.conversations_by_pk;

  // Subscription for conversation updates (used when socket is NOT connected)
  useSubscription(CONVERSATION_SUBSCRIPTION_BY_PK, {
    skip: socket?.connected || !selectedConversation,
    variables: { conversationId: selectedConversation },
    onData: ({ data: subscriptionResult, client }) => {
      const messages = subscriptionResult?.data?.messages;
      if (!messages || messages.length === 0) return;

      messages.forEach((message) => {
        const messageRef = client.cache.identify(message);

        client.cache.writeFragment({
          id: messageRef,
          fragment: gql`
            fragment NewMessage on messages {
              id
              status
              text
              isoutbound
              image
              image_path
              userid
              created_at
              read
              is_system
            }
          `,
          data: message
        });

        client.cache.modify({
          id: client.cache.identify({ __typename: "conversations", id: selectedConversation }),
          fields: {
            messages(existingMessages = []) {
              const alreadyExists = existingMessages.some((msg) => msg.__ref === messageRef);
              if (alreadyExists) return existingMessages;
              return [...existingMessages, { __ref: messageRef }];
            },
            updated_at() {
              return message.created_at;
            }
          }
        });
      });
    }
  });

  /**
   * Best-effort badge update:
   * This assumes your list query uses messages_aggregate.aggregate.count as UNREAD inbound count.
   * If it’s total messages, rename/create a dedicated unread aggregate in the list query and update that field instead.
   */
  const setConversationUnreadCountBestEffort = useCallback(
    (conversationId, unreadCount) => {
      if (!conversationId) return;

      client.cache.modify({
        id: client.cache.identify({ __typename: "conversations", id: conversationId }),
        fields: {
          messages_aggregate(existing) {
            if (!existing?.aggregate) return existing;
            return { ...existing, aggregate: { ...existing.aggregate, count: unreadCount } };
          }
        }
      });
    },
    [client.cache]
  );

  const updateCacheWithReadMessages = useCallback(
    (conversationId, messageIds) => {
      if (!conversationId || !messageIds?.length) return;

      messageIds.forEach((messageId) => {
        client.cache.modify({
          id: client.cache.identify({ __typename: "messages", id: messageId }),
          fields: { read: () => true }
        });
      });

      setConversationUnreadCountBestEffort(conversationId, 0);
    },
    [client.cache, setConversationUnreadCountBestEffort]
  );

  const applyUnreadStateWithMaxOneUnread = useCallback(
    ({ conversationId, lastUnreadMessageId, messageIdsMarkedRead = [], unreadCount = 1 }) => {
      if (!conversationId || !lastUnreadMessageId) return;

      messageIdsMarkedRead.forEach((id) => {
        client.cache.modify({
          id: client.cache.identify({ __typename: "messages", id }),
          fields: { read: () => true }
        });
      });

      client.cache.modify({
        id: client.cache.identify({ __typename: "messages", id: lastUnreadMessageId }),
        fields: { read: () => false }
      });

      setConversationUnreadCountBestEffort(conversationId, unreadCount);
    },
    [client.cache, setConversationUnreadCountBestEffort]
  );

  // WebSocket event handlers
  useEffect(() => {
    if (!socket?.connected) return;

    const handleConversationChange = (data) => {
      if (data?.type === "conversation-marked-read") {
        updateCacheWithReadMessages(data.conversationId, data.messageIds);
      }

      if (data?.type === "conversation-marked-unread") {
        applyUnreadStateWithMaxOneUnread({
          conversationId: data.conversationId,
          lastUnreadMessageId: data.lastUnreadMessageId,
          messageIdsMarkedRead: data.messageIdsMarkedRead,
          unreadCount: data.unreadCount
        });
      }
    };

    socket.on("conversation-changed", handleConversationChange);
    return () => socket.off("conversation-changed", handleConversationChange);
  }, [socket, updateCacheWithReadMessages, applyUnreadStateWithMaxOneUnread]);

  // Join/leave conversation via WebSocket
  useEffect(() => {
    if (!socket?.connected || !selectedConversation || !bodyshop?.id) return;

    socket.emit("join-bodyshop-conversation", {
      bodyshopId: bodyshop.id,
      conversationId: selectedConversation
    });

    return () => {
      socket.emit("leave-bodyshop-conversation", {
        bodyshopId: bodyshop.id,
        conversationId: selectedConversation
      });
    };
  }, [socket, bodyshop, selectedConversation]);

  const inboundNonSystemMessages = useMemo(() => {
    const msgs = conversation?.messages || [];
    return msgs
      .filter((m) => m && !m.isoutbound && !m.is_system)
      .slice()
      .sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
  }, [conversation?.messages]);

  const canMarkUnread = inboundNonSystemMessages.length > 0;

  const handleMarkConversationAsRead = async () => {
    if (!conversation || markingAsReadInProgress) return;

    const unreadMessageIds = conversation.messages
      ?.filter((message) => !message.read && !message.isoutbound && !message.is_system)
      .map((message) => message.id);

    if (unreadMessageIds?.length > 0) {
      setMarkingAsReadInProgress(true);
      try {
        await axios.post("/sms/markConversationRead", {
          conversation,
          imexshopid: bodyshop?.imexshopid,
          bodyshopid: bodyshop?.id
        });

        updateCacheWithReadMessages(selectedConversation, unreadMessageIds);
      } catch (error) {
        console.error("Error marking conversation as read:", error.message);
      } finally {
        setMarkingAsReadInProgress(false);
      }
    }
  };

  const handleMarkLastMessageAsUnread = async () => {
    if (!conversation || markingAsUnreadInProgress) return;
    if (!bodyshop?.id || !bodyshop?.imexshopid) return;

    const lastInbound = inboundNonSystemMessages[inboundNonSystemMessages.length - 1];
    if (!lastInbound?.id) return;

    setMarkingAsUnreadInProgress(true);
    try {
      const res = await axios.post("/sms/markLastMessageUnread", {
        conversationId: conversation.id,
        imexshopid: bodyshop.imexshopid,
        bodyshopid: bodyshop.id
      });

      const payload = res?.data || {};
      if (payload.lastUnreadMessageId) {
        applyUnreadStateWithMaxOneUnread({
          conversationId: conversation.id,
          lastUnreadMessageId: payload.lastUnreadMessageId,
          messageIdsMarkedRead: payload.messageIdsMarkedRead || [],
          unreadCount: typeof payload.unreadCount === "number" ? payload.unreadCount : 1
        });
      } else {
        setConversationUnreadCountBestEffort(conversation.id, 0);
      }
    } catch (error) {
      console.error("Error marking last message unread:", error.message);
    } finally {
      setMarkingAsUnreadInProgress(false);
    }
  };

  return (
    <ChatConversationComponent
      subState={[convoLoading, convoError]}
      conversation={conversation || {}}
      messages={conversation?.messages || []}
      handleMarkConversationAsRead={handleMarkConversationAsRead}
      handleMarkLastMessageAsUnread={handleMarkLastMessageAsUnread}
      markingAsUnreadInProgress={markingAsUnreadInProgress}
      canMarkUnread={canMarkUnread}
    />
  );
}

export default connect(mapStateToProps)(ChatConversationContainer);
