import {AppState, AppStateStatus, NativeEventSubscription} from 'react-native';
import {ulid} from 'react-native-ulid-jsi';
import {localEventsHandler, LocalEventTypes} from '@config/synchroniser';
import {OrchestratorUpdaterService} from '@modules/chats/instances/updaters';
import {createChannelDebounce} from '@modules/chats/utils/debounce';
import {postConversationMarkReceived} from '@modules/chats/services';
import {useGroups} from '@modules/chats/store/zustand/useGroups';
import {sentryLogs} from '@shared/utils/logs';

enum PendingOperationType {
  CONVERSATION_MARK = 'CONVERSATION_MARK',
  MESSAGE_RECEIVED = 'MESSAGE_RECEIVED',
}

interface ConversationMarkRequest {
  channel: string;
  latestMessageTs: string;
  idempotencyKey: string;
}

interface MessageReceivedRequest {
  channel: string;
  messageTs: string;
  idempotencyKey: string;
}

type PendingOperation =
  | {
      type: PendingOperationType.CONVERSATION_MARK;
      request: ConversationMarkRequest;
    }
  | {
      type: PendingOperationType.MESSAGE_RECEIVED;
      request: MessageReceivedRequest;
    };

/**
 * ConversationMarker class handles marking conversations as read.
 * It uses debouncing for each channel to optimize API calls and avoid excessive requests.
 * It also queues operations when the app is not active and processes them when it becomes active again.
 */
class ConversationMarker {
  /**
   * Queue of pending operations to be processed when app becomes active
   */
  private pendingOperationsQueue: PendingOperation[] = [];

  /**
   * AppState event subscription
   */
  private appStateSubscription?: NativeEventSubscription;

  /**
   * Debounced function for processing conversation mark requests per channel.
   */
  private debouncedProcessConversationMark = createChannelDebounce(
    (_: string, request: ConversationMarkRequest) =>
      this.processConversationMark(request),
    1000,
  );

  private debouncedProcessMessageReceived = createChannelDebounce(
    (_: string, request: MessageReceivedRequest) =>
      this.processMessageReceived(request),
    1000,
  );

  constructor() {
    this.initializeAppStateListener();
  }

  /**
   * Initialize listener for AppState changes to process queued operations
   */
  private initializeAppStateListener(): void {
    this.appStateSubscription = AppState.addEventListener(
      'change',
      this.onAppStateChange,
    );
  }

  /**
   * Handle AppState changes and process queued operations when app becomes active
   */
  private onAppStateChange = (nextAppState: AppStateStatus): void => {
    if (nextAppState === 'active' && this.pendingOperationsQueue.length > 0) {
      this.processPendingOperations();
    }
  };

  /**
   * Process all pending operations in the queue
   */
  private async processPendingOperations(): Promise<void> {
    const operations = [...this.pendingOperationsQueue];
    this.pendingOperationsQueue = [];

    for (const operation of operations) {
      try {
        if (operation.type === PendingOperationType.CONVERSATION_MARK) {
          await this.executeConversationMark(operation.request);
        } else if (operation.type === PendingOperationType.MESSAGE_RECEIVED) {
          this.processMessageReceived(operation.request);
        }
      } catch (error) {
        sentryLogs.error({
          message: 'Failed to process pending operation',
          module: 'CHATS',
          component: 'ConversationMarker',
          data: {
            error: error instanceof Error ? error.message : String(error),
          },
        });
      }
    }
  }

  /**
   * Execute the actual conversation mark operation
   */
  private async executeConversationMark(
    request: ConversationMarkRequest,
  ): Promise<void> {
    await Promise.all([
      OrchestratorUpdaterService.markConversationAsRead(
        request.channel,
        request.latestMessageTs,
      ),
      localEventsHandler.processEvent({
        data: {
          channel: request.channel,
          idempotencyKey: request.idempotencyKey,
          ts: request.latestMessageTs,
        },
        id: request.channel,
        type: LocalEventTypes.CHATS2_READ_CHANNEL,
        unique: true,
      }),
    ]);
  }

  /**
   * Mark a conversation as read up to a specific message timestamp.
   * This method is debounced per channel to avoid excessive API calls.
   *
   * @param channel - The conversation channel ID
   * @param latestMessageTs (optional) - The timestamp of the latest message in the conversation (if not provided, the latest message from the stored conversation will be used)
   */
  public markConversationAsRead(
    channel: string,
    latestMessageTs?: string,
  ): void {
    let latestMessageTsToUse = latestMessageTs;

    if (!latestMessageTsToUse) {
      const latestMessage =
        useGroups.getState().groups[channel]?.groupInfo?.latest;
      latestMessageTsToUse = latestMessage?.ts;
    }

    if (!latestMessageTsToUse) {
      return;
    }

    const request: ConversationMarkRequest = {
      channel,
      latestMessageTs: latestMessageTsToUse,
      idempotencyKey: ulid(),
    };

    // Trigger the debounced processing for this specific channel
    this.debouncedProcessConversationMark(channel, request);
  }

  public markMessageAsReceived(channel: string, messageTs: string): void {
    // We dont want to mark as a received for now. This is pending to defined from the product team what will be the strategy of this feature.
    return;

    const request: MessageReceivedRequest = {
      channel,
      messageTs,
      idempotencyKey: ulid(),
    };
    if (AppState.currentState === 'active') {
      this.debouncedProcessMessageReceived(channel, request);
    } else {
      this.pendingOperationsQueue.push({
        type: PendingOperationType.MESSAGE_RECEIVED,
        request,
      });
    }
  }

  /**
   * Process marking a specific conversation as read.
   * This method is called by the debounced function for each channel.
   */
  private async processConversationMark(
    request: ConversationMarkRequest,
  ): Promise<void> {
    try {
      if (AppState.currentState === 'active') {
        await this.executeConversationMark(request);
      } else {
        this.pendingOperationsQueue.push({
          type: PendingOperationType.CONVERSATION_MARK,
          request,
        });
      }
    } catch (error) {
      // Note: LocalStateUpdater will handle rollback if needed
      sentryLogs.error({
        message: 'Failed to mark conversation as read',
        module: 'CHATS',
        component: 'ConversationMarker',
        data: {
          error: error instanceof Error ? error.message : String(error),
        },
      });
    }
  }

  private processMessageReceived(request: MessageReceivedRequest): void {
    try {
      postConversationMarkReceived({
        channel: request.channel,
        last_received_message_ts: request.messageTs,
        idempotencyKey: request.idempotencyKey,
      });
    } catch (error) {
      // Note: LocalStateUpdater will handle rollback if needed
      sentryLogs.error({
        message: 'Failed to mark message as received',
        module: 'CHATS',
        component: 'ConversationMarker',
        data: {
          error: error instanceof Error ? error.message : String(error),
        },
      });
    }
  }

  /**
   * Cancel any pending debounced operations for a specific channel.
   * Useful for cleanup when the user navigates away.
   */
  public cancelPendingOperations(channel: string): void {
    this.debouncedProcessConversationMark.cancel(channel);
    this.debouncedProcessMessageReceived.cancel(channel);
  }

  /**
   * Flush any pending operations immediately for all channels.
   */
  public flushPendingOperations(): void {
    this.debouncedProcessConversationMark.flush();
    this.debouncedProcessMessageReceived.flush();
  }

  /**
   * Clean up resources and listeners
   */
  public destroy(): void {
    this.appStateSubscription?.remove();
    this.pendingOperationsQueue = [];
  }
}

export const conversationMarker = new ConversationMarker();
