import { useCallback, useMemo, useRef } from 'react';
import { useParams } from 'react-router';
import { matchPath, useLocation, useNavigate } from 'react-router-dom';

import debounce from 'lodash-es/debounce';
import { ulid } from 'ulid';

import useHuSnackbar from '@material-hu/components/design-system/Snackbar';

import { logEvent } from 'src/config/amplitude';
import { useAuth } from 'src/contexts/JWTContext';
import { usePageVisibilityContext } from 'src/contexts/PageVisibilityContext';
import {
  CONVERSATION_LEFT_REASON,
  CONVERSATION_TYPE,
} from 'src/pages/dashboard/Conversations/constants';
import {
  markMessage,
  updateConversationProperties,
} from 'src/pages/dashboard/Conversations/services';
import {
  type ConversationEventSocket,
  type ConversationMessage,
  type ConversationMessageSocket,
  type JsonEventUser,
  MessageTypeSocket,
  NotificationType,
  type PropertiesConversation,
} from 'src/pages/dashboard/Conversations/types';
import {
  createObjectMessage,
  getTypeMessage,
  isNewerMessage,
} from 'src/pages/dashboard/Conversations/utils';
import { EventName } from 'src/types/amplitude';
import { useLokaliseTranslation } from 'src/utils/i18n';

import {
  addMessage,
  addPinnedMessageCache,
  changePositionOfConversation,
  getConversationEvents,
  getConversationInfo,
  getMessageInCache,
  increaseUnreadMessagesInfo,
  removeConversationFromAllLists,
  removePinnedMessageCache,
  resetUnreadMessages,
  resetUnreadMessagesInfo,
  updateChannelProperties,
  updateGlobalUnreadChannels,
  updateGroupData,
  updateLastRead,
  updateLatestMessageInCache,
  updateMarkedUnreadTs,
  updateMembers,
  updateMessage,
  updateMessageReactionsFromEvent,
  updateMessageReadStatus,
  updatePinnedMessageCache,
  updateUnreadCountDisplayListInfo,
} from '../queries';
import { conversationsRoutes } from '../routes';

const useHandlerEvents = () => {
  const { id } = useParams();
  const location = useLocation();
  const navigate = useNavigate();
  const { user } = useAuth();
  const initialTs = useRef(ulid());
  const { enqueueSnackbar } = useHuSnackbar();
  const { t } = useLokaliseTranslation('chat');
  const { isVisibleRef } = usePageVisibilityContext();
  const callbackErrorUpdateConversation = () =>
    enqueueSnackbar({
      title: t('errors.update_conversations'),
      variant: 'error',
    });

  const exactConversationMatch = !!matchPath(
    {
      path: '/conversations/:id',
      end: false,
    },
    location.pathname,
  );

  const debouncedMarkAsRead = useMemo(
    () =>
      debounce((channel: string, properties?: PropertiesConversation) => {
        markMessage(channel);
        if (properties?.marked_unread_at) {
          updateConversationProperties(channel, {
            ...properties,
            marked_unread_at: undefined,
          });
        }
      }, 1000),
    [],
  );

  const handleAddMessage = useCallback(
    async (message: ConversationMessageSocket) => {
      const { channel_ts: channel, channel_is_im: isChannelIm } = message;

      const isCurrentUserMessage = Number(message.msg_sender_id) === user?.id;
      const isCurrentChannel = channel === id;

      const newMessage = createObjectMessage(message);
      const messageInCache = getMessageInCache(
        channel,
        newMessage.hu_data.message_ts,
      );
      const isChannelLeftOrJoined =
        newMessage.subtype === MessageTypeSocket.JOINED ||
        newMessage.subtype === MessageTypeSocket.LEFT;

      const conversationInfo = getConversationInfo(channel);
      const previousLatestTs = conversationInfo?.latest?.ts;

      const { currentTs } = getConversationEvents(initialTs.current);

      const isNewer = isNewerMessage({
        newTs: newMessage.hu_data.message_ts,
        currentTs,
      });

      if (!messageInCache) {
        addMessage({ id: channel, message: newMessage });
        if (isNewer) {
          if (isCurrentChannel && isVisibleRef.current) {
            debouncedMarkAsRead(channel);
            if (conversationInfo) {
              updateMarkedUnreadTs({
                channelId: channel,
                markedUnreadTs: newMessage.hu_data.message_ts,
              });
              if (conversationInfo.unread_count_display === 0) {
                updateLastRead(channel, newMessage.hu_data.message_ts);
              }
            }
          }
          updateLatestMessageInCache(channel, newMessage);
          await changePositionOfConversation({
            channelId: channel,
            message: newMessage,
            callback: callbackErrorUpdateConversation,
            userId: user?.id,
          });
        }
      }

      if (!isCurrentUserMessage || isChannelLeftOrJoined) {
        const messageType = getTypeMessage(
          newMessage.attachments,
          newMessage.text,
        );
        const conversationID = newMessage.channel;
        const eventProperties = {
          userId: user?.id.toString(),
          conversationType: isChannelIm
            ? CONVERSATION_TYPE.INDIVIDUAL
            : CONVERSATION_TYPE.GROUP,
          conversationID,
          messageType,
        };
        logEvent(EventName.CHATS_2_MESSAGE_RECEIVED, eventProperties);
        const shouldCountAsUnread = isNewer && newMessage.hu_data?.count_unread;

        if (
          shouldCountAsUnread &&
          (!isCurrentChannel || !exactConversationMatch || !id)
        ) {
          updateGlobalUnreadChannels({ channelId: channel });
          updateUnreadCountDisplayListInfo({ channelId: channel });
        }
        if (shouldCountAsUnread && isCurrentChannel && !isVisibleRef.current) {
          updateGlobalUnreadChannels({ channelId: channel });
          updateUnreadCountDisplayListInfo({ channelId: channel });
        }
        if (
          shouldCountAsUnread &&
          isCurrentChannel &&
          isVisibleRef.current &&
          conversationInfo &&
          conversationInfo.unread_count_display > 0 &&
          isNewerMessage({
            newTs: newMessage.hu_data.message_ts,
            currentTs: previousLatestTs,
          })
        ) {
          increaseUnreadMessagesInfo({ channelId: channel });
        }
      }
    },
    [id, exactConversationMatch, user?.id],
  );

  const resetMarkedUnread = useCallback(
    (message: ConversationMessageSocket) => {
      const { channel_ts: channel, json_event: jsonEvent } = message;
      const newMessage = JSON.parse(jsonEvent) as ConversationMessage;
      const isCurrentChannel = channel === id;
      if (!isCurrentChannel) {
        resetUnreadMessages({ channelId: channel });
        resetUnreadMessagesInfo({ channelId: channel });
        updateLastRead(channel, newMessage.hu_data.message_ts);
      }

      if (isCurrentChannel && isVisibleRef.current) {
        resetUnreadMessages({
          channelId: channel,
          markedUnreadTs: newMessage.hu_data.message_ts,
        });
      }
    },
    [id, user?.id, exactConversationMatch],
  );

  const handleUpdateMessage = useCallback(
    (message: ConversationMessageSocket) => {
      const jsonEvent = JSON.parse(message.json_event) as ConversationMessage;

      updateMessage({
        channelId: jsonEvent.channel,
        newMessage: jsonEvent,
      });
      updatePinnedMessageCache(jsonEvent.channel, jsonEvent);
    },
    [],
  );
  const handleUpdateReadStatus = useCallback(
    (message: ConversationMessageSocket) => {
      const { channel, metadata } = JSON.parse(
        message.json_event,
      ) as ConversationEventSocket;
      if (metadata?.event_payload.to_message_ts) {
        const readAt = metadata.event_payload.read_at;
        const receivedAt = metadata.event_payload.received_at;
        const toMessageTs = metadata.event_payload.to_message_ts;
        updateMessageReadStatus(channel, toMessageTs, readAt, receivedAt);
      }
    },
    [],
  );

  const handleDeletedMessage = useCallback(
    (message: ConversationMessageSocket) => {
      const jsonEvent = JSON.parse(message.json_event) as ConversationMessage;
      const { channel } = jsonEvent;
      updateMessage({
        channelId: channel,
        newMessage: jsonEvent,
      });
    },
    [],
  );

  const handleUpdateGroupInfo = useCallback(
    (message: ConversationMessageSocket) => {
      const { channel_ts: channel } = message;
      const parsedEvent = JSON.parse(
        message.json_event,
      ) as ConversationEventSocket;
      const { type, user: userId, metadata } = parsedEvent;
      const isTypeLeft = type === MessageTypeSocket.LEFT;
      const isTypeJoined = type === MessageTypeSocket.JOINED;

      if (isTypeLeft) {
        const userLeftId = metadata?.event_payload?.user?.toString();
        const isConnectedUser = userLeftId === user?.id.toString();
        const isCurrentChannel = channel === id;
        if (isConnectedUser && isCurrentChannel) {
          removeConversationFromAllLists(channel);
          navigate(conversationsRoutes.conversations());
        }

        const isKicked = userLeftId !== userId;
        const eventProperties = {
          userId: userLeftId,
          conversationID: channel,
          leftReason: isKicked
            ? CONVERSATION_LEFT_REASON.KICKED
            : CONVERSATION_LEFT_REASON.VOLUNTARY,
          triggeredBy: userId,
        };
        logEvent(EventName.CHATS_2_CONVERSATION_LEFT, eventProperties);
      }
      if (isTypeJoined) {
        const eventProperties = {
          userId: metadata?.event_payload?.users,
          conversationType: CONVERSATION_TYPE.GROUP,
          conversationID: channel,
          triggeredBy: userId,
        };
        logEvent(EventName.CHATS_2_CONVERSATION_JOINED, eventProperties);
      }
      updateMembers({
        channelId: channel,
        type,
        membership: metadata?.event_payload.membership,
        memberships: metadata?.event_payload.memberships,
        userId: user?.id,
      });
    },
    [user?.id, id, navigate],
  );

  const handleReaction = useCallback((message: ConversationMessageSocket) => {
    const jsonEvent = JSON.parse(message.json_event) as ConversationMessage;
    updateMessageReactionsFromEvent(jsonEvent);
  }, []);

  const handleUptateGroup = useCallback(
    (message: ConversationMessageSocket) => {
      const infoEventParsed = JSON.parse(
        message.json_event,
      ) as ConversationEventSocket;
      const { metadata, channel, hu_data: huData } = infoEventParsed;
      const payload = metadata?.event_payload;

      updateGroupData({
        id: channel,
        newName: payload?.newName || '',
        newPictureId: payload?.newPictureId || '',
        newDescription: payload?.newDescription || '',
        version: huData.version,
      });
    },
    [],
  );

  const handleUpdatePropertiesConversation = useCallback(
    (message: ConversationMessageSocket) => {
      const infoEventParsed = JSON.parse(message.json_event) as JsonEventUser;
      const { metadata, channel: channelId } = infoEventParsed;
      const version = metadata?.event_payload.version;
      const payload = metadata?.event_payload.notification_type;
      const pinnedAt = metadata?.event_payload.pinned_at;
      const markedUnreadAt = metadata?.event_payload.marked_unread_at;

      updateChannelProperties({
        pinnedAt,
        channelId,
        version,
        updateFn: channel => ({
          ...channel,
          properties: {
            notification_status: {
              notification_type: payload || NotificationType.ALL,
            },
            pinned_at: pinnedAt,
            marked_unread_at: markedUnreadAt,
          },
        }),
      });
    },
    [],
  );

  const handleUpdatePinnedMessages = useCallback(
    (message: ConversationMessageSocket) => {
      const infoEventParsed = JSON.parse(
        message.json_event,
      ) as ConversationEventSocket;
      removePinnedMessageCache(
        infoEventParsed.channel,
        infoEventParsed.hu_data.message_ts,
      );
    },
    [],
  );
  const handleAddPinnedMessage = useCallback(
    (message: ConversationMessageSocket) => {
      const infoEventParsed = JSON.parse(
        message.json_event,
      ) as ConversationEventSocket;
      const newMessage = createObjectMessage(message);
      addPinnedMessageCache(infoEventParsed.channel, newMessage);
    },
    [],
  );

  const handleUpdateMessageReadByAll = useCallback(
    (message: ConversationMessageSocket) => {
      const infoEventParsed = JSON.parse(
        message.json_event,
      ) as ConversationEventSocket;
      const { metadata, channel } = infoEventParsed;
      if (metadata?.event_payload?.message_ts) {
        const messageTs = metadata?.event_payload?.message_ts;
        const localMessageTs = Date.now();
        updateMessageReadStatus(
          channel,
          messageTs,
          localMessageTs,
          localMessageTs,
        );
      }
    },
    [],
  );

  return {
    resetMarkedUnread,
    handleUpdateMessage,
    handleDeletedMessage,
    handleUpdateReadStatus,
    handleUpdateGroupInfo,
    handleReaction,
    handleUptateGroup,
    handleAddMessage,
    handleUpdatePropertiesConversation,
    handleUpdatePinnedMessages,
    handleAddPinnedMessage,
    handleUpdateMessageReadByAll,
  };
};

export default useHandlerEvents;
