import {db} from '@db/instance';
import {Asset, DeliverStatus, Message} from '@modules/chats/interfaces';
import {
  calculateTextHeight,
  mapMessageToDB,
  mapAssetToDB,
  mapMessageFromDB,
  fontScale,
} from '@modules/chats/utils';
import {asyncMap} from '@shared/utils';

import {
  getPendingMessagesDB,
  insertMessages,
  getFirstMessagesForGroupMsg as getFirstMessagesForGroupInternal,
  getLatestMessagesForGroupMsg as getLatestMessagesForGroupInternal,
  insertRecalculatedMessages,
  updateDeliverStatusInternal,
} from '../repositories/messages';
import {saveAssets} from '../repositories/assets';
import {InsertAsset, InsertMessage} from '../interfaces';

/**
 * This function is used to get the messages with the height calculated.
 * @param messages - The messages to get the height for.
 * @returns The messages with the height calculated.
 */
export const getMessagesWithHeight = async (messages: Message[]) => {
  const recalculatedMessages: Message[] = [];

  const promises = messages.map(async message => {
    if (message.associated_font_scale === fontScale) {
      return message;
    }
    const height = await calculateTextHeight({
      text: message.formatted_text ?? '',
      blocks: message.blocks,
    });
    const result: Message = {
      ...message,
      height,
      associated_font_scale: fontScale,
    };
    recalculatedMessages.push(result);
    return result;
  });

  const results = await Promise.all(promises);

  if (recalculatedMessages.length) {
    await saveRecalculatedMessages(recalculatedMessages);
  }
  return results;
};

export const getPendingMessages = async () => {
  const result = await getPendingMessagesDB();
  const mappedMessages = result.map(message =>
    mapMessageFromDB({
      message,
      assets: message.assets,
    }),
  );
  return getMessagesWithHeight(mappedMessages);
};

export const getLatestMessagesForGroup = async (
  conversationId: string,
  limit?: number,
) => {
  const messages = await getLatestMessagesForGroupInternal(
    conversationId,
    limit,
  );
  const mappedMessages = messages.map(message =>
    mapMessageFromDB({
      message,
      assets: message.assets,
      reactions: message.reactions,
    }),
  );
  return getMessagesWithHeight(mappedMessages);
};

export const getFirstMessagesForGroup = async (groupId: string) => {
  const messages = await getFirstMessagesForGroupInternal(groupId);
  const mappedMessages = messages.map(message =>
    mapMessageFromDB({
      message,
      assets: message.assets,
      reactions: message.reactions,
    }),
  );
  return getMessagesWithHeight(mappedMessages);
};

export const saveRecalculatedMessages = async (messages: Message[]) => {
  const messagesToInsert: InsertMessage[] = [];

  for (const message of messages) {
    messagesToInsert.push(mapMessageToDB(message));
  }

  await insertRecalculatedMessages(messagesToInsert);
};

export const saveNewMessages = async (messages: Message[]) => {
  const messagesToInsert: InsertMessage[] = [];
  const assetsToInsert: Record<string, InsertAsset[]> = {};

  await asyncMap(messages, async message => {
    const messageWithHeight = {
      ...message,
      height: await calculateTextHeight({
        text: message.formatted_text,
        blocks: message.blocks,
      }),
    };

    messagesToInsert.push(mapMessageToDB(messageWithHeight));

    if (message.attachments?.length) {
      assetsToInsert[message.client_msg_id] =
        message.attachments.map(mapAssetToDB);
    }
  });

  const assets = Object.entries(assetsToInsert);
  await db.instance.transaction(async tx => {
    await insertMessages(messagesToInsert, tx);

    if (assets.length) {
      await asyncMap(assets, ([messageId, messageAssets]) =>
        saveAssets(messageAssets, messageId, tx),
      );
    }
  });
};

export const updateDeliverStatus = async ({
  fromMessageTs,
  toMessageTs,
  channelId,
  deliverStatus,
}: {
  fromMessageTs: string;
  toMessageTs: string;
  channelId: string;
  deliverStatus: DeliverStatus;
}) => {
  await updateDeliverStatusInternal({
    fromMessageTs,
    toMessageTs,
    channelId,
    deliverStatus,
  });
};

export const updateAssetsByMessage = async (
  assets: Asset[],
  messageId: string,
) => {
  const assetsToSave = assets.map(mapAssetToDB);
  await saveAssets(assetsToSave, messageId);
};
