import { QueuedMessage, MessageQueue } from "../shared/types"
import { logApp } from "./debug"
import { getRendererHandlers } from "@egoist/tipc/main"
import { RendererHandlers } from "./renderer-handlers"
import { WINDOWS } from "./window"

/**
 * Service for managing message queues per conversation.
 * When message queuing is enabled, users can submit messages while an agent session is active.
 * These messages are queued and processed sequentially after the current session completes.
 */
class MessageQueueService {
  private static instance: MessageQueueService | null = null
  private queues: Map<string, QueuedMessage[]> = new Map()
  // Track which conversations are currently being processed to prevent concurrent processing
  private processingConversations: Set<string> = new Set()
  // Track which conversations have their queue paused (e.g., after kill switch)
  private pausedConversations: Set<string> = new Set()

  static getInstance(): MessageQueueService {
    if (!MessageQueueService.instance) {
      MessageQueueService.instance = new MessageQueueService()
    }
    return MessageQueueService.instance
  }

  private constructor() {}

  /**
   * Pause queue processing for a conversation.
   * When paused, processQueuedMessages will not process any more messages.
   * Used by kill switch to prevent executing the next queued message after stopping.
   */
  pauseQueue(conversationId: string): void {
    this.pausedConversations.add(conversationId)
    logApp(`[MessageQueueService] Paused queue for ${conversationId}`)
    this.emitQueueUpdate(conversationId)
  }

  /**
   * Resume queue processing for a conversation.
   */
  resumeQueue(conversationId: string): void {
    this.pausedConversations.delete(conversationId)
    logApp(`[MessageQueueService] Resumed queue for ${conversationId}`)
    this.emitQueueUpdate(conversationId)
  }

  /**
   * Check if a conversation's queue is paused.
   */
  isQueuePaused(conversationId: string): boolean {
    return this.pausedConversations.has(conversationId)
  }

  /**
   * Try to acquire a processing lock for a conversation.
   * Returns true if lock acquired, false if already being processed or paused.
   */
  tryAcquireProcessingLock(conversationId: string): boolean {
    if (this.pausedConversations.has(conversationId)) {
      logApp(`[MessageQueueService] Queue is paused for ${conversationId}, skipping`)
      return false
    }
    if (this.processingConversations.has(conversationId)) {
      logApp(`[MessageQueueService] Already processing queue for ${conversationId}, skipping`)
      return false
    }
    this.processingConversations.add(conversationId)
    logApp(`[MessageQueueService] Acquired processing lock for ${conversationId}`)
    return true
  }

  /**
   * Release the processing lock for a conversation.
   */
  releaseProcessingLock(conversationId: string): void {
    this.processingConversations.delete(conversationId)
    logApp(`[MessageQueueService] Released processing lock for ${conversationId}`)
  }

  /**
   * Check if a conversation is currently being processed.
   */
  isProcessing(conversationId: string): boolean {
    return this.processingConversations.has(conversationId)
  }

  private generateMessageId(): string {
    return `qmsg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`
  }

  /**
   * Add a message to the queue for a conversation
   */
  enqueue(
    conversationId: string,
    text: string,
    sessionId?: string,
    launchState?: QueuedMessage["launchState"],
  ): QueuedMessage {
    const message: QueuedMessage = {
      id: this.generateMessageId(),
      conversationId,
      sessionId,
      ...(launchState ? { launchState } : {}),
      text,
      createdAt: Date.now(),
      status: "pending",
    }

    const queue = this.queues.get(conversationId) || []
    queue.push(message)
    this.queues.set(conversationId, queue)

    logApp(`[MessageQueueService] Enqueued message for ${conversationId}: ${message.id}`)
    this.emitQueueUpdate(conversationId)
    
    return message
  }

  /**
   * Get all queued messages for a conversation
   */
  getQueue(conversationId: string): QueuedMessage[] {
    return this.queues.get(conversationId) || []
  }

  /**
   * Get all queues (for debugging/UI purposes)
   */
  getAllQueues(): MessageQueue[] {
    const result: MessageQueue[] = []
    this.queues.forEach((messages, conversationId) => {
      if (messages.length > 0) {
        result.push({ conversationId, messages })
      }
    })
    return result
  }

  /**
   * Remove a specific message from the queue.
   * Cannot remove a message that is currently being processed.
   */
  removeFromQueue(conversationId: string, messageId: string): boolean {
    const queue = this.queues.get(conversationId)
    if (!queue) return false

    const index = queue.findIndex((m) => m.id === messageId)
    if (index === -1) return false

    // Don't allow removing a message that's currently processing
    if (queue[index].status === "processing") {
      logApp(`[MessageQueueService] Cannot remove message ${messageId} - currently processing`)
      return false
    }

    queue.splice(index, 1)
    if (queue.length === 0) {
      this.queues.delete(conversationId)
    }
    logApp(`[MessageQueueService] Removed message ${messageId} from ${conversationId}`)
    this.emitQueueUpdate(conversationId)

    return true
  }

  /**
   * Clear all messages in a conversation's queue.
   * Cannot clear if any message is currently being processed.
   */
  clearQueue(conversationId: string): boolean {
    const queue = this.queues.get(conversationId)
    if (!queue) return true // Nothing to clear

    // Don't clear if there's a message currently processing
    if (queue.some((m) => m.status === "processing")) {
      logApp(`[MessageQueueService] Cannot clear queue for ${conversationId} - message is processing`)
      return false
    }

    this.queues.delete(conversationId)
    logApp(`[MessageQueueService] Cleared queue for ${conversationId}`)
    this.emitQueueUpdate(conversationId)
    return true
  }

  /**
   * Update the text of a queued message.
   * If the message was in failed status, resets it to pending for retry.
   */
  updateMessageText(conversationId: string, messageId: string, newText: string): boolean {
    const queue = this.queues.get(conversationId)
    if (!queue) return false

    const message = queue.find((m) => m.id === messageId)
    if (!message) return false

    if (message.status === "processing") {
      logApp(`[MessageQueueService] Cannot update message ${messageId} text while it is processing`)
      return false
    }

    // Prevent editing messages that have already been added to conversation history
    // to maintain consistency between history and what gets processed on retry
    if (message.addedToHistory) {
      logApp(`[MessageQueueService] Cannot update message ${messageId} text - already added to conversation history`)
      return false
    }

    message.text = newText
    // Reset failed messages to pending status so they can be retried
    if (message.status === "failed") {
      message.status = "pending"
      delete message.errorMessage
      logApp(`[MessageQueueService] Reset failed message ${messageId} to pending in ${conversationId}`)
    } else {
      logApp(`[MessageQueueService] Updated message ${messageId} text in ${conversationId}`)
    }
    this.emitQueueUpdate(conversationId)

    return true
  }

  /**
   * Peek at the next message to process.
   * Returns the first message only if it's pending - enforces strict FIFO ordering.
   * Failed/cancelled messages at the head of the queue block processing until handled.
   */
  peek(conversationId: string): QueuedMessage | null {
    const queue = this.queues.get(conversationId)
    if (!queue || queue.length === 0) return null
    // Strict FIFO: only return first item if it's pending
    // Failed/cancelled messages block the queue until user handles them (retry or remove)
    const firstMessage = queue[0]
    return firstMessage.status === "pending" ? firstMessage : null
  }

  /**
   * Mark a message as currently being processed.
   * This prevents UI actions (edit/remove) from affecting the message while processing.
   */
  markProcessing(conversationId: string, messageId: string): boolean {
    const queue = this.queues.get(conversationId)
    if (!queue) return false

    const message = queue.find((m) => m.id === messageId)
    if (!message) return false

    message.status = "processing"
    logApp(`[MessageQueueService] Marked message ${messageId} as processing in ${conversationId}`)
    this.emitQueueUpdate(conversationId)

    return true
  }

  /**
   * Mark a message as successfully processed and remove it from the queue
   * Finds the message by ID regardless of position (handles queue reordering during processing)
   */
  markProcessed(conversationId: string, messageId: string): boolean {
    const queue = this.queues.get(conversationId)
    if (!queue || queue.length === 0) return false

    const index = queue.findIndex((m) => m.id === messageId)
    if (index === -1) {
      logApp(`[MessageQueueService] Warning: markProcessed called for ${messageId} but message not found in queue`)
      return false
    }

    if (index !== 0) {
      logApp(`[MessageQueueService] Message ${messageId} was at position ${index} (queue was reordered during processing)`)
    }

    queue.splice(index, 1)
    if (queue.length === 0) {
      this.queues.delete(conversationId)
    }
    logApp(`[MessageQueueService] Marked message ${messageId} as processed for ${conversationId}`)
    this.emitQueueUpdate(conversationId)

    return true
  }

  /**
   * Mark a message as failed with an error message
   */
  markFailed(conversationId: string, messageId: string, errorMessage: string): boolean {
    const queue = this.queues.get(conversationId)
    if (!queue || queue.length === 0) return false

    const message = queue.find((m) => m.id === messageId)
    if (!message) {
      logApp(`[MessageQueueService] Warning: markFailed called for ${messageId} but message not found in queue`)
      return false
    }

    message.status = "failed"
    message.errorMessage = errorMessage
    logApp(`[MessageQueueService] Marked message ${messageId} as failed for ${conversationId}: ${errorMessage}`)
    this.emitQueueUpdate(conversationId)

    return true
  }

  /**
   * Mark a message as having been added to conversation history.
   * This prevents duplicates when retrying failed messages.
   */
  markAddedToHistory(conversationId: string, messageId: string): boolean {
    const queue = this.queues.get(conversationId)
    if (!queue) return false

    const message = queue.find((m) => m.id === messageId)
    if (!message) return false

    message.addedToHistory = true
    logApp(`[MessageQueueService] Marked message ${messageId} as added to history in ${conversationId}`)
    // Note: No need to emit queue update - this is an internal tracking flag
    return true
  }

  /**
   * Reset a failed message to pending status for retry.
   * Unlike updateMessageText, this only changes status and works for addedToHistory messages.
   */
  resetToPending(conversationId: string, messageId: string): boolean {
    const queue = this.queues.get(conversationId)
    if (!queue) return false

    const message = queue.find((m) => m.id === messageId)
    if (!message) return false

    if (message.status !== "failed") {
      logApp(`[MessageQueueService] Cannot reset message ${messageId} - not in failed status (current: ${message.status})`)
      return false
    }

    message.status = "pending"
    delete message.errorMessage
    logApp(`[MessageQueueService] Reset message ${messageId} to pending for retry in ${conversationId}`)
    this.emitQueueUpdate(conversationId)

    return true
  }

  /**
   * Reorder messages in the queue
   */
  reorderQueue(conversationId: string, messageIds: string[]): boolean {
    const queue = this.queues.get(conversationId)
    if (!queue) return false

    const messageMap = new Map(queue.map((m) => [m.id, m]))

    const newQueue: QueuedMessage[] = []
    for (const id of messageIds) {
      const message = messageMap.get(id)
      if (message) {
        newQueue.push(message)
        messageMap.delete(id)
      }
    }

    messageMap.forEach((m) => newQueue.push(m))

    this.queues.set(conversationId, newQueue)
    logApp(`[MessageQueueService] Reordered queue for ${conversationId}`)
    this.emitQueueUpdate(conversationId)

    return true
  }

  /**
   * Check if a conversation has queued messages
   */
  hasQueuedMessages(conversationId: string): boolean {
    const queue = this.queues.get(conversationId)
    return !!queue && queue.length > 0
  }

  /**
   * Emit queue update to renderer
   */
  private emitQueueUpdate(conversationId: string): void {
    const main = WINDOWS.get("main")
    const panel = WINDOWS.get("panel")

    const queue = this.getQueue(conversationId)
    const isPaused = this.isQueuePaused(conversationId)

    ;[main, panel].forEach((win) => {
      if (win) {
        try {
          const handlers = getRendererHandlers<RendererHandlers>(win.webContents)
          if (handlers?.onMessageQueueUpdate) {
            try {
              handlers.onMessageQueueUpdate.send({
                conversationId,
                queue,
                isPaused,
              })
            } catch (error) {
              logApp("Failed to send queue update:", error)
            }
          }
        } catch (error) {
          logApp("Failed to get renderer handlers for queue update:", error)
        }
      }
    })
  }
}

export const messageQueueService = MessageQueueService.getInstance()

