import {
  buildReviewSlotMap,
  resolveDocumentTypeLabel,
  resolveExpenseTypeCode
} from './travelReimbursementReviewModel.js'
import { normalizeExpenseQueryPayload } from './travelReimbursementExpenseQueryModel.js'
import {
  inferPreviewKindFromFile,
  isTemporaryPreviewUrl as isTemporaryDocumentPreviewUrl,
  resolveDocumentPreviewAsset,
  resolveDocumentPreviewKind as resolveUnifiedDocumentPreviewKind
} from '../../utils/documentPreviewAssets.js'

const SCENARIO_LABELS = {
  expense: '报销',
  accounts_receivable: '应收',
  accounts_payable: '应付',
  knowledge: '知识',
  unknown: '通用'
}

const INTENT_LABELS = {
  query: '查询',
  explain: '解释',
  compare: '对比',
  risk_check: '风险检查',
  draft: '信息核对',
  operate: '动作请求'
}

function resolveStatusLabel(status) {
  if (status === 'succeeded') return '已完成'
  if (status === 'blocked') return '已阻断'
  return '处理中'
}

function resolveStatusTone(status) {
  if (status === 'succeeded') return 'success'
  if (status === 'blocked') return 'warning'
  return 'note'
}

export const MAX_ATTACHMENTS = 10
export const MAX_OCR_DOCUMENTS = 10
export const VISIBLE_ATTACHMENT_CHIPS = 2
export const ATTACHMENT_ASSOCIATION_CONFIRM_HREF = '#confirm-attachment-association'

export function buildUnsavedDraftAttachmentConfirmationMessage({ fileNames = [] } = {}) {
  const names = (Array.isArray(fileNames) ? fileNames : [])
    .map((item) => String(item || '').trim())
    .filter(Boolean)
  const attachmentLine = names.length
    ? `本次待归集附件：${names.length} 份（${names.join('、')}）`
    : '本次待归集附件：待识别'

  return [
    '当前这笔报销信息还没有保存为草稿。',
    '',
    '如果继续上传票据，我需要先把当前已识别的信息保存成一张草稿单据，再识别并归集本次附件。',
    '',
    attachmentLine,
    '',
    '',
    `如果 **[确定](${ATTACHMENT_ASSOCIATION_CONFIRM_HREF})**，我会先保存这笔未保存单据，再把此次上传的附件归集到该单据。`
  ].join('\n').trim()
}

function normalizeOcrDocumentFields(item = {}) {
  const sources = [
    item?.document_fields,
    item?.fields,
    item?.document_info?.fields,
    item?.metadata?.fields
  ]
  const fields = sources.find((source) => Array.isArray(source)) || []
  return fields
    .map((field) => {
      const label = String(field?.label || field?.name || field?.key || '').trim()
      const key = String(field?.key || field?.name || label || '').trim()
      const value = String(field?.value || field?.text || '').trim()
      return { key, label, value }
    })
    .filter((field) => field.key && field.label && field.value)
}

export function normalizeOcrDocuments(payload) {
  const documents = Array.isArray(payload?.documents) ? payload.documents : []
  return documents.slice(0, MAX_OCR_DOCUMENTS).map((item) => ({
    filename: item.filename,
    summary: item.summary,
    text: String(item.text || ''),
    avg_score: Number(item.avg_score || 0),
    line_count: Number(item.line_count || 0),
    document_type: String(item.document_type || 'other').trim() || 'other',
    document_type_label: String(item.document_type_label || '').trim(),
    scene_code: String(item.scene_code || 'other').trim() || 'other',
    scene_label: String(item.scene_label || '').trim(),
    preview_kind: String(item.preview_kind || '').trim(),
    preview_data_url: String(item.preview_data_url || '').trim(),
    preview_url: String(item.preview_url || '').trim(),
    receipt_id: String(item.receipt_id || item.receiptId || '').trim(),
    receipt_status: String(item.receipt_status || item.receiptStatus || '').trim(),
    receipt_preview_url: String(item.receipt_preview_url || item.receiptPreviewUrl || '').trim(),
    receipt_source_url: String(item.receipt_source_url || item.receiptSourceUrl || '').trim(),
    document_fields: normalizeOcrDocumentFields(item),
    warnings: Array.isArray(item.warnings) ? item.warnings : []
  }))
}

function defineFileReceiptId(file, receiptId) {
  const normalizedReceiptId = String(receiptId || '').trim()
  if (!file || !normalizedReceiptId) {
    return false
  }

  try {
    Object.defineProperty(file, 'receiptId', {
      value: normalizedReceiptId,
      enumerable: false,
      configurable: true
    })
    return true
  } catch {
    try {
      file.receiptId = normalizedReceiptId
      return String(file.receiptId || '').trim() === normalizedReceiptId
    } catch {
      return false
    }
  }
}

export function attachReceiptFolderIdsToFiles(files = [], payload = null) {
  const safeFiles = Array.isArray(files) ? files : []
  const documents = Array.isArray(payload?.documents) ? payload.documents : []
  let attachedCount = 0

  safeFiles.slice(0, documents.length).forEach((file, index) => {
    const document = documents[index] || {}
    const receiptId = String(document.receipt_id || document.receiptId || '').trim()
    if (receiptId && defineFileReceiptId(file, receiptId)) {
      attachedCount += 1
    }
  })

  return attachedCount
}

export async function collectReceiptFiles({
  files = [],
  recognizedAttachmentData = null,
  recognizeOcrFiles,
  timeoutMs = 90000,
  timeoutMessage = '票据 OCR 识别超时，已继续使用附件名称处理。'
} = {}) {
  const safeFiles = Array.isArray(files) ? files : []
  const reusedData = recognizedAttachmentData && typeof recognizedAttachmentData === 'object'
    ? recognizedAttachmentData
    : null

  if (reusedData) {
    const ocrDocuments = Array.isArray(reusedData.ocrDocuments) ? [...reusedData.ocrDocuments] : []
    const ocrPayload = reusedData.ocrPayload || { documents: ocrDocuments }
    attachReceiptFolderIdsToFiles(safeFiles, ocrPayload)
    return {
      ocrPayload,
      ocrSummary: String(reusedData.ocrSummary || '').trim() || buildOcrSummaryFromDocuments(ocrDocuments),
      ocrDocuments,
      ocrFilePreviews: Array.isArray(reusedData.ocrFilePreviews) ? [...reusedData.ocrFilePreviews] : []
    }
  }

  if (typeof recognizeOcrFiles !== 'function') {
    throw new Error('票据采集服务未配置。')
  }

  const ocrPayload = await recognizeOcrFiles(safeFiles, {
    timeoutMs,
    timeoutMessage
  })
  attachReceiptFolderIdsToFiles(safeFiles, ocrPayload)

  return {
    ocrPayload,
    ocrSummary: buildOcrSummary(ocrPayload),
    ocrDocuments: normalizeOcrDocuments(ocrPayload),
    ocrFilePreviews: buildOcrFilePreviews(ocrPayload)
  }
}

export function buildOcrSummary(payload) {
  return buildOcrSummaryFromDocuments(normalizeOcrDocuments(payload))
}

export function buildOcrSummaryFromDocuments(documents) {
  return (Array.isArray(documents) ? documents : [])
    .slice(0, MAX_OCR_DOCUMENTS)
    .map((item) => {
      const filename = String(item?.filename || '').trim()
      const summary = String(item?.summary || item?.text || '').trim()
      if (filename && summary) {
        return `${filename}：${summary}`
      }
      return filename || summary
    })
    .filter(Boolean)
    .join('；')
}

function resolveAssociationDocumentTypeLabel(document) {
  const explicitLabel = String(document?.document_type_label || '').trim()
  if (explicitLabel) {
    return explicitLabel
  }

  const sceneLabel = String(document?.scene_label || '').trim()
  if (sceneLabel) {
    return sceneLabel
  }

  const typeLabel = resolveDocumentTypeLabel(document?.document_type)
  return String(typeLabel || '').trim() || '其他票据'
}

function buildAssociationDocumentContentLines(document) {
  const fields = Array.isArray(document?.document_fields) ? document.document_fields : []
  const fieldLines = fields
    .map((field) => {
      const label = String(field?.label || '').trim()
      const value = String(field?.value || '').trim()
      return label && value ? `- ${label}：${value}` : ''
    })
    .filter(Boolean)

  if (fieldLines.length) {
    return fieldLines.slice(0, 8)
  }

  const summary = String(document?.summary || document?.text || '').trim()
  if (summary) {
    return [`- 识别内容：${summary}`]
  }

  return ['- 识别内容：暂未提取到结构化字段，请以票据原件为准。']
}

function buildAssociationDocumentCard(lines) {
  return (Array.isArray(lines) ? lines : [])
    .map((line) => String(line || '').trim() ? `> ${line}` : '>')
    .join('\n')
}

export function buildAttachmentAssociationConfirmationMessage({
  claimNo = '',
  claimTitle = '',
  fileNames = [],
  ocrDocuments = []
} = {}) {
  const documents = Array.isArray(ocrDocuments) && ocrDocuments.length
    ? ocrDocuments
    : (Array.isArray(fileNames) ? fileNames : [])
      .map((filename) => ({ filename }))
      .filter((item) => String(item.filename || '').trim())
  const targetLines = [
    claimNo ? `- 草稿单号：${claimNo}` : '',
    claimTitle ? `- 单据说明：${claimTitle}` : '',
    `- 本次待归集附件：${documents.length || fileNames.length || 0} 份`
  ].filter(Boolean)

  const documentBlocks = documents.map((document, index) => {
    const filename = String(document?.filename || '').trim() || `附件 ${index + 1}`
    const typeLabel = resolveAssociationDocumentTypeLabel(document)
    const contentLines = buildAssociationDocumentContentLines(document)
      .map((line) => String(line || '').replace(/^-\s*/, ''))
    return buildAssociationDocumentCard([
      `**附件 ${index + 1}：${filename}**`,
      '',
      `附件类型：${typeLabel}`,
      '',
      ...contentLines
    ])
  })

  return [
    '已识别附件信息：',
    '',
    documentBlocks.join('\n\n'),
    '',
    '',
    '请问是否确定将票据信息归集到单据：',
    '',
    targetLines.join('\n'),
    '',
    '',
    `如果 **[确认](${ATTACHMENT_ASSOCIATION_CONFIRM_HREF})** 该信息，我将直接将票据进行归集。`
  ]
    .join('\n')
    .replace(/\n{4,}/g, '\n\n\n')
    .trim()
}

export function normalizeReviewDocumentFieldKey(label) {
  const compact = String(label || '').replace(/\s+/g, '').toLowerCase()
  if (!compact) return ''
  if (
    ['金额', '价税合计', '合计', '总额', '总计', '票价', '支付金额', '实付金额', '实收金额'].some((token) =>
      compact.includes(token.toLowerCase())
    )
  ) {
    return 'amount'
  }
  if (['日期', '时间', '开票日期', '发生时间'].some((token) => compact.includes(token.toLowerCase()))) {
    return 'date'
  }
  if (['商户', '酒店', '销售方', '开票方', '收款方'].some((token) => compact.includes(token.toLowerCase()))) {
    return 'merchant_name'
  }
  if (['票据号码', '发票号码', '票号', '单号', '订单号'].some((token) => compact.includes(token.toLowerCase()))) {
    return 'invoice_number'
  }
  if (compact.includes('发票代码')) {
    return 'invoice_code'
  }
  if (compact.includes('车次') || compact.includes('航班')) {
    return 'trip_no'
  }
  if (compact.includes('行程') || compact.includes('路线')) {
    return 'route'
  }
  return compact
}

export function buildOcrDocumentsFromReviewPayload(reviewPayload) {
  const documents = Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards : []
  return documents.slice(0, MAX_OCR_DOCUMENTS).map((item) => {
    const fields = Array.isArray(item?.fields)
      ? item.fields
        .map((field) => {
          const label = String(field?.label || '').trim()
          const value = String(field?.value || '').trim()
          if (!label || !value) {
            return null
          }
          return {
            key: normalizeReviewDocumentFieldKey(label),
            label,
            value
          }
        })
        .filter(Boolean)
      : []

    return {
      filename: String(item?.filename || '').trim(),
      summary: String(item?.summary || '').trim(),
      text: [
        String(item?.scene_label || '').trim(),
        String(item?.summary || '').trim(),
        ...fields.map((field) => `${field.label}：${field.value}`)
      ]
        .filter(Boolean)
        .join(' ')
        .slice(0, 240),
      avg_score: Number(item?.avg_score || 0),
      document_type: String(item?.document_type || 'other').trim() || 'other',
      document_type_label: resolveDocumentTypeLabel(item?.document_type),
      scene_code: resolveExpenseTypeCode(item?.suggested_expense_type),
      scene_label: String(item?.scene_label || '').trim(),
      preview_kind: String(item?.preview_kind || '').trim(),
      preview_data_url: String(item?.preview_data_url || '').trim(),
      preview_url: String(item?.preview_url || '').trim(),
      document_fields: fields,
      warnings: Array.isArray(item?.warnings) ? item.warnings : []
    }
  }).filter((item) => item.filename)
}

export function mergeUploadAttachmentNames(existingNames, incomingNames) {
  const merged = []
  const seen = new Set()

  for (const value of [...(existingNames || []), ...(incomingNames || [])]) {
    const normalized = String(value || '').trim()
    if (!normalized || seen.has(normalized)) continue
    seen.add(normalized)
    merged.push(normalized)
    if (merged.length >= MAX_ATTACHMENTS) {
      break
    }
  }

  return merged
}

export function mergeUploadOcrDocuments(existingDocuments, incomingDocuments) {
  const merged = []
  const seen = new Set()

  for (const item of [...(existingDocuments || []), ...(incomingDocuments || [])]) {
    const filename = String(item?.filename || '').trim()
    if (!filename || seen.has(filename)) continue
    seen.add(filename)
    merged.push(item)
    if (merged.length >= MAX_OCR_DOCUMENTS) {
      break
    }
  }

  return merged
}

export function inferPreviewKind(file) {
  return inferPreviewKindFromFile(file)
}

export function buildFilePreviews(files, previewRegistry) {
  return files.map((file) => {
    const kind = inferPreviewKind(file)
    if (!['image', 'pdf'].includes(kind)) {
      return {
        filename: file.name,
        kind
      }
    }

    const url = URL.createObjectURL(file)
    previewRegistry.push(url)
    return {
      filename: file.name,
      kind,
      url
    }
  })
}

export function resolveDocumentPreview(filePreviews, filename) {
  if (!Array.isArray(filePreviews)) return null
  const matches = filePreviews.filter((item) => item.filename === filename)
  if (!matches.length) {
    return null
  }
  return (
    matches.find((item) => item.kind === 'image' && item.url) ||
    matches.find((item) => item.url) ||
    matches[0]
  )
}

export function isTemporaryPreviewUrl(url) {
  return isTemporaryDocumentPreviewUrl(url)
}

export function buildFileIdentity(file) {
  return [file?.name, file?.size, file?.lastModified, file?.type].join('__')
}

export function mergeFilesWithLimit(existingFiles, incomingFiles, limit = MAX_ATTACHMENTS) {
  const nextFiles = []
  const seen = new Set()

  for (const file of Array.isArray(existingFiles) ? existingFiles : []) {
    const key = buildFileIdentity(file)
    if (seen.has(key)) continue
    seen.add(key)
    nextFiles.push(file)
  }

  let duplicateCount = 0
  let overflowCount = 0

  for (const file of Array.isArray(incomingFiles) ? incomingFiles : []) {
    const key = buildFileIdentity(file)
    if (seen.has(key)) {
      duplicateCount += 1
      continue
    }
    if (nextFiles.length >= limit) {
      overflowCount += 1
      continue
    }
    seen.add(key)
    nextFiles.push(file)
  }

  return {
    files: nextFiles,
    duplicateCount,
    overflowCount
  }
}

export function mergeFilePreviews(existingPreviews, incomingPreviews) {
  const result = []
  const indexByKey = new Map()

  for (const preview of [...(existingPreviews || []), ...(incomingPreviews || [])]) {
    const key = [preview?.filename, preview?.kind].join('__')
    if (!preview?.filename) continue

    const existingIndex = indexByKey.get(key)
    if (existingIndex === undefined) {
      indexByKey.set(key, result.length)
      result.push(preview)
      continue
    }

    const existingPreview = result[existingIndex]
    const nextUrl = String(preview?.url || '').trim()
    const existingUrl = String(existingPreview?.url || '').trim()
    if (nextUrl && (!existingUrl || isTemporaryPreviewUrl(existingUrl) || nextUrl !== existingUrl)) {
      result[existingIndex] = preview
    }
  }

  return result
}

export function filterPersistableFilePreviews(filePreviews) {
  return (Array.isArray(filePreviews) ? filePreviews : [])
    .filter((preview) => {
      const filename = String(preview?.filename || '').trim()
      const url = String(preview?.url || '').trim()
      return filename && !isTemporaryPreviewUrl(url)
    })
}

function resolveDocumentPreviewKind(item) {
  return resolveUnifiedDocumentPreviewKind(item)
}

export function buildOcrFilePreviews(payload) {
  const documents = Array.isArray(payload?.documents) ? payload.documents : []
  return documents
    .map((item) => {
      const asset = resolveDocumentPreviewAsset(item)
      return {
        filename: String(item?.filename || '').trim(),
        kind: asset?.kind || '',
        url: asset?.url || ''
      }
    })
    .filter((item) => item.filename && item.kind === 'image' && item.url)
}

export function buildReviewFilePreviewsFromReviewPayload(reviewPayload) {
  const documents = Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards : []
  return documents
    .map((item) => {
      const asset = resolveDocumentPreviewAsset(item)
      return {
        filename: String(item?.filename || '').trim(),
        kind: asset?.kind || '',
        url: asset?.url || ''
      }
    })
    .filter((item) => item.filename && item.kind === 'image' && item.url)
}

export function buildReviewFilePreviewsFromMessages(messages) {
  const previews = []
  for (const message of Array.isArray(messages) ? messages : []) {
    previews.push(...buildReviewFilePreviewsFromReviewPayload(message?.reviewPayload))
  }
  return mergeFilePreviews([], previews)
}

export function resolveAttachmentPreviewKind(metadata) {
  const explicitKind = String(metadata?.preview_kind || '').trim()
  if (explicitKind) {
    return explicitKind
  }

  const mediaType = String(metadata?.media_type || '').trim().toLowerCase()
  if (mediaType.startsWith('image/')) {
    return 'image'
  }
  if (mediaType === 'application/pdf') {
    return 'pdf'
  }
  return ''
}

export function extractReviewAttachmentNames(reviewPayload) {
  const documentNames = Array.isArray(reviewPayload?.document_cards)
    ? reviewPayload.document_cards.map((item) => String(item?.filename || '').trim()).filter(Boolean)
    : []
  if (documentNames.length) {
    return documentNames
  }

  const slotMap = buildReviewSlotMap(reviewPayload)
  const attachmentValue = String(slotMap.attachments?.value || '').trim()
  if (!attachmentValue) {
    return []
  }

  return attachmentValue.split(/[、,，]/).map((item) => item.trim()).filter(Boolean)
}

export function buildErrorInsight(error, fileNames = []) {
  return {
    intent: 'agent',
    metricLabel: '运行状态',
    metricValue: '失败',
    title: '智能体调用失败',
    summary: error?.message || '无法连接后端 Orchestrator。',
    agent: {
      runId: '未生成',
      selectedAgent: 'orchestrator',
      scenario: '未知',
      intent: '未知',
      permissionLevel: 'unknown',
      routeReason: 'request_failed',
      requiresConfirmation: false,
      degraded: false,
      fileNames,
      citations: [],
      suggestedActions: [],
      queryPayload: null,
      draftPayload: null,
      reviewPayload: null,
      riskFlags: [],
      toolCount: 0,
      failedToolCount: 0,
      selectedCapabilityCodes: [],
      filePreviews: [],
      statusLabel: '失败',
      statusTone: 'note'
    }
  }
}

export function buildAgentInsight(payload, fileNames = [], filePreviews = []) {
  const trace = payload?.trace_summary || {}
  const result = payload?.result || {}
  const statusLabel = resolveStatusLabel(payload?.status)

  return {
    intent: 'agent',
    metricLabel: '运行状态',
    metricValue: statusLabel,
    title:
      result?.draft_payload?.title ||
      `${SCENARIO_LABELS[trace?.scenario] || '通用'}${INTENT_LABELS[trace?.intent] || '处理'}结果`,
    summary: result?.answer || result?.message || '智能体已完成处理。',
    agent: {
      runId: payload?.run_id || '未生成',
      selectedAgent: payload?.selected_agent || 'orchestrator',
      scenario: SCENARIO_LABELS[trace?.scenario] || trace?.scenario || '未知',
      intent: INTENT_LABELS[trace?.intent] || trace?.intent || '未知',
      permissionLevel: payload?.permission_level || 'unknown',
      routeReason: payload?.route_reason || 'unknown',
      requiresConfirmation: Boolean(payload?.requires_confirmation),
      degraded: Boolean(trace?.degraded),
      fileNames,
      citations: Array.isArray(result?.citations) ? result.citations : [],
      suggestedActions: Array.isArray(result?.suggested_actions) ? result.suggested_actions : [],
      queryPayload: normalizeExpenseQueryPayload(result?.query_payload),
      draftPayload: result?.draft_payload || null,
      reviewPayload: result?.review_payload || null,
      riskFlags: Array.isArray(result?.risk_flags) ? result.risk_flags : [],
      toolCount: Number(trace?.tool_count || 0),
      failedToolCount: Number(trace?.failed_tool_count || 0),
      selectedCapabilityCodes: Array.isArray(trace?.selected_capability_codes)
        ? trace.selected_capability_codes
        : [],
      filePreviews,
      statusLabel,
      statusTone: resolveStatusTone(payload?.status)
    }
  }
}
