<template>
  <section v-if="canView" class="connector-health panel" aria-label="财务连接器运行观测">
    <header class="connector-health__head">
      <div>
        <span class="connector-health__eyebrow">FINANCIAL CONNECTOR</span>
        <h3>外部回执与对账健康度</h3>
        <p>只展示脱敏运行事实；模拟回执和人工付款不会冒充生产现金证明。</p>
      </div>
      <button type="button" :disabled="loading" @click="load">
        <i :class="loading ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-refresh'"></i>
        {{ loading ? '刷新中' : '刷新' }}
      </button>
    </header>

    <div v-if="error" class="connector-health__state is-error" role="alert">
      <i class="mdi mdi-alert-circle-outline"></i>
      <span>{{ error }}</span>
    </div>
    <div v-else-if="loading && !payload" class="connector-health__state" role="status">
      <i class="mdi mdi-loading mdi-spin"></i>
      <span>正在读取当前租户连接器指标</span>
    </div>
    <template v-else>
      <div class="connector-health__metrics">
        <article v-for="metric in metrics" :key="metric.key" :class="metric.tone">
          <span>{{ metric.label }}</span>
          <strong>{{ metric.value }}</strong>
          <small :title="metric.detail">{{ metric.detail }}</small>
        </article>
      </div>

      <div class="connector-health__evidence" aria-label="付款证据可信等级说明">
        <strong>证据口径</strong>
        <span class="is-external"><i class="mdi mdi-shield-check-outline"></i>生产外部回执 · 高可信现金事实</span>
        <span class="is-manual"><i class="mdi mdi-account-check-outline"></i>人工付款确认 · 低等级内部状态</span>
        <span class="is-simulated"><i class="mdi mdi-flask-outline"></i>模拟/预发布回执 · 仅验证，不入核心账</span>
      </div>

      <div v-if="items.length" class="connector-health__table-wrap">
        <table>
          <thead>
            <tr>
              <th>来源</th>
              <th>环境 / 证据</th>
              <th>状态</th>
              <th>最近成功</th>
              <th>事件</th>
              <th>失败率</th>
              <th>积压 / 异常</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="item in items" :key="item.config_id">
              <td><strong>{{ item.provider }}</strong><small>{{ item.key_version }}</small></td>
              <td><span :class="['evidence-chip', evidenceTone(item)]">{{ item.evidence_label }}</span></td>
              <td><span :class="['status-chip', item.status]">{{ statusLabel(item.status) }}</span></td>
              <td>{{ formatTime(item.last_success_at) }}</td>
              <td>{{ number(item.event_count) }}</td>
              <td>{{ percent(item.failure_rate) }}</td>
              <td>{{ number(item.backlog_count) }} / {{ number(item.reconciliation_anomaly_count) }}</td>
            </tr>
          </tbody>
        </table>
      </div>
      <div v-else class="connector-health__state">
        <i class="mdi mdi-lan-disconnect"></i>
        <span>当前租户尚未配置财务连接器。</span>
      </div>
      <footer v-if="payload">
        统计窗口 {{ payload.window_hours || 24 }} 小时 · 更新于 {{ formatTime(payload.generated_at) }}
      </footer>
    </template>
  </section>
</template>

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

import { fetchFinancialConnectorObservability } from '../../services/financialConnectors.js'

const props = defineProps({
  currentUser: { type: Object, default: () => ({}) },
  windowHours: { type: Number, default: 24 }
})

const payload = ref(null)
const loading = ref(false)
const error = ref('')
let requestSequence = 0

const roleCodes = computed(() => (
  Array.isArray(props.currentUser?.roleCodes)
    ? props.currentUser.roleCodes.map((item) => String(item || '').trim().toLowerCase())
    : []
))
const canView = computed(() => (
  Boolean(props.currentUser?.isAdmin)
  || roleCodes.value.includes('finance')
  || roleCodes.value.includes('executive')
))
const summary = computed(() => payload.value?.summary || {})
const items = computed(() => Array.isArray(payload.value?.items) ? payload.value.items : [])
const lastSuccess = computed(() => {
  const timestamps = items.value
    .map((item) => Date.parse(item.last_success_at || ''))
    .filter(Number.isFinite)
  return timestamps.length ? new Date(Math.max(...timestamps)).toISOString() : ''
})
const metrics = computed(() => [
  {
    key: 'last-success',
    label: '最后成功',
    value: formatTime(lastSuccess.value),
    detail: '最近一次已处理连接器事实',
    tone: 'is-good'
  },
  {
    key: 'failure-rate',
    label: '失败率',
    value: percent(summary.value.failure_rate),
    detail: `${number(summary.value.failed_event_count)} / ${number(summary.value.event_count)} 个事件`,
    tone: Number(summary.value.failed_event_count || 0) ? 'is-warning' : 'is-good'
  },
  {
    key: 'retry',
    label: '重试',
    value: summary.value.retry_count == null ? '待耐久遥测' : number(summary.value.retry_count),
    detail: payload.value?.retry_metric?.reason || '已采集重试次数',
    tone: summary.value.retry_count == null ? 'is-neutral' : 'is-good'
  },
  {
    key: 'backlog',
    label: '积压',
    value: number(summary.value.backlog_count),
    detail: '处理中或等待前置事实的事件',
    tone: Number(summary.value.backlog_count || 0) ? 'is-warning' : 'is-good'
  },
  {
    key: 'signature',
    label: '签名失败',
    value: summary.value.signature_failure_count == null ? '待耐久遥测' : number(summary.value.signature_failure_count),
    detail: payload.value?.signature_failure_metric?.reason || '已采集签名失败次数',
    tone: summary.value.signature_failure_count == null ? 'is-neutral' : 'is-good'
  },
  {
    key: 'reconciliation',
    label: '对账异常',
    value: number(summary.value.reconciliation_anomaly_count),
    detail: 'exception / reopened 对账记录',
    tone: Number(summary.value.reconciliation_anomaly_count || 0) ? 'is-warning' : 'is-good'
  }
])

async function load() {
  if (!canView.value) return
  const currentSequence = ++requestSequence
  loading.value = true
  error.value = ''
  try {
    const result = await fetchFinancialConnectorObservability({ windowHours: props.windowHours })
    if (currentSequence === requestSequence) payload.value = result
  } catch (loadError) {
    if (currentSequence === requestSequence) {
      error.value = String(loadError?.message || '财务连接器指标加载失败。')
    }
  } finally {
    if (currentSequence === requestSequence) loading.value = false
  }
}

function number(value) {
  return new Intl.NumberFormat('zh-CN').format(Number(value || 0))
}

function percent(value) {
  return `${(Number(value || 0) * 100).toFixed(1)}%`
}

function formatTime(value) {
  const timestamp = Date.parse(String(value || ''))
  if (!Number.isFinite(timestamp)) return '暂无成功'
  return new Intl.DateTimeFormat('zh-CN', {
    month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false
  }).format(new Date(timestamp))
}

function statusLabel(value) {
  return ({ active: '运行中', disabled: '已停用', rotating: '轮换中' })[value] || '未知'
}

function evidenceTone(item) {
  if (item?.evidence_classification === 'external_cash') return 'is-external'
  if (item?.evidence_classification === 'staging_connector') return 'is-staging'
  return 'is-simulated'
}

onMounted(load)
watch(() => [canView.value, props.windowHours], load)
</script>

<style scoped>
.connector-health { margin: 18px 0; padding: 20px; display: grid; gap: 18px; }
.connector-health__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.connector-health__head h3 { margin: 3px 0 5px; font-size: 20px; }
.connector-health__head p, .connector-health footer { margin: 0; color: var(--text-secondary); font-size: 13px; }
.connector-health__eyebrow { color: var(--theme-primary); font-size: 11px; font-weight: 800; letter-spacing: .12em; }
.connector-health__head button { border: 1px solid var(--border-color); border-radius: 10px; padding: 8px 12px; background: var(--surface); color: var(--text-primary); cursor: pointer; }
.connector-health__head button:disabled { cursor: wait; opacity: .6; }
.connector-health__metrics { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 10px; }
.connector-health__metrics article { padding: 13px; border: 1px solid var(--border-color); border-radius: 12px; background: color-mix(in srgb, var(--surface) 88%, transparent); }
.connector-health__metrics article.is-warning { border-color: color-mix(in srgb, #f59e0b 45%, var(--border-color)); }
.connector-health__metrics article.is-neutral { border-style: dashed; }
.connector-health__metrics span, .connector-health__metrics small { display: block; color: var(--text-secondary); }
.connector-health__metrics strong { display: block; margin: 6px 0 4px; font-size: 18px; }
.connector-health__metrics small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; }
.connector-health__evidence { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 12px; }
.connector-health__evidence span, .evidence-chip, .status-chip { border-radius: 999px; padding: 5px 9px; font-weight: 700; }
.is-external { color: #047857; background: #ecfdf5; }
.is-manual { color: #b45309; background: #fffbeb; }
.is-simulated, .evidence-chip.is-simulated { color: #475569; background: #f1f5f9; }
.evidence-chip.is-staging { color: #6d28d9; background: #f5f3ff; }
.status-chip.active { color: #047857; background: #ecfdf5; }
.status-chip.disabled { color: #64748b; background: #f1f5f9; }
.status-chip.rotating { color: #a16207; background: #fefce8; }
.connector-health__table-wrap { overflow-x: auto; }
.connector-health table { width: 100%; border-collapse: collapse; font-size: 13px; }
.connector-health th, .connector-health td { padding: 11px 9px; border-bottom: 1px solid var(--border-color); text-align: left; white-space: nowrap; }
.connector-health td small { display: block; margin-top: 3px; color: var(--text-secondary); }
.connector-health__state { display: flex; align-items: center; justify-content: center; gap: 8px; min-height: 84px; color: var(--text-secondary); }
.connector-health__state.is-error { color: #b91c1c; }
@media (max-width: 1180px) { .connector-health__metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 720px) {
  .connector-health { padding: 16px; }
  .connector-health__head { align-items: stretch; flex-direction: column; }
  .connector-health__metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
</style>
