import { loggerService } from '@logger'
import db from '@renderer/databases'
import { getStoreSetting } from '@renderer/hooks/useSettings'
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
import { NotificationService } from '@renderer/services/NotificationService'
import store from '@renderer/store'
import {
  clearCompletedProcessing,
  updateBaseItemIsPreprocessed,
  updateBaseItemUniqueId,
  updateItemProcessingStatus
} from '@renderer/store/knowledge'
import type { KnowledgeItem } from '@renderer/types'
import { uuid } from '@renderer/utils'
import type { LoaderReturn } from '@shared/config/types'
import { t } from 'i18next'

const logger = loggerService.withContext('KnowledgeQueue')

class KnowledgeQueue {
  private processing: Map<string, boolean> = new Map()
  private readonly MAX_RETRIES = 1

  public async checkAllBases(): Promise<void> {
    const state = store.getState()
    const bases = state.knowledge.bases

    await Promise.all(
      bases.map(async (base) => {
        const processableItems = base.items.filter((item) => {
          if (item.processingStatus === 'failed') {
            return !item.retryCount || item.retryCount < this.MAX_RETRIES
          }
          return item.processingStatus === 'pending'
        })

        const hasProcessableItems = processableItems.length > 0

        if (hasProcessableItems && !this.processing.get(base.id)) {
          await this.processQueue(base.id)
        }
      })
    )
  }

  async processQueue(baseId: string): Promise<void> {
    if (this.processing.get(baseId)) {
      logger.info(`Queue for base ${baseId} is already being processed`)
      return
    }

    this.processing.set(baseId, true)

    let succeeded = 0
    let failed = 0

    try {
      const state = store.getState()
      const base = state.knowledge.bases.find((b) => b.id === baseId)

      if (!base) {
        throw new Error('Knowledge base not found')
      }

      const findProcessableItem = () => {
        const state = store.getState()
        const base = state.knowledge.bases.find((b) => b.id === baseId)
        return (
          base?.items.find((item) => {
            if (item.processingStatus === 'failed') {
              return !item.retryCount || item.retryCount < this.MAX_RETRIES
            } else {
              return item.processingStatus === 'pending'
            }
          }) ?? null
        )
      }

      let processableItem = findProcessableItem()
      while (processableItem) {
        const result = await this.processItem(baseId, processableItem)
        if (result) {
          succeeded++
        } else {
          failed++
        }
        processableItem = findProcessableItem()
      }
    } finally {
      this.sendBatchNotification(succeeded, failed)
      logger.info(`Finished processing queue for base ${baseId}`)
      this.processing.set(baseId, false)
    }
  }

  stopProcessing(baseId: string): void {
    this.processing.set(baseId, false)
  }

  stopAllProcessing(): void {
    for (const baseId of this.processing.keys()) {
      this.processing.set(baseId, false)
    }
  }

  private sendBatchNotification(succeeded: number, failed: number): void {
    const total = succeeded + failed
    if (total === 0) return

    let type: 'success' | 'error' | 'warning'
    let message: string

    if (failed === 0) {
      type = 'success'
      message = t('notification.knowledge.batch_success', { succeeded })
    } else if (succeeded === 0) {
      type = 'error'
      message = t('notification.knowledge.batch_error', { failed })
    } else {
      type = 'warning'
      message = t('notification.knowledge.batch_mixed', { succeeded, failed })
    }

    void NotificationService.getInstance().send({
      id: uuid(),
      type,
      title: t('common.knowledge_base'),
      message,
      silent: false,
      timestamp: Date.now(),
      source: 'knowledge'
    })
  }

  private async processItem(baseId: string, item: KnowledgeItem): Promise<boolean> {
    const userId = getStoreSetting('userId')
    try {
      if (item.retryCount && item.retryCount >= this.MAX_RETRIES) {
        const errorMessage = item.processingError
          ? `Max retries exceeded: ${item.processingError}`
          : 'Max retries exceeded'
        logger.warn(`Item ${item.id} has reached max retries, marking as failed`)
        store.dispatch(
          updateItemProcessingStatus({
            baseId,
            itemId: item.id,
            status: 'failed',
            error: errorMessage
          })
        )
        return false
      }

      logger.info(`Starting to process item ${item.id} (${item.type})`)

      store.dispatch(
        updateItemProcessingStatus({
          baseId,
          itemId: item.id,
          status: 'processing',
          retryCount: (item.retryCount || 0) + 1
        })
      )

      const base = store.getState().knowledge.bases.find((b) => b.id === baseId)

      if (!base) {
        throw new Error(`[KnowledgeQueue] Knowledge base ${baseId} not found`)
      }

      const baseParams = getKnowledgeBaseParams(base)
      const sourceItem = base.items.find((i) => i.id === item.id)

      if (!sourceItem) {
        throw new Error(`[KnowledgeQueue] Source item ${item.id} not found in base ${baseId}`)
      }

      let result: LoaderReturn | null = null
      let note, content

      logger.info(`Processing item: ${sourceItem.content}`)

      switch (item.type) {
        case 'note':
          note = await db.knowledge_notes.get(item.id)
          if (note) {
            content = note.content
            logger.info('{ ...sourceItem, content }', { ...sourceItem, content })
            result = await window.api.knowledgeBase.add({ base: baseParams, item: { ...sourceItem, content } })
          }
          break
        default: {
          result = await window.api.knowledgeBase.add({
            base: baseParams,
            item: sourceItem,
            userId: userId
          })
          break
        }
      }

      if (!result) {
        throw new Error(`[KnowledgeQueue] Backend processing returned null for item ${item.id}`)
      }

      if (result.status === 'failed') {
        logger.error(`Backend processing error for item ${item.id}: ${result.message}`)

        const errorPrefix =
          result.messageSource === 'embedding'
            ? t('knowledge.status_embedding_failed')
            : t('knowledge.status_preprocess_failed')

        throw new Error(
          result.message ? `${errorPrefix}: ${result.message}` : `Backend processing failed for item ${item.id}`
        )
      }

      logger.info(`Successfully completed processing item ${item.id}`)

      store.dispatch(
        updateItemProcessingStatus({
          baseId,
          itemId: item.id,
          status: 'completed'
        })
      )

      if (result) {
        store.dispatch(
          updateBaseItemUniqueId({
            baseId,
            itemId: item.id,
            uniqueId: result.uniqueId,
            uniqueIds: result.uniqueIds
          })
        )
        store.dispatch(
          updateBaseItemIsPreprocessed({
            baseId,
            itemId: item.id,
            isPreprocessed: !!base.preprocessProvider
          })
        )
      }
      logger.info(`Updated uniqueId for item ${item.id} in base ${baseId} `)

      store.dispatch(clearCompletedProcessing({ baseId }))
      return true
    } catch (error) {
      logger.error(`Error processing item ${item.id}: `, error as Error)

      store.dispatch(
        updateItemProcessingStatus({
          baseId,
          itemId: item.id,
          status: 'failed',
          error: error instanceof Error ? error.message : 'Unknown error',
          retryCount: (item.retryCount || 0) + 1
        })
      )
      return false
    }
  }
}

export default new KnowledgeQueue()
