import {useCallback, useState} from 'react';
import NetInfo from '@react-native-community/netinfo';
import {TaggedUser} from '@interfaces/taggedUsers';
import {
  Message,
  MessageStatus,
  MediaEntity,
  MediaType,
} from '@modules/chats/interfaces';
import {
  prepareMessageWithMentions,
  mapMediaArrayToAssets,
  mapMediaToService,
  mapMessageToReplyBlock,
  getHighlightedSpans,
  mapMediaToAsset,
  mapPendingAsset,
} from '@modules/chats/utils';
import {LOCAL_INITIAL_VERSION} from '@modules/chats/constants';
import {OrchestratorUpdaterService} from '@modules/chats/instances/updaters';
import {
  isInLatestMessageWindow,
  resetMessageWindow,
} from '@modules/chats/store/zustand/useGroups';
import {useUser} from '@redux/selectors';
import {persistedUserId} from '@services/localStorage';
import {getCompleteName, getUlid} from '@shared/utils';

import {fileHandler} from '../instances/FileHandler';
import {messagesQueue} from '../instances/MessagesQueue';
import {getStatFromUri, uploadAudioFile} from '../utils';

interface Params {
  channel: string;
  onMessageGenerated?: () => void;
}

interface SendMessageParams {
  taggedUsers?: TaggedUser[];
  replyMessage?: Nullable<Message>;
  generatedByAi?: boolean;
}

export const usePostMessage = ({channel, onMessageGenerated}: Params) => {
  const user = useUser();
  const fullName = getCompleteName(user);
  const [isRetrying, setIsRetrying] = useState(false);

  const ensureLatestWindow = async () => {
    if (!isInLatestMessageWindow(channel)) {
      await resetMessageWindow(channel);
    }
  };

  /**
   * Generate a temporal local message, save it in the store and the local DB
   */
  const generateTemporaryMessage = async ({
    text = '',
    media,
    replyMessage,
    taggedUsers,
    generatedByAi,
  }: {
    text?: string;
    media?: MediaEntity[];
  } & SendMessageParams) => {
    let message: Message | null = null;

    const formattedText = text.trim();
    let textToServer = formattedText;

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

      const trimStart = text.trimStart();
      const leftDiff = text.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(formattedText, newTaggedUsers);
    }

    // Prepare highlighted spans for mentions
    const highlighted_spans = taggedUsers?.length
      ? getHighlightedSpans(textToServer)
      : [];

    try {
      const id = getUlid();
      message = {
        channel,
        text: textToServer,
        formatted_text: formattedText,
        ts: id,
        client_msg_id: id,
        user: `${persistedUserId}`,
        status: MessageStatus.Sending,
        username: fullName,
        sender_profile_picture: user.profilePicture || undefined,
        version: LOCAL_INITIAL_VERSION,
        height: !text && media?.length ? 0 : -1,
        attachments: media ? mapMediaArrayToAssets(media, channel) : undefined,
        blocks: replyMessage ? mapMessageToReplyBlock(replyMessage) : undefined,
        highlighted_spans,
        generated_by: generatedByAi
          ? {
              name: 'note-summarizer',
              origin: 'app',
            }
          : undefined,
      };

      onMessageGenerated?.();

      if (media) {
        // Save media in the fileHandler
        const files = media.reduce((acc, mediaItem) => {
          if (mediaItem.fileAsset) {
            acc.push({
              assetId: mediaItem.fileAsset.id,
              uri: mediaItem.localUri,
              remoteUrl: mediaItem.fileAsset.url,
              messageId: message!.client_msg_id,
              filename: mediaItem.name,
              asset: mapMediaToAsset(mediaItem, channel),
              channel,
            });
          }
          return acc;
        }, [] as Parameters<typeof fileHandler.setFileBatch>[0]);

        fileHandler.setFileBatch(files);
      }
      // Save the temporary message in the db
      await OrchestratorUpdaterService.processMessage(message);

      return message;
    } catch (error) {
      if (message) {
        OrchestratorUpdaterService.processMessage({
          ...message,
          status: MessageStatus.Error,
        });
      }

      return null;
    }
  };

  /**
   * Send text message in `channel`
   */
  const sendMessage = async (text: string, params: SendMessageParams = {}) => {
    await ensureLatestWindow();
    const message = await generateTemporaryMessage({text, ...params});
    if (!message) {
      return;
    }

    messagesQueue.addMessage(message);
  };

  /**
   * Send media message (with an optional text) in `channel`
   */
  const sendMedia = async (
    media: MediaEntity[],
    params: SendMessageParams & {text?: string} = {},
  ) => {
    await ensureLatestWindow();
    const message = await generateTemporaryMessage({
      media,
      text: params.text,
      ...params,
    });

    if (!message) {
      return;
    }

    messagesQueue.addMessage({
      ...message,
      attachments: mapMediaToService(media),
    });
  };

  const sendVoiceMessage = useCallback(
    async ({
      uri,
      durationMs,
      replyMessage,
    }: {
      uri: string;
      durationMs: number;
      replyMessage?: Nullable<Message>;
    }) => {
      await ensureLatestWindow();
      let message: Message | null = null;

      try {
        // Create message with loading state for the media
        const id = getUlid();
        const assetId = Date.now();
        const {size, filename} = await getStatFromUri(uri);
        const pendingAsset: MediaEntity = {
          id: assetId.toString(),
          type: MediaType.AUDIO,
          fileAsset: null,
          status: 'uploading',
          contentType: 'audio/mp4',
          localUri: uri,
          name: filename,
          sizeInBytes: size,
          metadata: {
            durationInMs: durationMs,
          },
        };
        message = {
          channel,
          ts: id,
          client_msg_id: id,
          user: `${persistedUserId}`,
          text: '',
          formatted_text: '',
          highlighted_spans: [],
          status: MessageStatus.Sending,
          username: fullName,
          sender_profile_picture: user.profilePicture || undefined,
          version: LOCAL_INITIAL_VERSION,
          height: 0,
          blocks: replyMessage
            ? mapMessageToReplyBlock(replyMessage)
            : undefined,
          pendingAssets: [pendingAsset],
        };
        onMessageGenerated?.();

        fileHandler.setFileBatch([
          {
            assetId,
            uri: pendingAsset.localUri,
            remoteUrl: pendingAsset.fileAsset?.url ?? '',
            messageId: message.client_msg_id,
            filename: pendingAsset.name,
            asset: mapPendingAsset(pendingAsset),
            channel,
          },
        ]);

        // Save message right away to show the loading state
        OrchestratorUpdaterService.processMessage(message);

        // If not connected, avoid uploading the media to send later
        const networkState = await NetInfo.fetch();
        const isOnline =
          networkState.isConnected && networkState.isInternetReachable;
        if (!isOnline) {
          return;
        }

        // Send message
        messagesQueue.addMessage(message);
      } catch (error) {
        if (message) {
          OrchestratorUpdaterService.processMessage({
            ...message,
            status: MessageStatus.Error,
          });
        }
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [],
  );

  const sendGif = async (
    gif: MediaEntity,
    params: Pick<SendMessageParams, 'replyMessage'> = {},
  ) => {
    await ensureLatestWindow();
    const message = await generateTemporaryMessage({
      media: [gif],
      ...params,
    });

    if (!message) {
      return;
    }

    messagesQueue.addMessage({
      ...message,
      attachments: mapMediaToService([gif]),
    });
  };

  const retrySending = async (message: Message) => {
    if (isRetrying) {
      return;
    }

    setIsRetrying(true);
    const newMessage: Message = {...message};
    const pendingAsset = newMessage.pendingAssets?.at(0);

    // If there is a pending asset, we need to upload it
    if (pendingAsset) {
      const isAudio = pendingAsset.type === MediaType.AUDIO;
      if (isAudio) {
        const asset = await uploadAudioFile({
          uri: pendingAsset.localUri,
          durationMs: pendingAsset.metadata?.durationInMs ?? 0,
        });
        newMessage.attachments = mapMediaToService([asset]);
      } else {
        // TODO: other types of attachments
      }
    }

    messagesQueue.addMessage(newMessage);
    setIsRetrying(false);
  };

  return {sendMessage, sendMedia, sendVoiceMessage, sendGif, retrySending};
};
