import {chatMembershipsHandler} from '@config/synchroniser';
import {getConversations, getRecent} from '@modules/chats/services';
import {OrchestratorUpdaterService} from '@modules/chats/instances/updaters';
import {CHATS_LOCAL_STORAGE_KEYS} from '@modules/chats/constants';
import {useGroups} from '@modules/chats/store/zustand/useGroups';
import {Conversation, HuDataMember, Message} from '@modules/chats/interfaces';
import {processConversationsInTransaction} from '@modules/chats/db';
import {
  getMessagesWithLocalData,
  mapLatestToMessage,
} from '@modules/chats/utils';
import {FileMessagesLogger} from '@modules/chats/instances/FileMessagesLogger';
import {logUserDBValidationIssues} from '@modules/chats/utils/validators';
import {
  getPersistedHasFetchedAllConversations,
  getPersistedHasFetchedRecentEvents,
  removeItemFromStorage,
  setItemFromStorage,
} from '@services/localStorage';
import {sentryLogs, wait} from '@shared/utils';
import {isFeatureFlagEnabled} from '@stores/featureFlags';

import {MessageRangesHandler} from './MessageRangesHandler';
import {PinnedMessagesUpdaterService} from './updaters';

const RECENT_BATCH_SIZE = 20;
const CONVERSATION_BATCH_SIZE = 100;

function getConversationsWithoutDuplicates(conversations: Conversation[]) {
  const filteredConversationsMap = conversations.reduce<{
    [id: string]: Conversation;
  }>((acc, c) => {
    if (!c.id || typeof c.id !== 'string') {
      sentryLogs.error({
        message: 'Conversation id not defined',
        module: 'CHATS',
        component: 'RecentsHandler',
        data: {
          conversation: c,
        },
      });
      return acc;
    }

    if (!acc[c.id] || acc[c.id].version < c.version) {
      acc[c.id] = c;
    }
    return acc;
  }, {});

  return Object.values(filteredConversationsMap);
}

class RecentsHandlerClass {
  private allConversationsFetched: boolean = false;
  private recentsFetched: boolean = false;

  public getFlags() {
    const allConversationsFetched = getPersistedHasFetchedAllConversations();
    const recentMessagesFetched = getPersistedHasFetchedRecentEvents();
    this.allConversationsFetched = allConversationsFetched;
    this.recentsFetched = recentMessagesFetched;

    return {
      needGetAllConversations: !this.allConversationsFetched,
      needGetRecents: !this.recentsFetched,
    };
  }

  public needGetRecents() {
    return !this.recentsFetched;
  }

  public needGetAllConversations() {
    return !this.allConversationsFetched;
  }

  /**
   * Get recents (top 20) conversations from server if not already fetched.
   */
  public async getRecents() {
    sentryLogs.debug({
      message: 'Fetching recents',
      module: 'CHATS',
      component: 'RecentsHandler',
    });

    await this.fetchRecents();

    // Set the flag to avoid fetching recents again
    setItemFromStorage({
      key: CHATS_LOCAL_STORAGE_KEYS.HasFetchedRecentEvents,
      value: 'true',
      isObject: false,
    });
  }

  /**
   * Get all conversations from server if not already fetched.
   */
  public async getAllConversations() {
    sentryLogs.debug({
      message: 'Fetching all conversations',
      module: 'CHATS',
      component: 'RecentsHandler',
    });

    await this.fetchAllConversations();

    // Set the flag to avoid fetching all conversations again
    setItemFromStorage({
      key: CHATS_LOCAL_STORAGE_KEYS.HasFetchedAllConversations,
      value: 'true',
      isObject: false,
    });
  }

  /**
   * Fetches all conversations from the server, through "/conversations.list" endpoint.
   * This should be use with "recents" to get the full list of conversations.
   */
  private async fetchAllConversations() {
    let nextCursor = 'init' as Nullable<string> | undefined;
    let totalProcessed = 0;
    const promises: Promise<void>[] = [];
    const allConversations: Conversation[] = [];
    const allMessages: Message[] = [];

    const startTime = Date.now();

    while (nextCursor != null) {
      try {
        const {data, nextCursor: nextCursorCand} = await getConversations(
          nextCursor === 'init'
            ? {limit: CONVERSATION_BATCH_SIZE}
            : {cursor: nextCursor, limit: CONVERSATION_BATCH_SIZE},
        );

        if (data.length === 0) {
          break;
        }

        // Get conversations without duplicates, users and latest messages
        const conversations = getConversationsWithoutDuplicates(data);
        const users: HuDataMember[] = [];
        const latestMessages: Message[] = [];
        conversations.forEach(c => {
          if (c.latest) {
            // Log msg.ts for each latest message
            FileMessagesLogger.logMessageTs(c.latest.ts);

            latestMessages.push(
              mapLatestToMessage({
                message: c.latest,
                channel: c.id,
              }),
            );
          }
          if (c.other_user) {
            logUserDBValidationIssues(
              c.other_user,
              'Recents: fetchAllConversations',
            );
            users.push(c.other_user);
          }
        });

        // Inject local data to each message (mentions & measuring height)
        const messages = await getMessagesWithLocalData(latestMessages);

        // Process this batch immediately in a transaction
        promises.push(
          processConversationsInTransaction({
            conversations,
            messages,
            users,
          }),
        );

        totalProcessed += conversations.length;

        // Update next cursor
        nextCursor = nextCursorCand;

        // Save conversations and messages to process later
        allConversations.push(...conversations);
        allMessages.push(...messages);
      } catch (e) {
        await wait(2000);
        continue;
      }
    }

    sentryLogs.debug({
      message: 'All conversations fetched',
      module: 'CHATS',
      component: 'RecentsHandler',
      data: {
        totalProcessed,
        totalMs: Date.now() - startTime,
      },
    });

    // Wait for all batches to be processed
    try {
      await Promise.all(promises);
    } catch (error) {
      sentryLogs.error({
        message: 'Failed to persist conversations batch to DB',
        module: 'CHATS',
        component: 'RecentsHandler.fetchAllConversations',
        error,
        data: {totalProcessed},
      });
      throw error;
    }

    // Save into stores
    allConversations.forEach(conversation => {
      if (conversation.latest && conversation.is_channel) {
        chatMembershipsHandler.addToQueue(conversation.id);
      }

      // Update message ranges for each conversation with its latest message
      // Each conversation only has 1 message (latest), so isEnd=true
      if (conversation.latest) {
        MessageRangesHandler.addMessagesAndUpdateRanges(
          conversation.id,
          [
            mapLatestToMessage({
              message: conversation.latest,
              channel: conversation.id,
            }),
          ],
          {isStart: false, isEnd: true},
        );
      }
    });

    // Hydrate the attachments store for every latest message before handing
    // the conversation list to useGroups. Without this, the latest lands in
    // `messagesWindow` with its attachments but the asset store stays empty —
    // and the orchestrator's `loadMessagesAbove` only fetches messages *older*
    // than the latest, so it never repairs the gap.
    //
    // Reactions are skipped: `ConversationLatest` doesn't carry the field
    // (see `mapLatestFromServer`), and forwarding messages without reactions
    // to `processWithMessages` would land in its "clear all" branch and
    // wipe persisted reactions for every latest's `client_msg_id`.
    OrchestratorUpdaterService.processLatestsPostDB(allMessages, {
      processReactions: false,
    });

    useGroups.getState().saveAllConversations(allConversations, allMessages);
  }

  /**
   * Fetches the top 20 recent conversations from the server. Each conversation has latest 100 messages.
   * This is an expensive operation from server perspective, so it should be done only once per user (first login).
   */
  private async fetchRecents() {
    const startTime = Date.now();
    let data: {
      group_conversation: Conversation;
      latest_messages: Message[];
    }[] = [];
    let retryCount = 1;

    // eslint-disable-next-line no-constant-condition
    while (true) {
      try {
        const response = await getRecent({
          limit: RECENT_BATCH_SIZE,
        });
        data = response.data;
        break;
      } catch (e) {
        if (retryCount === 3) {
          throw new Error('Failed to fetch recents', {cause: e as Error});
        }
        retryCount++;
        await wait(2000);
      }
    }

    if (!data) {
      return;
    }

    // Log all msg.ts before processing
    data.forEach(conversation => {
      conversation.latest_messages.forEach(msg => {
        FileMessagesLogger.logMessageTs(msg.ts);
      });
    });

    await OrchestratorUpdaterService.saveRecentConversations(data);

    const isPinMessageEnabled = isFeatureFlagEnabled('CHATS_V2_PIN_MESSAGE');

    // Update message ranges for each conversation
    // Recents have up to 100 messages per conversation, isEnd=true (latest messages)
    data.forEach(item => {
      const channelId = item.group_conversation.id;

      if (item.latest_messages.length > 0) {
        MessageRangesHandler.addMessagesAndUpdateRanges(
          channelId,
          item.latest_messages,
          {isStart: false, isEnd: true},
        );
      }

      // Add channels to memberships queue
      if (item.group_conversation.is_channel) {
        chatMembershipsHandler.addToQueue(channelId);
      }

      if (isPinMessageEnabled) {
        PinnedMessagesUpdaterService.getPinnedMessages(channelId).catch(
          () => {},
        );
      }
    });

    sentryLogs.debug({
      message: 'Recents fetched',
      module: 'CHATS',
      component: 'RecentsHandler',
      data: {
        total: data.length,
        totalMs: Date.now() - startTime,
      },
    });
  }

  public clear() {
    this.allConversationsFetched = false;
    this.recentsFetched = false;
    removeItemFromStorage(CHATS_LOCAL_STORAGE_KEYS.HasFetchedRecentEvents);
    removeItemFromStorage(CHATS_LOCAL_STORAGE_KEYS.HasFetchedAllConversations);
  }
}

export const RecentsHandler = new RecentsHandlerClass();
