import {useTranslation} from 'react-i18next';
import {useNetInfo} from '@react-native-community/netinfo';
import {
  localEventsHandler,
  LocalEventTypes,
  LocalEvent,
} from '@config/synchroniser';
import {TaggedUser} from '@interfaces/taggedUsers';
import {
  OrchestratorUpdaterService,
  MessagesUpdaterService,
  ReactionsUpdaterService,
} from '@modules/chats/instances/updaters';
import {getUserReactions} from '@modules/chats/store';
import {
  getHighlightedSpans,
  getTextWithHighlights,
  mapMessageFromDB,
  mapMessageWithLocalEvents,
  prepareMessageWithMentions,
} from '@modules/chats/utils';
import {useGroups} from '@modules/chats/store/zustand/useGroups';
import {LOCAL_INITIAL_VERSION, PinDuration} from '@modules/chats/constants';
import {showSnackbar} from '@redux/dispatchers';
import {persistedUserId} from '@services/localStorage';
import {AMPLITUDE_EVENTS} from '@shared/constants';
import {getUlid, logAmplitudeEvent, sentryLogs} from '@shared/utils';

import {getMessageById} from '../db';
import {fileHandler} from '../instances/FileHandler';
import {HighlightedSpan} from '../interfaces';
import {messagesQueue} from '../instances/MessagesQueue';
import {useMessageActionsContext} from '../contexts/MessageActionsContext';
import {PinnedMessagesUpdaterService} from '../instances/updaters';

interface Params {
  channel: string;
}

/**
 * Get `message` object from the store or the database by `client_msg_id`
 */
const getMessage = async (channel: string, clientMsgId: string) => {
  const group = useGroups.getState().groups[channel];
  const message = group
    ? [...group.messagesWindow, ...group.pendingMessages].find(
        item => item.client_msg_id === clientMsgId,
      )
    : null;

  if (message && message.version !== LOCAL_INITIAL_VERSION) {
    return message;
  }
  const messageFromDB = await getMessageById(clientMsgId, {
    withReactions: true,
  });

  return messageFromDB
    ? mapMessageFromDB({
        message: messageFromDB,
        assets: messageFromDB.assets,
        reactions: messageFromDB.reactions,
      })
    : null;
};

export const useMessageActions = (params: Params) => {
  const {t} = useTranslation();
  const {isInternetReachable} = useNetInfo();
  const {togglePinFailed} = useMessageActionsContext();

  const editMessage = async ({
    clientMsgId,
    newText,
    taggedUsers,
  }: {
    clientMsgId: string;
    newText: string;
    taggedUsers: TaggedUser[];
  }) => {
    const message = await getMessage(params.channel, clientMsgId);

    if (!message || message.version === LOCAL_INITIAL_VERSION) {
      sentryLogs.warn({
        message: 'Message action cancelled, message not synced with server',
        module: 'CHATS',
        component: 'useMessageActions',
        data: {
          clientMsgId,
          action: 'editMessage',
          channel: params.channel,
        },
      });
      return;
    }

    // tag-users package returns the string with mentions (e.g. 'Hello @John Doe')
    const text = newText.trim();
    let textToServer = text;
    let highlightedSpans: HighlightedSpan[] = [];
    let newFormattedText = text;

    // In case of tagged users, we need to prepare the message with its original user id
    if (taggedUsers?.length) {
      let newTaggedUsers = [...taggedUsers];

      const trimStart = newText.trimStart();
      const leftDiff = newText.length - trimStart.length;

      // If trim modified the text, we need to realocate taggedUsers positions
      if (leftDiff > 0) {
        newTaggedUsers = newTaggedUsers.map(taggedUser => ({
          ...taggedUser,
          start: taggedUser.start - leftDiff,
        }));
      }

      textToServer = prepareMessageWithMentions(text, newTaggedUsers);

      // Prepare highlighted spans for mentions
      highlightedSpans = getHighlightedSpans(textToServer);

      // Update formatted text
      newFormattedText = await getTextWithHighlights({
        message: textToServer,
        highlightedSpans: highlightedSpans,
      });
    }

    const localEvent: LocalEvent = {
      id: clientMsgId,
      type: LocalEventTypes.CHATS2_EDIT_MESSAGE,
      unique: true,
      data: {
        channel: params.channel,
        ts: message.ts,
        text: textToServer,
        highlighted_spans: highlightedSpans,
        formatted_text: newFormattedText,
        idempotencyKey: getUlid(),
      },
    };

    // Update layers
    OrchestratorUpdaterService.processMessage(
      mapMessageWithLocalEvents(message, [localEvent]),
    );

    // Process the event
    localEventsHandler.processEvent(localEvent);
  };

  const deleteMessage = async ({clientMsgId}: {clientMsgId: string}) => {
    const message = await getMessage(params.channel, clientMsgId);

    if (!message || message.version === LOCAL_INITIAL_VERSION) {
      if (message?.version === LOCAL_INITIAL_VERSION) {
        const messageUpdated = mapMessageWithLocalEvents(message, [
          {
            id: clientMsgId,
            type: LocalEventTypes.CHATS2_DELETE_MESSAGE,
            unique: true,
            data: {
              channel: params.channel,
              ts: message.ts,
              idempotencyKey: getUlid(),
            },
          },
        ]);
        await MessagesUpdaterService.deleteUnsentMessage(messageUpdated);
        messagesQueue.removeMessage(message.client_msg_id);
      } else {
        sentryLogs.warn({
          message: 'Message action cancelled, message not synced with server',
          module: 'CHATS',
          component: 'useMessageActions',
          data: {
            clientMsgId,
            action: 'deleteMessage',
            channel: params.channel,
          },
        });
      }
      return;
    }

    const localEvent: LocalEvent = {
      id: clientMsgId,
      type: LocalEventTypes.CHATS2_DELETE_MESSAGE,
      unique: true,
      data: {
        channel: params.channel,
        ts: message.ts,
        idempotencyKey: getUlid(),
      },
    };

    const messageUpdated = mapMessageWithLocalEvents(message, [localEvent]);

    // Update layers
    OrchestratorUpdaterService.processMessage(messageUpdated);

    // Process the event
    localEventsHandler.processEvent(localEvent);

    if (message.attachments) {
      fileHandler.processAttachmentsBatch([
        {
          attachments: message.attachments,
          messageId: message.client_msg_id,
          messageMetadata: {
            hidden: true,
            channel: params.channel,
          },
        },
      ]);
    }
  };

  const toggleReaction = async ({
    clientMsgId,
    name,
  }: {
    clientMsgId: string;
    name: string;
  }) => {
    try {
      const message = await getMessage(params.channel, clientMsgId);
      const userReactions = getUserReactions(clientMsgId);

      if (!message) {
        throw new Error('Message not found');
      }

      if (message.version === LOCAL_INITIAL_VERSION) {
        throw new Error('Message not synced with server');
      }

      const action = userReactions.includes(name) ? 'remove' : 'add';

      await Promise.all([
        ReactionsUpdaterService.optimisticUpdateReaction({
          channel: params.channel,
          reactionName: name,
          userId: persistedUserId!,
          action,
          clientMsgId,
        }),
        localEventsHandler.processEvent({
          id: `${clientMsgId}:${name}`,
          type: LocalEventTypes.CHATS2_TOGGLE_REACTION,
          unique: true,
          data: {
            channel: params.channel,
            ts: message.ts,
            name,
            type: action,
            idempotencyKey: getUlid(),
          },
        }),
      ]);
    } catch (error) {
      sentryLogs.error({
        error,
        message: 'Toggle reaction error',
        module: 'CHATS',
        component: 'useMessageActions',
        data: {
          clientMsgId,
          action: 'toggleReaction',
          channel: params.channel,
        },
      });
    }
  };

  const togglePin = async ({
    clientMsgId,
    duration,
    tsToReplace,
  }: {
    clientMsgId: string;
    duration?: PinDuration;
    tsToReplace?: string;
  }) => {
    let isUnpin = false;
    try {
      const message = await getMessage(params.channel, clientMsgId);

      if (!message) {
        sentryLogs.warn({
          message: 'Message action cancelled, message not found',
          module: 'CHATS',
          component: 'useMessageActions',
          data: {
            clientMsgId,
            action: 'togglePin',
            channel: params.channel,
          },
        });
        return;
      }

      const ts = message.ts;
      const channel = params.channel;
      const idempotencyKey = getUlid();

      isUnpin = PinnedMessagesUpdaterService.isPinned(channel, ts);

      if (isUnpin) {
        await PinnedMessagesUpdaterService.remove({
          channel,
          ts,
          idempotencyKey,
        });
        logAmplitudeEvent(AMPLITUDE_EVENTS.CHATS_2_MESSAGE_UNPINNED, {
          conversationId: channel,
          messageId: ts,
        });
      } else {
        const pinDuration = duration ?? 'always';
        await PinnedMessagesUpdaterService.add({
          channel,
          message,
          duration: pinDuration,
          idempotencyKey,
          tsToReplace,
        });
        logAmplitudeEvent(AMPLITUDE_EVENTS.CHATS_2_MESSAGE_PINNED, {
          conversationId: channel,
          messageId: ts,
          duration: pinDuration,
        });
      }
    } catch (error) {
      if (isInternetReachable === false) {
        togglePinFailed();
      } else {
        showSnackbar({
          title: t(isUnpin ? 'chat.pin.unpin_failed' : 'chat.pin.pin_failed'),
          variant: 'error',
        });
      }
      sentryLogs.error({
        error,
        message: 'Toggle pin error',
        module: 'CHATS',
        component: 'useMessageActions',
        data: {
          clientMsgId,
          action: 'togglePin',
          channel: params.channel,
        },
      });
    }
  };

  return {
    editMessage,
    deleteMessage,
    toggleReaction,
    togglePin,
  };
};
