<template>
  <article v-if="visible" class="detail-card panel risk-observation-evidence-card">
    <div class="detail-card-head">
      <div>
        <h3 class="detail-card-title-with-icon">
          <i class="mdi mdi-shield-search"></i>
          <span>风险证据链</span>
        </h3>
        <p>展示当前单据进入统一风险观察池后的评分、证据、图谱关系和反馈状态。</p>
      </div>
      <button
        class="risk-evidence-refresh"
        type="button"
        :disabled="loading"
        @click="loadObservations"
      >
        <i :class="loading ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-refresh'"></i>
        <span>刷新</span>
      </button>
    </div>

    <div v-if="loading" class="risk-evidence-state">
      <i class="mdi mdi-loading mdi-spin"></i>
      <span>正在加载风险证据链</span>
    </div>
    <div v-else-if="errorMessage" class="risk-evidence-state error">
      <i class="mdi mdi-alert-circle-outline"></i>
      <span>{{ errorMessage }}</span>
    </div>
    <template v-else-if="mainObservation">
      <div class="risk-evidence-current-head">
        <div>
          <span>当前风险观察详情</span>
          <strong>{{ mainObservation.title || formatSignal(mainObservation.riskSignal) }}</strong>
        </div>
        <em>{{ activeObservationPosition }}</em>
      </div>

      <div
        :id="detailRegionId"
        class="risk-evidence-detail-region"
        role="region"
        aria-live="polite"
      >
        <div class="risk-evidence-summary">
          <div class="risk-evidence-score" :class="mainObservation.riskLevel">
            <strong>{{ mainObservation.riskScore }}</strong>
            <span>{{ formatRiskLevel(mainObservation.riskLevel) }}</span>
          </div>
          <div class="risk-evidence-copy">
            <h4>{{ mainObservation.title || formatSignal(mainObservation.riskSignal) }}</h4>
            <p>{{ mainObservation.description || '暂无风险描述。' }}</p>
            <div class="risk-evidence-meta">
              <span>{{ formatSource(mainObservation.source) }}</span>
              <span>{{ mainObservation.algorithmVersion || '未记录算法版本' }}</span>
              <span>{{ formatFeedbackStatus(mainObservation.feedbackStatus) }}</span>
            </div>
          </div>
        </div>

        <section class="risk-disposition-panel" aria-label="风险处置">
          <div class="risk-disposition-head">
            <div>
              <span>风险处置</span>
              <strong>{{ dispositionStateLabel }}</strong>
            </div>
            <div class="risk-disposition-badges">
              <em :class="currentDisposition.adjudication">
                {{ formatAdjudication(currentDisposition.adjudication) }}
              </em>
              <em :class="currentDisposition.lifecycleStatus">
                {{ formatLifecycleStatus(currentDisposition.lifecycleStatus) }}
              </em>
              <em>v{{ currentDisposition.version }}</em>
            </div>
          </div>
          <p v-if="currentDisposition.resolution" class="risk-disposition-resolution">
            {{ currentDisposition.resolution }}
          </p>
          <RiskWaiverRecord :disposition="currentDisposition" :expired="waiverExpired" />
          <template v-if="dispositionActions.length">
            <textarea
              v-if="nonWaiverActions.length"
              v-model="dispositionNote"
              :disabled="dispositionBusy"
              maxlength="1000"
              placeholder="填写复核依据、补充要求或解决说明（误报、补材料和解决时必填）"
            ></textarea>
            <div class="risk-disposition-actions">
              <button
                v-for="action in dispositionActions"
                :key="action.action"
                type="button"
                :class="action.tone"
                :disabled="dispositionBusy"
                @click="handleDispositionAction(action.action)"
              >
                <i :class="dispositionBusyAction === action.action ? 'mdi mdi-loading mdi-spin' : action.icon"></i>
                {{ action.label }}
              </button>
            </div>
          </template>
          <p v-else class="risk-disposition-complete">{{ dispositionCompleteMessage }}</p>
          <p
            v-if="currentDisposition.readOnlyReason"
            class="risk-disposition-read-only"
          >
            <i class="mdi mdi-lock-outline"></i>
            {{ currentDisposition.readOnlyReason }}
          </p>
          <div
            v-if="dispositionError"
            class="risk-disposition-error"
            role="alert"
            :data-error-code="dispositionError.code"
          >
            <strong>{{ dispositionError.title }}</strong>
            <span>{{ dispositionError.message }}</span>
          </div>
        </section>

        <div class="risk-evidence-grid">
          <section class="risk-evidence-section">
            <span class="risk-evidence-section-title">贡献分</span>
            <div class="risk-score-list">
              <div v-for="item in scoreItems" :key="item.key" class="risk-score-row">
                <span>{{ item.label }}</span>
                <i><b :style="{ width: item.width }"></b></i>
                <strong>{{ item.value }}</strong>
              </div>
            </div>
          </section>

          <section class="risk-evidence-section">
            <span class="risk-evidence-section-title">证据</span>
            <ul v-if="evidenceRows.length" class="risk-evidence-list">
              <li v-for="item in evidenceRows" :key="item.key">
                <strong>{{ item.title }}</strong>
                <span>{{ item.detail }}</span>
              </li>
            </ul>
            <p v-else class="risk-evidence-empty">暂无证据明细。</p>
          </section>

          <section class="risk-evidence-section">
            <span class="risk-evidence-section-title">图谱关系</span>
            <div class="risk-chip-list">
              <span v-for="item in graphItems" :key="item">{{ item }}</span>
              <em v-if="!graphItems.length">暂无图谱关系。</em>
            </div>
          </section>

          <section class="risk-evidence-section">
            <span class="risk-evidence-section-title">基线与建议</span>
            <div class="risk-chip-list">
              <span v-for="item in baselineAndDecisionItems" :key="item">{{ item }}</span>
              <em v-if="!baselineAndDecisionItems.length">暂无基线或建议动作。</em>
            </div>
          </section>

          <section class="risk-evidence-section">
            <span class="risk-evidence-section-title">制度与相似案例</span>
            <div class="risk-chip-list">
              <span v-for="item in policyAndSimilarItems" :key="item">{{ item }}</span>
              <em v-if="!policyAndSimilarItems.length">暂无制度引用或相似案例。</em>
            </div>
          </section>

          <section class="risk-evidence-section">
            <span class="risk-evidence-section-title">反馈历史</span>
            <ul v-if="feedbackRows.length" class="risk-evidence-list">
              <li v-for="item in feedbackRows" :key="item.key">
                <strong>{{ item.title }}</strong>
                <span>{{ item.detail }}</span>
              </li>
            </ul>
            <p v-else class="risk-evidence-empty">暂无人工反馈。</p>
          </section>
        </div>
      </div>

      <div v-if="observations.length > 1" class="risk-observation-list">
        <button
          v-for="(item, index) in observations"
          :key="observationIdentity(item, index)"
          type="button"
          class="risk-observation-row"
          :class="{ active: isActiveObservation(item, index) }"
          :aria-pressed="isActiveObservation(item, index)"
          :aria-controls="detailRegionId"
          @click="selectObservation(item, index)"
        >
          <span>{{ formatRiskLevel(item.riskLevel) }}</span>
          <strong>{{ item.title || formatSignal(item.riskSignal) }}</strong>
          <em>{{ item.riskScore }}</em>
        </button>
      </div>
    </template>
    <RiskWaiverActionDialog
      :open="Boolean(waiverDialogAction)"
      :action="waiverDialogAction"
      :busy="dispositionBusy"
      :disposition="currentDisposition"
      @cancel="closeWaiverDialog"
      @confirm="confirmWaiverAction"
    />
  </article>
</template>

<script setup>
import { computed, onBeforeUnmount, ref, watch } from 'vue'

import {
  createRiskDispositionRequestId,
  executeRiskDispositionAction,
  fetchClaimRiskObservations,
  normalizeRiskDispositionError
} from '../../services/riskObservations.js'
import RiskWaiverActionDialog from './RiskWaiverActionDialog.vue'
import RiskWaiverRecord from './RiskWaiverRecord.vue'

const props = defineProps({
  claimId: { type: String, default: '' }
})

const observations = ref([])
const loading = ref(false)
const errorMessage = ref('')
const activeObservationKey = ref('')
const dispositionNote = ref('')
const dispositionBusyAction = ref('')
const dispositionError = ref(null)
const dispositionRequest = ref({ key: '', requestId: '' })
const waiverDialogAction = ref('')
const waiverClock = ref(Date.now())
const detailRegionId = 'risk-observation-active-detail'
let loadSequence = 0
let waiverExpiryTimer = 0

const waiverActions = new Set(['request_waiver', 'approve_waiver', 'reject_waiver'])
const actionCatalog = Object.freeze({
  confirm: {
    action: 'confirm', label: '确认风险', icon: 'mdi mdi-shield-check', tone: 'danger'
  },
  false_positive: {
    action: 'false_positive', label: '标记误报', icon: 'mdi mdi-shield-off-outline', tone: 'safe'
  },
  request_supplement: {
    action: 'request_supplement',
    label: '请求补材料',
    icon: 'mdi mdi-file-plus-outline',
    tone: 'warning'
  },
  start_remediation: {
    action: 'start_remediation', label: '启动整改', icon: 'mdi mdi-tools', tone: 'warning'
  },
  request_waiver: {
    action: 'request_waiver', label: '申请豁免', icon: 'mdi mdi-file-sign', tone: 'neutral'
  },
  approve_waiver: {
    action: 'approve_waiver',
    label: '批准豁免',
    icon: 'mdi mdi-check-decagram-outline',
    tone: 'safe'
  },
  reject_waiver: {
    action: 'reject_waiver', label: '拒绝豁免', icon: 'mdi mdi-close-octagon-outline', tone: 'danger'
  },
  resolve: {
    action: 'resolve', label: '确认已解决', icon: 'mdi mdi-check-decagram-outline', tone: 'safe'
  }
})

const visible = computed(() =>
  loading.value || Boolean(errorMessage.value) || observations.value.length > 0
)
const mainObservation = computed(() => {
  if (!observations.value.length) {
    return null
  }
  return (
    observations.value.find((item, index) => isActiveObservation(item, index))
    || observations.value[0]
  )
})
const activeObservationPosition = computed(() => {
  if (observations.value.length <= 1) {
    return '单条观察'
  }
  const activeIndex = observations.value.findIndex((item, index) =>
    isActiveObservation(item, index)
  )
  return activeIndex >= 0 ? `${activeIndex + 1} / ${observations.value.length}` : '未选择'
})
const currentDisposition = computed(() => {
  if (mainObservation.value?.disposition) {
    return mainObservation.value.disposition
  }
  const feedbackStatus = String(mainObservation.value?.feedbackStatus || '').trim()
  const status = String(mainObservation.value?.status || '').trim()
  return {
    adjudication: ['confirmed', 'false_positive'].includes(feedbackStatus)
      ? feedbackStatus
      : 'unreviewed',
    lifecycleStatus: status === 'resolved' ? 'resolved' : 'open',
    version: 0,
    resolution: '',
    availableActions: mainObservation.value?.availableActions || [],
    readOnlyReason: mainObservation.value?.availableActions?.length
      ? ''
      : mainObservation.value?.readOnlyReason
        || '服务端未返回可执行动作，当前风险仅供查看。',
    waiverConditions: []
  }
})
const dispositionBusy = computed(() => Boolean(dispositionBusyAction.value))
const waiverExpired = computed(() => {
  const expiry = new Date(currentDisposition.value.waiverExpiresAt)
  return !Number.isNaN(expiry.getTime()) && expiry.getTime() <= waiverClock.value
})
const dispositionStateLabel = computed(() => {
  if (currentDisposition.value.adjudication === 'false_positive') {
    return '该观察已判定为误报，不再阻断审批'
  }
  if (currentDisposition.value.lifecycleStatus === 'resolved') {
    return '风险已完成处置，不再阻断审批'
  }
  if (currentDisposition.value.lifecycleStatus === 'waived') {
    return waiverExpired.value
      ? '风险豁免已经到期，将继续阻断审批'
      : `风险已限时豁免，有效至 ${formatDateTime(currentDisposition.value.waiverExpiresAt)}`
  }
  if (currentDisposition.value.lifecycleStatus === 'waiver_rejected') {
    return '风险豁免已拒绝，仍会阻断审批'
  }
  if (currentDisposition.value.lifecycleStatus === 'waiver_requested') {
    return '豁免申请等待决定，当前仍会阻断审批'
  }
  if (['high', 'critical'].includes(String(mainObservation.value?.riskLevel || ''))) {
    return '高风险未关闭，将阻断审批通过'
  }
  return '请结合证据完成人工裁决'
})
const dispositionActions = computed(() => (
  (currentDisposition.value.availableActions || [])
    .map((action) => actionCatalog[action])
    .filter(Boolean)
))
const nonWaiverActions = computed(() => (
  dispositionActions.value.filter((item) => !waiverActions.has(item.action))
))
const dispositionCompleteMessage = computed(() => {
  if (currentDisposition.value.lifecycleStatus === 'waiver_rejected') {
    return '豁免已拒绝；风险维持阻断，需完成整改或其他服务端允许的处置。'
  }
  if (currentDisposition.value.lifecycleStatus === 'waived') {
    return waiverExpired.value
      ? '豁免已到期；风险控制已经恢复。'
      : `豁免仅在 ${formatDateTime(currentDisposition.value.waiverExpiresAt)} 前有效。`
  }
  if (currentDisposition.value.adjudication === 'false_positive') {
    return '该风险已标记为误报；如需更正，请通过审计流程处理。'
  }
  if (currentDisposition.value.lifecycleStatus === 'resolved') {
    return '该风险已解决，处置事件会保留在追加式审计链中。'
  }
  return '当前没有服务端授权的可执行动作。'
})
const scoreItems = computed(() => {
  const scores = mainObservation.value?.contributionScores || {}
  return Object.entries(scores).map(([key, value]) => {
    const numericValue = Math.max(0, Math.min(Number(value || 0), 100))
    return {
      key,
      label: formatScoreLabel(key),
      value: Math.round(numericValue),
      width: `${numericValue}%`
    }
  })
})
const evidenceRows = computed(() =>
  (mainObservation.value?.evidence || []).slice(0, 6).map((item, index) => ({
    key: `${item.code || item.title || index}`,
    title: String(item.title || item.code || item.source || `证据 ${index + 1}`).trim(),
    detail: String(item.detail || item.message || item.summary || '').trim() || '已记录证据。'
  }))
)
const graphItems = computed(() => [
  ...(mainObservation.value?.graphNodeKeys || []).slice(0, 4),
  ...(mainObservation.value?.graphEdgeKeys || []).slice(0, 4)
].map(formatChipValue).filter(Boolean))
const baselineAndDecisionItems = computed(() => [
  ...formatRecordItems(mainObservation.value?.baseline, '基线'),
  ...formatRecordItems(mainObservation.value?.decisionTrace, '建议')
])
const policyAndSimilarItems = computed(() => [
  ...(mainObservation.value?.policyRefs || [])
    .map(formatChipValue)
    .filter(Boolean)
    .map((item) => `制度：${item}`),
  ...(mainObservation.value?.similarCaseClaimIds || [])
    .map(formatChipValue)
    .filter(Boolean)
    .map((item) => `相似案例：${item}`)
])
const feedbackRows = computed(() =>
  (mainObservation.value?.feedbackItems || []).slice(0, 5).map((item, index) => {
    const feedbackType = item.feedbackType || item.feedback_type || item.type
    const actor = String(item.actor || item.createdBy || item.created_by || '未记录人员').trim()
    const createdAt = String(item.createdAt || item.created_at || '').trim()
    const comment = String(item.comment || item.remark || item.message || '').trim()
    return {
      key: `${item.id || createdAt || index}`,
      title: `${formatFeedbackType(feedbackType)} · ${actor}`,
      detail: [comment || '已记录反馈。', createdAt].filter(Boolean).join(' · ')
    }
  })
)

watch(
  () => props.claimId,
  () => {
    waiverDialogAction.value = ''
    dispositionError.value = null
    dispositionRequest.value = { key: '', requestId: '' }
    void loadObservations()
  },
  { immediate: true }
)

watch(
  () => currentDisposition.value.waiverExpiresAt,
  (expiresAt) => scheduleWaiverExpiryRefresh(expiresAt),
  { immediate: true }
)

onBeforeUnmount(clearWaiverExpiryTimer)

async function loadObservations() {
  const claimId = String(props.claimId || '').trim()
  const sequence = ++loadSequence
  errorMessage.value = ''
  if (!claimId) {
    observations.value = []
    activeObservationKey.value = ''
    loading.value = false
    return
  }

  loading.value = true
  try {
    const payload = await fetchClaimRiskObservations(claimId)
    if (sequence !== loadSequence) {
      return
    }
    observations.value = payload
    activeObservationKey.value = observationIdentity(payload[0], 0)
  } catch (error) {
    if (sequence === loadSequence) {
      observations.value = []
      activeObservationKey.value = ''
      errorMessage.value = error?.message || '风险证据链加载失败。'
    }
  } finally {
    if (sequence === loadSequence) {
      loading.value = false
    }
  }
}

function clearWaiverExpiryTimer() {
  if (waiverExpiryTimer) {
    globalThis.clearTimeout(waiverExpiryTimer)
    waiverExpiryTimer = 0
  }
}

function scheduleWaiverExpiryRefresh(value) {
  clearWaiverExpiryTimer()
  waiverClock.value = Date.now()
  const expiresAt = new Date(value).getTime()
  if (!Number.isFinite(expiresAt) || expiresAt <= waiverClock.value) return
  const maximumDelay = 2_147_000_000
  const delay = Math.min(expiresAt - waiverClock.value + 50, maximumDelay)
  waiverExpiryTimer = globalThis.setTimeout(async () => {
    waiverExpiryTimer = 0
    waiverClock.value = Date.now()
    if (waiverClock.value >= expiresAt) {
      await loadObservations()
      return
    }
    scheduleWaiverExpiryRefresh(value)
  }, delay)
}

function observationIdentity(item, index = -1) {
  const explicitKey = String(item?.observationKey || item?.id || '').trim()
  if (explicitKey) {
    return explicitKey
  }
  return [
    item?.claimId,
    item?.riskSignal,
    item?.createdAt,
    index >= 0 ? index : ''
  ].map((value) => String(value || '').trim()).filter(Boolean).join(':')
}

function isActiveObservation(item, index = -1) {
  return observationIdentity(item, index) === activeObservationKey.value
}

function selectObservation(item, index = -1) {
  const key = observationIdentity(item, index)
  if (key) {
    activeObservationKey.value = key
    dispositionNote.value = ''
    dispositionError.value = null
    dispositionRequest.value = { key: '', requestId: '' }
    waiverDialogAction.value = ''
  }
}

function handleDispositionAction(action) {
  if (!isActionAvailable(action) || dispositionBusy.value) return
  dispositionError.value = null
  if (waiverActions.has(action)) {
    waiverDialogAction.value = action
    return
  }
  void submitDispositionAction(action)
}

function closeWaiverDialog() {
  if (!dispositionBusy.value) waiverDialogAction.value = ''
}

async function confirmWaiverAction(payload) {
  const succeeded = await submitDispositionAction(payload?.action, payload)
  if (succeeded) waiverDialogAction.value = ''
}

function isActionAvailable(action) {
  return (currentDisposition.value.availableActions || []).includes(action)
}

async function submitDispositionAction(action, actionPayload = {}) {
  const observationId = String(mainObservation.value?.id || '').trim()
  if (!observationId || dispositionBusy.value) {
    return false
  }
  if (!isActionAvailable(action)) {
    dispositionError.value = {
      code: 'RISK_DISPOSITION_ACTION_UNAVAILABLE',
      title: '操作权限已经变化',
      message: currentDisposition.value.readOnlyReason || '服务端未授权当前风险处置动作。'
    }
    waiverDialogAction.value = ''
    return false
  }
  const note = dispositionNote.value.trim()
  if (['false_positive', 'request_supplement', 'resolve'].includes(action) && !note) {
    dispositionError.value = {
      code: 'RISK_DISPOSITION_NOTE_REQUIRED',
      title: '需要填写处理说明',
      message: '该处置动作需要填写复核依据或处理说明。'
    }
    return false
  }
  const mutationPayload = {
    ...actionPayload,
    comment: String(actionPayload.comment || note).trim(),
    resolution: action === 'resolve' ? note : ''
  }
  const requestKey = [
    observationId,
    action,
    currentDisposition.value.version,
    JSON.stringify(mutationPayload)
  ].join(':')
  if (dispositionRequest.value.key !== requestKey) {
    dispositionRequest.value = {
      key: requestKey,
      requestId: createRiskDispositionRequestId(action, observationId)
    }
  }
  dispositionBusyAction.value = action
  dispositionError.value = null
  try {
    await executeRiskDispositionAction(observationId, {
      ...mutationPayload,
      action,
      expectedVersion: currentDisposition.value.version,
      requestId: dispositionRequest.value.requestId
    })
    dispositionNote.value = ''
    dispositionRequest.value = { key: '', requestId: '' }
    await loadObservations()
    return true
  } catch (error) {
    const normalizedError = normalizeRiskDispositionError(error)
    dispositionError.value = normalizedError
    if (normalizedError.shouldRefresh) {
      if (normalizedError.code !== 'REQUEST_TIMEOUT') {
        dispositionRequest.value = { key: '', requestId: '' }
      }
      await loadObservations()
      if (!isActionAvailable(action)) waiverDialogAction.value = ''
    }
    return false
  } finally {
    dispositionBusyAction.value = ''
  }
}

function formatScoreLabel(value) {
  const labels = {
    S_rule: '规则命中',
    S_anomaly: '画像偏离',
    S_graph: '图谱异常',
    S_policy: '制度约束',
    S_history: '历史反馈'
  }
  return labels[value] || value
}

function formatChipValue(value) {
  if (typeof value === 'string') {
    return value.trim()
  }
  if (value && typeof value === 'object') {
    return String(value.key || value.edge_key || value.node_key || JSON.stringify(value)).trim()
  }
  return String(value || '').trim()
}

function formatRecordItems(value, prefix) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return []
  }
  return Object.entries(value)
    .slice(0, 6)
    .map(([key, item]) => `${prefix}：${key}=${formatChipValue(item)}`)
    .filter(Boolean)
}

function formatRiskLevel(value) {
  const labels = {
    critical: '重大风险',
    high: '高风险',
    medium: '中风险',
    low: '低风险'
  }
  return labels[String(value || '').trim()] || '未知风险'
}

function formatSignal(value) {
  const text = String(value || '').trim()
  const labels = {
    duplicate_invoice: '重复发票',
    split_billing: '拆分报销',
    frequent_small_claims: '高频小额',
    location_mismatch: '地点不一致',
    amount_outlier: '金额异常',
    preapproval_absent: '缺少事前申请'
  }
  return labels[text] || text.replace(/_/g, ' ') || '未知风险'
}

function formatSource(value) {
  const labels = {
    financial_risk_graph: '风险图谱',
    rule_center: '规则中心'
  }
  return labels[String(value || '').trim()] || '风险观察池'
}

function formatFeedbackType(value) {
  const labels = {
    confirm: '确认风险',
    confirmed: '确认风险',
    false_positive: '标记误报',
    ignore: '忽略',
    ignored: '忽略',
    resolve: '已处理',
    resolved: '已处理',
    comment: '备注'
  }
  return labels[String(value || '').trim()] || '人工反馈'
}

function formatFeedbackStatus(value) {
  const labels = {
    confirmed: '已确认',
    false_positive: '已标记误报',
    ignored: '已忽略',
    resolved: '已处理',
    unreviewed: '未复核'
  }
  return labels[String(value || '').trim()] || '未复核'
}

function formatDateTime(value) {
  const date = new Date(value)
  if (Number.isNaN(date.getTime())) return '未记录'
  return new Intl.DateTimeFormat('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    hour12: false
  }).format(date)
}

function formatAdjudication(value) {
  const labels = {
    unreviewed: '待裁决',
    confirmed: '风险成立',
    false_positive: '误报'
  }
  return labels[String(value || '').trim()] || '待裁决'
}

function formatLifecycleStatus(value) {
  const labels = {
    open: '待处置',
    supplement_requested: '待补材料',
    remediation_in_progress: '整改中',
    waiver_requested: '豁免申请中',
    waived: '限时豁免',
    waiver_rejected: '豁免已拒绝',
    resolved: '已解决'
  }
  return labels[String(value || '').trim()] || '待处置'
}
</script>

<style scoped src="../../assets/styles/components/risk-observation-evidence-card.css"></style>
