import { reactive } from 'vue'

import * as aiAttachmentAssociationModel from '../../utils/aiAttachmentAssociationModel.js'
import { syncExpenseClaimFilesToDraft } from '../../utils/expenseClaimAttachmentSync.js'
import {
  buildFileIdentity,
  collectReceiptFiles
} from '../../views/scripts/travelReimbursementAttachmentModel.js'
import {
  createExpenseClaimItem,
  fetchExpenseClaimDetail,
  uploadExpenseClaimItemAttachment
} from '../../services/reimbursements.js'
import { recognizeOcrFiles } from '../../services/ocr.js'
import { createAttachmentAssociationJob } from '../../services/attachmentAssociationJobs.js'
import {
  AI_ATTACHMENT_ASSOCIATION_CONFIRM_ACTION,
  buildInlineAttachmentOcrDetails
} from './workbenchAiMessageModel.js'
import { isLikelyAiModeOcrFile } from './workbenchAiComposerModel.js'
import { useWorkbenchAiAttachmentAssociationJobs } from './useWorkbenchAiAttachmentAssociationJobs.js'

function buildAiAttachmentAssociationThinkingEvents(status = 'running') {
  const completed = status === 'completed'
  const failed = status === 'failed'
  const eventStatus = failed ? 'failed' : completed ? 'completed' : 'running'
  return [
    {
      eventId: 'attachment-ocr',
      title: '识别上传票据',
      content: '提取票据里的日期、地点和行程信息。',
      status: eventStatus
    },
    {
      eventId: 'claim-lookup',
      title: '查询可关联报销单',
      content: '查找草稿、待补充和退回状态的可归集单据。',
      status: eventStatus
    },
    {
      eventId: 'claim-match',
      title: '匹配票据与报销单',
      content: '根据票据时间、城市和报销事由判断最可能的关联单据。',
      status: eventStatus
    }
  ]
}

function resolveAiAttachmentAssociationClaimNo(payload = {}) {
  return String(payload?.claim_no || payload?.claimNo || '').trim()
}

function buildAiAttachmentAssociationResultThinkingEvents(status = 'running') {
  const completed = status === 'completed'
  const failed = status === 'failed'
  const eventStatus = failed ? 'failed' : completed ? 'completed' : 'running'
  return [
    {
      eventId: 'attachment-confirm',
      title: '确认自动归集',
      content: '正在读取匹配单据并准备写入附件。',
      status: eventStatus
    },
    {
      eventId: 'attachment-upload',
      title: '归集票据附件',
      content: '把本次上传的票据写入报销单明细。',
      status: eventStatus
    }
  ]
}

export function useWorkbenchAiAttachmentAssociationFlow({
  aiAttachmentAssociationRuntime,
  conversationId,
  conversationMessages,
  createAiAttachmentAssociationId,
  createInlineMessage,
  inlineConversationAutoScrollPinned,
  persistCurrentConversation,
  replaceInlineMessage,
  scrollInlineConversationToBottom,
  sending,
  streamOrSetInlineAssistantContent,
  notifyRequestUpdated,
  toast
}) {
  const aiModeReceiptContextCache = new Map()
  const aiModeReceiptRecognitionState = reactive({})

  function resolveAiModeReceiptRecognitionStateKey(file) {
    return buildFileIdentity(file)
  }

  function pruneAiModeReceiptRecognitionState(files = []) {
    const activeKeys = new Set(
      (Array.isArray(files) ? files : [])
        .filter((file) => isLikelyAiModeOcrFile(file))
        .map((file) => resolveAiModeReceiptRecognitionStateKey(file))
        .filter(Boolean)
    )
    Object.keys(aiModeReceiptRecognitionState).forEach((key) => {
      if (!activeKeys.has(key)) {
        delete aiModeReceiptRecognitionState[key]
      }
    })
  }

  function setAiModeReceiptRecognitionState(files = [], patch = {}) {
    const recognitionFiles = (Array.isArray(files) ? files : [])
      .filter((file) => isLikelyAiModeOcrFile(file))
    recognitionFiles.forEach((file) => {
      const key = resolveAiModeReceiptRecognitionStateKey(file)
      if (!key) {
        return
      }
      aiModeReceiptRecognitionState[key] = {
        ...(aiModeReceiptRecognitionState[key] || {}),
        fileName: String(file?.name || '').trim(),
        ...patch
      }
    })
  }

  function findAiModeReceiptDocumentForFile(file = {}, documents = [], index = 0) {
    const fileName = String(file?.name || '').trim()
    if (fileName) {
      const exactDocument = documents.find((document) => (
        String(document?.filename || document?.name || '').trim() === fileName
      ))
      if (exactDocument) {
        return exactDocument
      }
    }
    return documents[index] || null
  }

  function buildAiModeReceiptRecognitionPendingState() {
    return {
      status: 'recognizing',
      label: '智能录入识别中',
      title: '正在调用智能录入 OCR 识别票据内容'
    }
  }

  function buildAiModeReceiptRecognitionDoneState(document = null) {
    const detail = String(
      document?.document_type_label ||
        document?.scene_label ||
        document?.document_type ||
        ''
    ).trim()
    return {
      status: 'recognized',
      label: detail ? `当前会话已识别 · ${detail}` : '当前会话已识别',
      title: detail
        ? `当前上传附件 OCR 已完成，识别为${detail}。本状态不代表票据夹已有记录。`
        : '当前上传附件 OCR 已完成。本状态不代表票据夹已有记录。',
      document: document && typeof document === 'object' ? document : null
    }
  }

  function applyAiModeReceiptRecognitionResult(files = [], context = {}) {
    const documents = Array.isArray(context?.ocrDocuments) ? context.ocrDocuments : []
    const recognitionFiles = (Array.isArray(files) ? files : [])
      .filter((file) => isLikelyAiModeOcrFile(file))
    recognitionFiles.forEach((file, index) => {
      const document = findAiModeReceiptDocumentForFile(file, documents, index)
      setAiModeReceiptRecognitionState([file], buildAiModeReceiptRecognitionDoneState(document))
    })
  }

  function resolveAiModeReceiptRecognitionState(file) {
    const key = resolveAiModeReceiptRecognitionStateKey(file)
    return key ? aiModeReceiptRecognitionState[key] || null : null
  }

  function collectAiModeReceiptRecognitionFiles(files = []) {
    return (Array.isArray(files) ? files : [])
      .filter((file) => isLikelyAiModeOcrFile(file))
  }

  function hasPendingAiModeReceiptRecognition(files = []) {
    return collectAiModeReceiptRecognitionFiles(files).some((file) => {
      const state = resolveAiModeReceiptRecognitionState(file)
      return !state || state.status === 'recognizing'
    })
  }

  function hasFailedAiModeReceiptRecognition(files = []) {
    return collectAiModeReceiptRecognitionFiles(files).some((file) => (
      resolveAiModeReceiptRecognitionState(file)?.status === 'failed'
    ))
  }

  function buildAiModeReceiptBaseContext(safeFiles = [], ocrFiles = []) {
    const attachmentNames = safeFiles
      .map((file) => String(file?.name || '').trim())
      .filter(Boolean)
    const ocrSourceFileNames = ocrFiles
      .map((file) => String(file?.name || '').trim())
      .filter(Boolean)
    return {
      attachmentNames,
      attachmentCount: attachmentNames.length,
      ocrSourceFileNames,
      ocrSummary: '',
      ocrDocuments: []
    }
  }

  function buildAiModeReceiptContextCacheKey(ocrFiles = []) {
    return (Array.isArray(ocrFiles) ? ocrFiles : [])
      .map((file) => buildFileIdentity(file))
      .filter(Boolean)
      .join('|')
  }

  function buildAiModeReceiptContextFromCollected(baseContext = {}, collected = {}) {
    return {
      ...baseContext,
      ocrPayload: collected.ocrPayload || { documents: collected.ocrDocuments || [] },
      ocrSummary: String(collected.ocrSummary || '').trim(),
      ocrDocuments: Array.isArray(collected.ocrDocuments) ? collected.ocrDocuments : [],
      ocrFilePreviews: Array.isArray(collected.ocrFilePreviews) ? collected.ocrFilePreviews : []
    }
  }

  function rememberAiModeReceiptContext(cacheKey, context) {
    if (!cacheKey) {
      return
    }
    aiModeReceiptContextCache.set(cacheKey, {
      status: 'resolved',
      context: {
        ocrPayload: context.ocrPayload,
        ocrSummary: context.ocrSummary,
        ocrDocuments: context.ocrDocuments,
        ocrFilePreviews: context.ocrFilePreviews
      }
    })
    if (aiModeReceiptContextCache.size > 20) {
      aiModeReceiptContextCache.delete(aiModeReceiptContextCache.keys().next().value)
    }
  }

  function startAiModeReceiptRecognition(files = [], options = {}) {
    const safeFiles = Array.isArray(files) ? files : []
    const ocrFiles = safeFiles.filter((file) => isLikelyAiModeOcrFile(file))
    const cacheKey = buildAiModeReceiptContextCacheKey(ocrFiles)
    if (!ocrFiles.length || !cacheKey) {
      return null
    }

    const forceRefresh = Boolean(options.forceRefresh)
    const cached = aiModeReceiptContextCache.get(cacheKey)
    if (!forceRefresh && cached?.status === 'resolved') {
      applyAiModeReceiptRecognitionResult(ocrFiles, cached.context)
      return null
    }
    if (cached?.status === 'pending' && cached.promise) {
      setAiModeReceiptRecognitionState(ocrFiles, buildAiModeReceiptRecognitionPendingState())
      return cached.promise
    }

    setAiModeReceiptRecognitionState(ocrFiles, buildAiModeReceiptRecognitionPendingState())

    const promise = collectReceiptFiles({
      files: ocrFiles,
      recognizeOcrFiles
    }).then((collected) => {
      const context = buildAiModeReceiptContextFromCollected(
        buildAiModeReceiptBaseContext(safeFiles, ocrFiles),
        collected
      )
      rememberAiModeReceiptContext(cacheKey, context)
      applyAiModeReceiptRecognitionResult(ocrFiles, context)
      return context
    }).catch((error) => {
      aiModeReceiptContextCache.delete(cacheKey)
      setAiModeReceiptRecognitionState(ocrFiles, {
        status: 'failed',
        label: '识别失败',
        title: error?.message || '智能录入 OCR 识别失败'
      })
      throw error
    })

    aiModeReceiptContextCache.set(cacheKey, {
      status: 'pending',
      promise
    })
    return promise
  }

  function primeAiModeReceiptContext(files = []) {
    pruneAiModeReceiptRecognitionState(files)
    const promise = startAiModeReceiptRecognition(files, { forceRefresh: true })
    if (promise && typeof promise.catch === 'function') {
      promise.catch((error) => {
        console.warn('AI mode OCR preload failed:', error)
      })
    }
  }

  async function collectAiModeReceiptContext(files = []) {
    const safeFiles = Array.isArray(files) ? files : []
    const ocrFiles = safeFiles.filter((file) => isLikelyAiModeOcrFile(file))
    const baseContext = buildAiModeReceiptBaseContext(safeFiles, ocrFiles)

    if (!ocrFiles.length) {
      return baseContext
    }

    const cacheKey = buildAiModeReceiptContextCacheKey(ocrFiles)
    const cached = cacheKey ? aiModeReceiptContextCache.get(cacheKey) : null
    if (cached?.status === 'resolved') {
      applyAiModeReceiptRecognitionResult(ocrFiles, cached.context)
      return {
        ...baseContext,
        ...cached.context
      }
    }
    if (cached?.status === 'pending' && cached.promise) {
      try {
        const cachedContext = await cached.promise
        applyAiModeReceiptRecognitionResult(ocrFiles, cachedContext)
        return {
          ...baseContext,
          ocrPayload: cachedContext.ocrPayload,
          ocrSummary: cachedContext.ocrSummary,
          ocrDocuments: cachedContext.ocrDocuments,
          ocrFilePreviews: cachedContext.ocrFilePreviews
        }
      } catch (error) {
        console.warn('AI mode OCR preload result unavailable:', error)
      }
    }

    try {
      setAiModeReceiptRecognitionState(ocrFiles, buildAiModeReceiptRecognitionPendingState())
      const collected = await collectReceiptFiles({
        files: ocrFiles,
        recognizeOcrFiles
      })
      const context = buildAiModeReceiptContextFromCollected(baseContext, collected)
      rememberAiModeReceiptContext(cacheKey, context)
      applyAiModeReceiptRecognitionResult(ocrFiles, context)
      return context
    } catch (error) {
      console.warn('AI mode OCR request failed:', error)
      setAiModeReceiptRecognitionState(ocrFiles, {
        status: 'failed',
        label: '识别失败',
        title: error?.message || '智能录入 OCR 识别失败'
      })
      return {
        ...baseContext,
        ocrError: error?.message || 'OCR识别失败，已继续使用附件名称。'
      }
    }
  }

  function findAiAttachmentAssociationRuntime(options = {}) {
    const normalizedAssociationId = String(options.associationId || options.association_id || '').trim()
    const normalizedClaimNo = String(options.claimNo || options.claim_no || '').trim()
    if (normalizedAssociationId) {
      const runtime = aiAttachmentAssociationRuntime.get(normalizedAssociationId)
      if (runtime) {
        return { associationId: normalizedAssociationId, runtime }
      }
    }

    if (normalizedClaimNo) {
      for (const [runtimeId, runtime] of aiAttachmentAssociationRuntime.entries()) {
        if (String(runtime?.claimNo || '').trim() === normalizedClaimNo && runtime?.files?.length) {
          return { associationId: runtimeId, runtime }
        }
      }
    }

    return { associationId: '', runtime: null }
  }

  function updateAiAttachmentAssociationActionState(message = {}, associationId = '', state = {}, options = {}) {
    const normalizedAssociationId = String(associationId || '').trim()
    const normalizedClaimNo = String(options.claimNo || options.claim_no || '').trim()
    if (!message || !Array.isArray(message.suggestedActions) || (!normalizedAssociationId && !normalizedClaimNo)) {
      return
    }
    message.suggestedActions = message.suggestedActions.map((action) => {
      const actionAssociationId = String(action?.payload?.association_id || action?.payload?.associationId || '').trim()
      const actionClaimNo = resolveAiAttachmentAssociationClaimNo(action?.payload || {})
      const isSameAssociation = normalizedAssociationId && actionAssociationId === normalizedAssociationId
      const isSameClaim = normalizedClaimNo && actionClaimNo === normalizedClaimNo
      if (String(action?.action_type || '').trim() !== AI_ATTACHMENT_ASSOCIATION_CONFIRM_ACTION || (!isSameAssociation && !isSameClaim)) {
        return action
      }
      return {
        ...action,
        ...state
      }
    })
  }

  function buildAiAttachmentAssociationDetailActions(runtime = {}) {
    const claimNo = String(runtime.claimNo || '').trim()
    const claimId = String(runtime.claimId || '').trim()
    if (!claimNo && !claimId) {
      return []
    }
    return [{
      label: '查看草稿',
      description: '打开已归集票据的报销草稿。',
      icon: 'mdi mdi-open-in-new',
      action_type: 'open_application_detail',
      payload: {
        claim_id: claimId,
        claim_no: claimNo,
        document_type: 'expense'
      }
    }]
  }

  const attachmentJobFlow = useWorkbenchAiAttachmentAssociationJobs({
    conversationMessages,
    createInlineMessage,
    persistCurrentConversation,
    replaceInlineMessage,
    streamOrSetInlineAssistantContent,
    notifyRequestUpdated,
    toast,
    buildDetailActions: buildAiAttachmentAssociationDetailActions,
    buildThinkingEvents: buildAiAttachmentAssociationResultThinkingEvents
  })

  async function confirmAiAttachmentAssociation(actionPayload = {}, sourceMessage = null) {
    const requestedAssociationId = String(actionPayload.association_id || actionPayload.associationId || '').trim()
    const payloadClaimNo = resolveAiAttachmentAssociationClaimNo(actionPayload)
    const runtimeResult = findAiAttachmentAssociationRuntime({
      associationId: requestedAssociationId,
      claimNo: payloadClaimNo
    })
    const associationId = runtimeResult.associationId
    const runtime = runtimeResult.runtime
    const actionClaimNo = payloadClaimNo || String(runtime?.claimNo || '').trim()
    if (!associationId || !runtime?.files?.length) {
      toast('当前会话里没有可归集的附件原件，请重新上传票据后再确认。')
      return
    }

    updateAiAttachmentAssociationActionState(sourceMessage, associationId, {
      label: '正在归集...',
      disabled: true
    }, { claimNo: actionClaimNo })
    persistCurrentConversation()
    sending.value = true

    const pendingMessage = createInlineMessage('assistant', '', {
      pending: true,
      stewardPlan: {
        streamStatus: 'streaming',
        thinkingEvents: buildAiAttachmentAssociationResultThinkingEvents('running')
      }
    })
    conversationMessages.value.push(pendingMessage)
    scrollInlineConversationToBottom()

    try {
      const syncResult = await syncExpenseClaimFilesToDraft({
        claimId: runtime.claimId,
        files: runtime.files,
        fetchExpenseClaimDetail,
        createExpenseClaimItem,
        uploadExpenseClaimItemAttachment
      })
      notifyRequestUpdated?.({
        claimId: runtime.claimId,
        claimNo: runtime.claimNo,
        source: 'ai-workbench-attachment-association-sync',
        uploadedCount: Number(syncResult?.uploadedCount || 0),
        skippedCount: Number(syncResult?.skippedCount || 0)
      })
      const finalMessageText = aiAttachmentAssociationModel.buildAiAttachmentAssociationResultMessage({
        claimNo: runtime.claimNo,
        fileNames: runtime.fileNames,
        uploadedCount: syncResult.uploadedCount,
        skippedCount: syncResult.skippedCount
      })
      await streamOrSetInlineAssistantContent(pendingMessage.id, finalMessageText)
      replaceInlineMessage(
        pendingMessage.id,
        createInlineMessage('assistant', finalMessageText, {
          id: pendingMessage.id,
          stewardPlan: {
            streamStatus: 'completed',
            thinkingEvents: buildAiAttachmentAssociationResultThinkingEvents('completed')
          },
          suggestedActions: buildAiAttachmentAssociationDetailActions(runtime)
        })
      )
      updateAiAttachmentAssociationActionState(sourceMessage, associationId, {
        label: '已自动关联',
        disabled: true
      }, { claimNo: actionClaimNo })
      aiAttachmentAssociationRuntime.delete(associationId)
      persistCurrentConversation()
    } catch (error) {
      const finalMessageText = error?.message || '自动归集失败，请稍后重试。'
      replaceInlineMessage(
        pendingMessage.id,
        createInlineMessage('assistant', finalMessageText, {
          id: pendingMessage.id,
          stewardPlan: {
            streamStatus: 'failed',
            thinkingEvents: buildAiAttachmentAssociationResultThinkingEvents('failed')
          }
        })
      )
      updateAiAttachmentAssociationActionState(sourceMessage, associationId, {
        label: '重新自动关联',
        disabled: false
      }, { claimNo: actionClaimNo })
      toast(finalMessageText)
      persistCurrentConversation()
    } finally {
      sending.value = false
      scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
    }
  }

  async function requestAiAttachmentAssociationReply(prompt, entry = {}, files = []) {
    let shouldAutoScrollOnFinish = true
    const pendingMessage = createInlineMessage('assistant', '', {
      pending: true,
      stewardPlan: {
        streamStatus: 'streaming',
        thinkingEvents: buildAiAttachmentAssociationThinkingEvents('running')
      }
    })
    conversationMessages.value.push(pendingMessage)
    scrollInlineConversationToBottom()

    try {
      const collected = await collectAiModeReceiptContext(files)
      const attachmentOcrDetails = buildInlineAttachmentOcrDetails(collected, files)
      const receiptIds = attachmentJobFlow.extractReceiptIdsFromOcrDocuments(collected.ocrDocuments)
      if (!receiptIds.length) {
        throw new Error('当前附件没有持久化票据记录，请重新上传后再试。')
      }
      const fileNames = files.map((file) => file?.name || '').filter(Boolean)
      const job = attachmentJobFlow.normalizeJob(await createAttachmentAssociationJob({
        receipt_ids: receiptIds,
        prompt,
        conversation_id: conversationId?.value || ''
      }))
      if (!job) {
        throw new Error('附件关联任务创建失败，请稍后重试。')
      }
      const runningMessageText = attachmentJobFlow.buildRunningMessage(job, fileNames)
      shouldAutoScrollOnFinish = inlineConversationAutoScrollPinned.value
      replaceInlineMessage(
        pendingMessage.id,
        createInlineMessage('assistant', runningMessageText, {
          id: pendingMessage.id,
          pending: true,
          stewardPlan: {
            streamStatus: 'streaming',
            thinkingEvents: buildAiAttachmentAssociationResultThinkingEvents('running')
          },
          attachmentOcrDetails,
          attachmentAssociationJob: job
        })
      )
      persistCurrentConversation()
      void attachmentJobFlow.pollJob({
        jobId: job.jobId,
        messageId: pendingMessage.id,
        fileNames,
        attachmentOcrDetails,
        initialJob: job
      })
    } catch (error) {
      shouldAutoScrollOnFinish = inlineConversationAutoScrollPinned.value
      const finalMessageText = error?.message || '票据识别或单据匹配失败，请稍后再试。'
      replaceInlineMessage(
        pendingMessage.id,
        createInlineMessage('assistant', finalMessageText, {
          id: pendingMessage.id,
          stewardPlan: {
            streamStatus: 'failed',
            thinkingEvents: buildAiAttachmentAssociationThinkingEvents('failed')
          }
        })
      )
      toast(finalMessageText)
      persistCurrentConversation()
    } finally {
      sending.value = false
      scrollInlineConversationToBottom({ force: shouldAutoScrollOnFinish })
    }
  }

  return {
    collectAiModeReceiptContext,
    confirmAiAttachmentAssociation,
    hasFailedAiModeReceiptRecognition,
    hasPendingAiModeReceiptRecognition,
    primeAiModeReceiptContext,
    requestAiAttachmentAssociationReply,
    resumePendingAiAttachmentAssociationJobs: attachmentJobFlow.resumePendingJobs,
    resolveAiModeReceiptRecognitionState,
    resolveAiAttachmentAssociationClaimNo
  }
}
