import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
import { gql } from "@apollo/client";

const logLocal = (message, ...args) => {
  if (import.meta.env.VITE_APP_IS_TEST || !import.meta.env.PROD) {
    console.log(`==================== ${message} ====================`);
    console.dir({ ...args });
  }
};

// Utility function to enrich conversation data
const enrichConversation = (conversation, isOutbound) => ({
  ...conversation,
  updated_at: conversation.updated_at || new Date().toISOString(),
  unreadcnt: conversation.unreadcnt || 0,
  archived: conversation.archived || false,
  label: conversation.label || null,
  job_conversations: conversation.job_conversations || [],
  messages_aggregate: conversation.messages_aggregate || {
    __typename: "messages_aggregate",
    aggregate: {
      __typename: "messages_aggregate_fields",
      count: isOutbound ? 0 : 1
    }
  },
  __typename: "conversations"
});

export const registerMessagingHandlers = ({ socket, client }) => {
  if (!(socket && client)) return;

  const handleNewMessageSummary = async (message) => {
    const { conversationId, newConversation, existingConversation, isoutbound } = message;

    logLocal("handleNewMessageSummary - Start", { message, isNew: !existingConversation });

    const queryVariables = { offset: 0 };

    if (!existingConversation && conversationId) {
      // Attempt to read from the cache to determine if this is actually a new conversation
      try {
        const cachedConversation = client.cache.readFragment({
          id: client.cache.identify({ __typename: "conversations", id: conversationId }),
          fragment: gql`
            fragment ExistingConversationCheck on conversations {
              id
            }
          `
        });

        if (cachedConversation) {
          logLocal("handleNewMessageSummary - Existing Conversation inferred from cache", {
            conversationId
          });
          return handleNewMessageSummary({
            ...message,
            existingConversation: true
          });
        }
      } catch {
        logLocal("handleNewMessageSummary - Cache miss", { conversationId });
      }
    }

    // Handle new conversation
    if (!existingConversation && newConversation?.phone_num) {
      logLocal("handleNewMessageSummary - New Conversation", newConversation);

      try {
        const queryResults = client.cache.readQuery({
          query: CONVERSATION_LIST_QUERY,
          variables: queryVariables
        });

        const existingConversations = queryResults?.conversations || [];
        const enrichedConversation = enrichConversation(newConversation, isoutbound);

        if (!existingConversations.some((conv) => conv.id === enrichedConversation.id)) {
          client.cache.modify({
            id: "ROOT_QUERY",
            fields: {
              conversations(existingConversations = []) {
                return [enrichedConversation, ...existingConversations];
              }
            }
          });
        }
      } catch (error) {
        console.error("Error updating cache for new conversation:", error);
      }
      return;
    }

    // Handle existing conversation
    if (existingConversation) {
      try {
        client.cache.modify({
          id: client.cache.identify({ __typename: "conversations", id: conversationId }),
          fields: {
            updated_at: () => new Date().toISOString(),
            archived: () => false,
            messages_aggregate(cached = { aggregate: { count: 0 } }) {
              const currentCount = cached.aggregate?.count || 0;
              if (!isoutbound) {
                return {
                  __typename: "messages_aggregate",
                  aggregate: {
                    __typename: "messages_aggregate_fields",
                    count: currentCount + 1
                  }
                };
              }
              return cached;
            }
          }
        });
      } catch (error) {
        console.error("Error updating cache for existing conversation:", error);
      }

      return;
    }

    logLocal("New Conversation Summary finished without work", { message });
  };

  const handleNewMessageDetailed = (message) => {
    const { conversationId, newMessage } = message;

    logLocal("handleNewMessageDetailed - Start", message);

    try {
      // Check if the conversation exists in the cache
      const queryResults = client.cache.readQuery({
        query: GET_CONVERSATION_DETAILS,
        variables: { conversationId }
      });

      if (!queryResults?.conversations_by_pk) {
        console.warn("Conversation not found in cache:", { conversationId });
        return;
      }

      // Append the new message to the conversation's message list using cache.modify
      client.cache.modify({
        id: client.cache.identify({ __typename: "conversations", id: conversationId }),
        fields: {
          messages(existingMessages = []) {
            return [...existingMessages, newMessage];
          }
        }
      });

      logLocal("handleNewMessageDetailed - Message appended successfully", {
        conversationId,
        newMessage
      });
    } catch (error) {
      console.error("Error updating conversation messages in cache:", error);
    }
  };

  const handleMessageChanged = (message) => {
    if (!message) {
      logLocal("handleMessageChanged - No message provided", message);
      return;
    }

    logLocal("handleMessageChanged - Start", message);

    try {
      client.cache.modify({
        id: client.cache.identify({ __typename: "conversations", id: message.conversationid }),
        fields: {
          messages(existingMessages = [], { readField }) {
            return existingMessages.map((messageRef) => {
              // Check if this is the message to update
              if (readField("id", messageRef) === message.id) {
                const currentStatus = readField("status", messageRef);

                // Handle known types of message changes
                switch (message.type) {
                  case "status-changed":
                    // Prevent overwriting if the current status is already "delivered"
                    if (currentStatus === "delivered") {
                      logLocal("handleMessageChanged - Status already delivered, skipping update", {
                        messageId: message.id
                      });
                      return messageRef;
                    }

                    // Update the status field
                    return {
                      ...messageRef,
                      status: message.status
                    };

                  case "text-updated":
                    // Handle changes to the message text
                    return {
                      ...messageRef,
                      text: message.text
                    };

                  default:
                    // Log a warning for unhandled message types
                    logLocal("handleMessageChanged - Unhandled message type", { type: message.type });
                    return messageRef;
                }
              }

              return messageRef;
            });
          }
        }
      });

      logLocal("handleMessageChanged - Message updated successfully", {
        messageId: message.id,
        type: message.type
      });
    } catch (error) {
      console.error("handleMessageChanged - Error modifying cache:", error);
    }
  };

  const handleConversationChanged = async (data) => {
    if (!data) {
      logLocal("handleConversationChanged - No data provided", data);
      return;
    }

    const { conversationId, type, job_conversations, messageIds, ...fields } = data;
    logLocal("handleConversationChanged - Start", data);

    const updatedAt = new Date().toISOString();

    const updateConversationList = (newConversation) => {
      try {
        const existingList = client.cache.readQuery({
          query: CONVERSATION_LIST_QUERY,
          variables: { offset: 0 }
        });

        const updatedList = existingList?.conversations
          ? [newConversation, ...existingList.conversations.filter((conv) => conv.id !== newConversation.id)]
          : [newConversation]; // Prevent duplicates

        client.cache.writeQuery({
          query: CONVERSATION_LIST_QUERY,
          variables: { offset: 0 },
          data: {
            conversations: updatedList
          }
        });

        logLocal("handleConversationChanged - Conversation list updated successfully", newConversation);
      } catch (error) {
        console.error("Error updating conversation list in the cache:", error);
      }
    };

    // Handle specific types
    try {
      switch (type) {
        case "conversation-marked-read":
          if (conversationId && messageIds?.length > 0) {
            client.cache.modify({
              id: client.cache.identify({ __typename: "conversations", id: conversationId }),
              fields: {
                messages(existingMessages = [], { readField }) {
                  return existingMessages.map((message) => {
                    if (messageIds.includes(readField("id", message))) {
                      return { ...message, read: true };
                    }
                    return message;
                  });
                },
                messages_aggregate: () => ({
                  __typename: "messages_aggregate",
                  aggregate: { __typename: "messages_aggregate_fields", count: 0 }
                })
              }
            });
          }
          break;

        case "conversation-created":
          updateConversationList({ ...fields, job_conversations, updated_at: updatedAt });
          break;

        case "conversation-unarchived":
        case "conversation-archived":
          // Would like to someday figure out how to get this working without refetch queries,
          // But I have but a solid 4 hours into it, and there are just too many weird occurrences
          try {
            const listQueryVariables = { offset: 0 };
            const detailsQueryVariables = { conversationId };

            // Check if conversation details exist in the cache
            const detailsExist = !!client.cache.readQuery({
              query: GET_CONVERSATION_DETAILS,
              variables: detailsQueryVariables
            });

            // Refetch conversation list
            await client.refetchQueries({
              include: [CONVERSATION_LIST_QUERY, ...(detailsExist ? [GET_CONVERSATION_DETAILS] : [])],
              variables: [
                { query: CONVERSATION_LIST_QUERY, variables: listQueryVariables },
                ...(detailsExist
                  ? [
                      {
                        query: GET_CONVERSATION_DETAILS,
                        variables: detailsQueryVariables
                      }
                    ]
                  : [])
              ]
            });

            logLocal("handleConversationChanged - Refetched queries after state change", {
              conversationId,
              type
            });
          } catch (error) {
            console.error("Error refetching queries after conversation state change:", error);
          }
          break;

        case "tag-added": { // Ensure `job_conversations` is properly formatted
          const formattedJobConversations = job_conversations.map((jc) => ({
            __typename: "job_conversations",
            jobid: jc.jobid || jc.job?.id,
            conversationid: conversationId,
            job: jc.job || {
              __typename: "jobs",
              id: data.selectedJob.id,
              ro_number: data.selectedJob.ro_number,
              ownr_co_nm: data.selectedJob.ownr_co_nm,
              ownr_fn: data.selectedJob.ownr_fn,
              ownr_ln: data.selectedJob.ownr_ln
            }
          }));

          client.cache.modify({
            id: client.cache.identify({ __typename: "conversations", id: conversationId }),
            fields: {
              job_conversations: (existing = []) => {
                // Ensure no duplicates based on both `conversationid` and `jobid`
                const existingLinks = new Set(
                  existing.map((jc) => {
                    const jobId = client.cache.readFragment({
                      id: client.cache.identify(jc),
                      fragment: gql`
                        fragment JobConversationLinkAdded on job_conversations {
                          jobid
                          conversationid
                        }
                      `
                    })?.jobid;
                    return `${jobId}:${conversationId}`; // Unique identifier for a job-conversation link
                  })
                );

                const newItems = formattedJobConversations.filter((jc) => {
                  const uniqueLink = `${jc.jobid}:${jc.conversationid}`;
                  return !existingLinks.has(uniqueLink);
                });

                return [...existing, ...newItems];
              }
            }
          });

          break;
        }

        case "tag-removed":
          try {
            const conversationCacheId = client.cache.identify({ __typename: "conversations", id: conversationId });

            // Evict the specific cache entry for job_conversations
            client.cache.evict({
              id: conversationCacheId,
              fieldName: "job_conversations"
            });

            // Garbage collect evicted entries
            client.cache.gc();

            logLocal("handleConversationChanged - tag removed - Refetched conversation list after state change", {
              conversationId,
              type
            });
          } catch (error) {
            console.error("Error refetching queries after conversation state change: (Tag Removed)", error);
          }

          break;

        default:
          logLocal("handleConversationChanged - Unhandled type", { type });
          client.cache.modify({
            id: client.cache.identify({ __typename: "conversations", id: conversationId }),
            fields: {
              ...Object.fromEntries(
                Object.entries(fields).map(([key, value]) => [key, (cached) => (value !== undefined ? value : cached)])
              )
            }
          });
      }
    } catch (error) {
      console.error("Error handling conversation changes:", { type, error });
    }
  };

  // Existing handler for phone number opt-out
  const handlePhoneNumberOptedOut = async (data) => {
    const { bodyshopid, phone_number } = data;
    logLocal("handlePhoneNumberOptedOut - Start", data);

    try {
      client.cache.modify({
        id: "ROOT_QUERY",
        fields: {
          phone_number_opt_out(existing = [], { readField }) {
            const phoneNumberExists = existing.some(
              (ref) => readField("phone_number", ref) === phone_number && readField("bodyshopid", ref) === bodyshopid
            );

            if (phoneNumberExists) {
              logLocal("handlePhoneNumberOptedOut - Phone number already in cache", { phone_number, bodyshopid });
              return existing;
            }

            const newOptOut = {
              __typename: "phone_number_opt_out",
              id: `temporary-${phone_number}-${Date.now()}`,
              bodyshopid,
              phone_number,
              created_at: new Date().toISOString(),
              updated_at: new Date().toISOString()
            };

            return [...existing, newOptOut];
          }
        },
        broadcast: true
      });

      client.cache.evict({
        id: "ROOT_QUERY",
        fieldName: "phone_number_opt_out",
        args: { bodyshopid, search: phone_number }
      });
      client.cache.gc();

      logLocal("handlePhoneNumberOptedOut - Cache updated successfully", data);
    } catch (error) {
      console.error("Error updating cache for phone number opt-out:", error);
      logLocal("handlePhoneNumberOptedOut - Error", { error: error.message });
    }
  };

  // New handler for phone number opt-in
  const handlePhoneNumberOptedIn = async (data) => {
    const { bodyshopid, phone_number } = data;
    logLocal("handlePhoneNumberOptedIn - Start", data);

    try {
      // Update the Apollo cache for GET_PHONE_NUMBER_OPT_OUTS by removing the phone number
      client.cache.modify({
        id: "ROOT_QUERY",
        fields: {
          phone_number_opt_out(existing = [], { readField }) {
            // Filter out the phone number from the opt-out list
            return existing.filter(
              (ref) => !(readField("phone_number", ref) === phone_number && readField("bodyshopid", ref) === bodyshopid)
            );
          }
        },
        broadcast: true // Trigger UI updates
      });

      // Evict the cache entry to force a refetch on next query
      client.cache.evict({
        id: "ROOT_QUERY",
        fieldName: "phone_number_opt_out",
        args: { bodyshopid, search: phone_number }
      });
      client.cache.gc();

      logLocal("handlePhoneNumberOptedIn - Cache updated successfully", data);
    } catch (error) {
      console.error("Error updating cache for phone number opt-in:", error);
      logLocal("handlePhoneNumberOptedIn - Error", { error: error.message });
    }
  };

  socket.on("new-message-summary", handleNewMessageSummary);
  socket.on("new-message-detailed", handleNewMessageDetailed);
  socket.on("message-changed", handleMessageChanged);
  socket.on("conversation-changed", handleConversationChanged);
  socket.on("phone-number-opted-out", handlePhoneNumberOptedOut);
  socket.on("phone-number-opted-in", handlePhoneNumberOptedIn);
};

export const unregisterMessagingHandlers = ({ socket }) => {
  if (!socket) return;
  socket.off("new-message-summary");
  socket.off("new-message-detailed");
  socket.off("message-changed");
  socket.off("conversation-changed");
  socket.off("phone-number-opted-out");
  socket.off("phone-number-opted-in");
};
