import { Button, Card, Space, Table, Tag, Toast, Typography } from '@douyinfe/semi-ui';
import { IconCopy } from '@douyinfe/semi-icons';
import { useEffect, useState } from 'react';
import { api } from '../api/client';
import type { CapacityMetrics, LinkHealth, OpsHealth, SourceReadinessPlan, SourceReadinessRow } from '../api/types';
import { PageHeader } from '../components/PageHeader';

const statusColor: Record<string, 'green' | 'orange' | 'red' | 'grey'> = {
  ok: 'green',
  warning: 'orange',
  error: 'red'
};

function formatNumber(value?: number | null) {
  return value == null ? '-' : value.toLocaleString();
}

function formatRate(value?: number | null) {
  return value == null || !Number.isFinite(Number(value)) ? '0%' : `${Number(value).toLocaleString(undefined, { maximumFractionDigits: 1 })}%`;
}

function normalizeSourceReadiness(plan: SourceReadinessPlan | null): SourceReadinessPlan | null {
  return plan && Array.isArray(plan.sources) ? plan : null;
}

function statusText(ok?: boolean) {
  return ok ? '正常' : '异常';
}

function boolColor(ok?: boolean): 'green' | 'red' {
  return ok ? 'green' : 'red';
}

function amapSecurityText(health: OpsHealth | null) {
  const runtime = health?.runtime;
  if (!runtime?.amapWebJsConfigured) return '地图未配置';
  if (runtime.amapSecurityCodeExposed) return '安全码已暴露';
  if (runtime.amapSecurityProxyEnabled) return '安全码未暴露';
  return '安全代理未启用';
}

function amapSecurityColor(health: OpsHealth | null): 'green' | 'orange' | 'red' {
  const runtime = health?.runtime;
  if (runtime?.amapSecurityCodeExposed) return 'red';
  if (runtime?.amapWebJsConfigured && runtime?.amapSecurityProxyEnabled) return 'green';
  return 'orange';
}

type CapacityRisk = {
  key: string;
  title: string;
  value: string;
  category: string;
  severity: 'warning' | 'error';
  finding: string;
  action: string;
};

type CapacityMetricRow = {
  key: keyof CapacityMetrics;
  name: string;
  value: number;
  threshold: string;
  status: 'ok' | 'warning' | 'error';
  detail: string;
};

type OpsActionItem = {
  key: string;
  title: string;
  level: 'P0' | 'P1' | 'P2';
  owner: string;
  evidence: string;
  action: string;
  acceptance: string;
};

const customerEvidencePaths = [
  {
    title: '车辆不在线',
    action: '看实时写入',
    evidence: 'Redis 在线 Key、快速写入 pending、NATS ACK',
    detail: '解释客户看到车辆离线时，是否是实时帧、在线 TTL 或快速写入链路中断。'
  },
  {
    title: '轨迹缺数据',
    action: '看历史消费',
    evidence: 'Kafka Lag、历史批次、历史行待写',
    detail: '解释轨迹回放或历史明细缺失时，数据是否卡在 Kafka、TDengine 或批量写入。'
  },
  {
    title: '无法导出',
    action: '看存储/API',
    evidence: 'TDengine、MySQL、请求超时、运行版本',
    detail: '解释客户导出失败、查询慢或字段缺失时，核心存储和 API 是否可用。'
  },
  {
    title: '地图不可用',
    action: '看高德配置',
    evidence: 'Web JS Key、服务端 API Key、安全代理',
    detail: '解释实时地图、轨迹回放和地理能力不可用时，地图配置是否完整且安全。'
  }
];

const emptyCapacityMetrics: CapacityMetrics = {
  activeConnections: 0,
  kafkaLag: 0,
  bridgeConsumerPending: 0,
  bridgeAckPending: 0,
  bridgeBatchPendingMessages: 0,
  fastWriterConsumerPending: 0,
  fastWriterAckPending: 0,
  fastWriterBatchPending: 0,
  historyBatchPending: 0,
  historyRowsPending: 0
};

function getCapacityMetrics(health: OpsHealth | null): CapacityMetrics {
  return {
    ...emptyCapacityMetrics,
    ...(health?.capacityMetrics ?? {}),
    activeConnections: health?.capacityMetrics?.activeConnections ?? health?.activeConnections ?? 0,
    kafkaLag: health?.capacityMetrics?.kafkaLag ?? health?.kafkaLag ?? 0
  };
}

function metricStatus(value: number, warningAt: number, errorAt?: number): 'ok' | 'warning' | 'error' {
  if (errorAt != null && value > errorAt) return 'error';
  return value > warningAt ? 'warning' : 'ok';
}

function capacityMetricRows(health: OpsHealth | null): CapacityMetricRow[] {
  const metrics = getCapacityMetrics(health);
  return [
    {
      key: 'bridgeConsumerPending',
      name: 'NATS 桥接待消费',
      value: metrics.bridgeConsumerPending,
      threshold: '> 10,000 预警',
      status: metricStatus(metrics.bridgeConsumerPending, 10000),
      detail: 'RAW/FIELDS 从 NATS 进入 Kafka 前的待消费量。'
    },
    {
      key: 'bridgeAckPending',
      name: 'NATS 桥接 ACK',
      value: metrics.bridgeAckPending,
      threshold: '> 100 严重',
      status: metricStatus(metrics.bridgeAckPending, 0, 100),
      detail: '桥接消费者已拉取但未确认的消息，持续不为 0 需要优先排查。'
    },
    {
      key: 'fastWriterConsumerPending',
      name: '快速写入待消费',
      value: metrics.fastWriterConsumerPending,
      threshold: '> 10,000 预警',
      status: metricStatus(metrics.fastWriterConsumerPending, 10000),
      detail: '实时数据写 TDengine、Redis 前的 NATS 待消费量。'
    },
    {
      key: 'fastWriterAckPending',
      name: '快速写入 ACK',
      value: metrics.fastWriterAckPending,
      threshold: '> 10 严重',
      status: metricStatus(metrics.fastWriterAckPending, 0, 10),
      detail: '快速写入消费者已拉取但未确认的消息。'
    },
    {
      key: 'historyBatchPending',
      name: '历史批次待写',
      value: metrics.historyBatchPending,
      threshold: '> 0 预警',
      status: metricStatus(metrics.historyBatchPending, 0),
      detail: '历史落库批处理内存队列，正常应快速归零。'
    },
    {
      key: 'historyRowsPending',
      name: '历史行待写',
      value: metrics.historyRowsPending,
      threshold: '> 0 预警',
      status: metricStatus(metrics.historyRowsPending, 0),
      detail: '历史落库批处理待写行数，用于判断 TDengine/MySQL 写入是否滞后。'
    },
    {
      key: 'kafkaLag',
      name: 'Kafka 消费 Lag',
      value: metrics.kafkaLag,
      threshold: '> 0 预警',
      status: metricStatus(metrics.kafkaLag, 0),
      detail: '后续统计和历史消费链路的 Kafka 积压。'
    },
    {
      key: 'activeConnections',
      name: '网关活跃连接',
      value: metrics.activeConnections,
      threshold: '> 100,000 预警',
      status: metricStatus(metrics.activeConnections, 100000),
      detail: '32960、808、MQTT 当前活跃连接总量。'
    }
  ];
}

function firstNumber(value: string) {
  const match = value.match(/[\d,.]+/);
  return match ? match[0] : '-';
}

function capacityRisk(finding: string): CapacityRisk {
  const lower = finding.toLowerCase();
  if (lower.includes('bridge') && lower.includes('ack pending')) {
    return {
      key: finding,
      title: 'NATS 桥接 ACK 未确认',
      value: firstNumber(finding),
      category: 'NATS -> Kafka',
      severity: 'error',
      finding,
      action: '优先检查 Kafka 是否监听 9092、磁盘是否打满，以及桥接服务是否持续写入成功。'
    };
  }
  if (lower.includes('bridge') && lower.includes('consumer pending')) {
    return {
      key: finding,
      title: 'NATS 桥接待消费',
      value: firstNumber(finding),
      category: 'NATS -> Kafka',
      severity: 'warning',
      finding,
      action: '确认 Kafka 已恢复后观察 pending 是否持续下降；若不下降，扩容桥接或降低 Kafka topic 保留压力。'
    };
  }
  if (lower.includes('kafka') || lower.includes('lag')) {
    return {
      key: finding,
      title: 'Kafka 消费积压',
      value: firstNumber(finding),
      category: 'Kafka',
      severity: 'warning',
      finding,
      action: '检查消费者组 lag、topic retention、broker 磁盘和生产写入错误。'
    };
  }
  if (lower.includes('redis')) {
    return {
      key: finding,
      title: 'Redis 在线投影异常',
      value: firstNumber(finding),
      category: 'Redis',
      severity: 'warning',
      finding,
      action: '核对 realtime KV、online TTL 和协议实时帧过滤，确认车辆在线统计来源。'
    };
  }
  if (lower.includes('connection')) {
    return {
      key: finding,
      title: '网关连接压力',
      value: firstNumber(finding),
      category: 'Gateway',
      severity: 'warning',
      finding,
      action: '查看 32960、808、MQTT 连接数、系统 FD、CPU 和接入端口队列。'
    };
  }
  return {
    key: finding,
    title: '容量风险',
    value: firstNumber(finding),
    category: 'General',
    severity: 'warning',
    finding,
    action: '进入对应链路日志和指标继续定位，确认是否影响车辆实时服务。'
  };
}

function opsCapacityHandoffText(health: OpsHealth | null) {
  const metrics = getCapacityMetrics(health);
  const risks = (health?.capacityFindings ?? []).map(capacityRisk);
  const unhealthyLinks = (health?.linkHealth ?? []).filter((item) => item.status !== 'ok');
  const runtime = health?.runtime;
  return [
    '【车辆数据中台运维容量交接】',
    `运行版本：${runtime?.platformRelease || '-'}`,
    `Kafka Lag：${formatNumber(metrics.kafkaLag)}`,
    `活跃连接：${formatNumber(metrics.activeConnections)}`,
    `Redis 在线 Key：${formatNumber(health?.redisOnlineKeys)}`,
    `NATS桥接：consumer pending ${formatNumber(metrics.bridgeConsumerPending)} / ack pending ${formatNumber(metrics.bridgeAckPending)} / batch pending ${formatNumber(metrics.bridgeBatchPendingMessages)}`,
    `快速写入：consumer pending ${formatNumber(metrics.fastWriterConsumerPending)} / ack pending ${formatNumber(metrics.fastWriterAckPending)} / batch pending ${formatNumber(metrics.fastWriterBatchPending)}`,
    `历史写入：batch pending ${formatNumber(metrics.historyBatchPending)} / rows pending ${formatNumber(metrics.historyRowsPending)}`,
    `存储状态：TDengine ${statusText(health?.tdengineWritable)} / MySQL ${statusText(health?.mysqlWritable)}`,
    `地图配置：Web JS ${runtime?.amapWebJsConfigured ? '已配置' : '未配置'} / API ${runtime?.amapApiConfigured ? '已配置' : '未配置'} / ${amapSecurityText(health)}`,
    `异常链路：${unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}(${item.detail || '-'})`).join('；') : '正常'}`,
    '',
    '容量发现：',
    ...(risks.length > 0 ? risks.map((risk, index) => `${index + 1}. [${risk.category}] ${risk.finding} -> ${risk.action}`) : ['暂无容量风险']),
    '',
    '处置顺序：',
    '1. 先看 Kafka Lag 与 NATS ACK pending，确认消息是否卡在桥接或下游消费',
    '2. 再看快速写入 pending，确认 Redis/TDengine 实时链路是否影响在线和实时数据',
    '3. 再看历史写入 pending，确认轨迹、RAW、统计是否滞后',
    '4. 最后核对 MySQL、TDengine、Redis 和地图配置，确认中台查询体验是否受影响'
  ].join('\n');
}

function opsActionItems(health: OpsHealth | null): OpsActionItem[] {
  const metrics = getCapacityMetrics(health);
  const actions: OpsActionItem[] = [];

  if (metrics.bridgeAckPending > 0 || metrics.bridgeConsumerPending > 10000) {
    actions.push({
      key: 'nats-kafka-bridge',
      title: '稳定 NATS 到 Kafka 桥接',
      level: metrics.bridgeAckPending > 100 ? 'P0' : 'P1',
      owner: '接入网关 / 消息队列',
      evidence: `ACK ${formatNumber(metrics.bridgeAckPending)}，待消费 ${formatNumber(metrics.bridgeConsumerPending)}`,
      action: '检查 Kafka broker、topic 写入、NATS bridge 日志和服务器磁盘；必要时临时扩容桥接消费者。',
      acceptance: 'ACK pending 回到 0，consumer pending 连续下降，Kafka Lag 不再增长。'
    });
  }

  if (metrics.fastWriterAckPending > 0 || metrics.fastWriterConsumerPending > 10000) {
    actions.push({
      key: 'fast-writer',
      title: '保护实时写入通道',
      level: metrics.fastWriterAckPending > 10 ? 'P0' : 'P1',
      owner: '实时投影 / 存储',
      evidence: `ACK ${formatNumber(metrics.fastWriterAckPending)}，待消费 ${formatNumber(metrics.fastWriterConsumerPending)}`,
      action: '优先确认 Redis 在线投影和 TDengine 快写是否正常，再看字段扁平化写入耗时。',
      acceptance: '实时 KV 延迟恢复到秒级内，在线 TTL 正常续期，快速写入 pending 清零。'
    });
  }

  if (metrics.historyBatchPending > 0 || metrics.historyRowsPending > 0 || metrics.kafkaLag > 0) {
    actions.push({
      key: 'history-consumer',
      title: '疏通历史与统计消费',
      level: metrics.kafkaLag > 1000 || metrics.historyRowsPending > 10000 ? 'P0' : 'P1',
      owner: '历史消费 / 统计',
      evidence: `Kafka Lag ${formatNumber(metrics.kafkaLag)}，历史待写 ${formatNumber(metrics.historyRowsPending)} 行`,
      action: '查看 Kafka 消费者组、TDengine 批量写入错误和统计任务耗时；恢复后补看轨迹、RAW、里程统计。',
      acceptance: 'Lag 持续归零，历史待写行归零，按 VIN 的历史查询恢复毫秒到百毫秒级。'
    });
  }

  if (metrics.activeConnections > 100000) {
    actions.push({
      key: 'gateway-capacity',
      title: '扩容接入网关容量',
      level: 'P1',
      owner: '网关 / ECS',
      evidence: `活跃连接 ${formatNumber(metrics.activeConnections)}`,
      action: '检查 FD、CPU、网卡、端口 accept backlog 和协议源分布；准备按协议拆分实例。',
      acceptance: '连接数增长时 CPU 和内存稳定，登录、注册、位置、实时帧响应无超时。'
    });
  }

  if (health && (!health.tdengineWritable || !health.mysqlWritable)) {
    actions.push({
      key: 'storage-writable',
      title: '恢复核心存储可写',
      level: 'P0',
      owner: '数据库 / 应用',
      evidence: `TDengine ${statusText(health.tdengineWritable)}，MySQL ${statusText(health.mysqlWritable)}`,
      action: '先确认网络和账号，再看连接池、慢写、磁盘和表结构变更；避免实时链路被关系库写入拖慢。',
      acceptance: '健康检查全部恢复正常，RAW、location、snapshot 和身份表写入成功。'
    });
  }

  if (health && (health.activeConnections ?? 0) > 0 && (health.redisOnlineKeys ?? 0) === 0) {
    actions.push({
      key: 'redis-online',
      title: '校验 Redis 在线投影',
      level: 'P1',
      owner: '实时投影',
      evidence: `活跃连接 ${formatNumber(health.activeConnections)}，在线 Key ${formatNumber(health.redisOnlineKeys)}`,
      action: '确认只有实时数据帧进入 Redis，检查 online TTL 写入、VIN 解析和协议过滤条件。',
      acceptance: '按协议、VIN 的在线状态可查，离线时间统计有连续依据。'
    });
  }

  if (health?.runtime?.amapWebJsConfigured && !health.runtime.amapSecurityProxyEnabled) {
    actions.push({
      key: 'amap-security',
      title: '收敛地图密钥暴露风险',
      level: health.runtime.amapSecurityCodeExposed ? 'P1' : 'P2',
      owner: '前端 / 平台 API',
      evidence: amapSecurityText(health),
      action: '启用高德安全代理，前端只保留 Web JS Key，服务端负责逆地理和安全密钥。',
      acceptance: '轨迹回放和实时地图可用，浏览器端不暴露安全密钥。'
    });
  }

  if (!actions.length) {
    actions.push({
      key: 'steady-watch',
      title: '维持生产容量巡检',
      level: 'P2',
      owner: '值班 / 平台',
      evidence: '当前未发现积压或核心写入异常',
      action: '继续观察 32960、808、MQTT 三类实时数据源、在线状态、地图服务和历史查询耗时。',
      acceptance: '10W 车辆接入准备项持续无红色风险，实时投影和历史消费无积压。'
    });
  }

  return actions;
}

function opsCapacityActionPlanText(health: OpsHealth | null) {
  const actions = opsActionItems(health);
  return [
    '【车辆数据中台容量处置计划】',
    `生成版本：${health?.runtime?.platformRelease || '-'}`,
    ...actions.map((item, index) => [
      `${index + 1}. ${item.level} ${item.title}`,
      `负责人：${item.owner}`,
      `证据：${item.evidence}`,
      `动作：${item.action}`,
      `验收：${item.acceptance}`
    ].join('\n'))
  ].join('\n\n');
}

function sourceReadinessReport(plan: SourceReadinessPlan | null) {
  if (!plan) return '【数据源生产就绪度】\n暂无数据源就绪度数据';
  return [
    '【数据源生产就绪度】',
    `运行版本：${plan.platformRelease || '-'}`,
    `车辆规模：${formatNumber(plan.totalVehicles)} / 在线 ${formatNumber(plan.onlineVehicles)}`,
    `接入状态：连接 ${formatNumber(plan.activeConnections)} / Redis在线Key ${formatNumber(plan.redisOnlineKeys)} / Kafka Lag ${formatNumber(plan.kafkaLag)}`,
    '',
    ...plan.sources.map((source, index) => [
      `${index + 1}. [${source.severity}] ${source.protocol} - ${source.status}`,
      `   角色：${source.role}`,
      `   证据：${source.evidence}`,
      `   在线率：${formatRate(source.onlineRate)}，缺失车辆：${formatNumber(source.missingVehicles)}`,
      `   动作：${source.action}`,
      `   验收：${source.acceptance}`,
      `   实时监控：${window.location.origin}${window.location.pathname}${source.realtimeHash}`,
      `   历史证据：${window.location.origin}${window.location.pathname}${source.historyHash}`
    ].join('\n'))
  ].join('\n');
}

async function copyText(value: string, label: string) {
  try {
    await navigator.clipboard.writeText(value);
    Toast.success(`已复制${label}`);
  } catch {
    Toast.error(`复制${label}失败`);
  }
}

export function OpsQuality() {
  const [health, setHealth] = useState<OpsHealth | null>(null);
  const [sourceReadiness, setSourceReadiness] = useState<SourceReadinessPlan | null>(null);
  const [loading, setLoading] = useState(true);

  const load = () => {
    setLoading(true);
    Promise.all([
      api.opsHealth(),
      api.sourceReadiness().catch((error: Error) => {
        Toast.error(error.message);
        return null;
      })
    ])
      .then(([nextHealth, nextSourceReadiness]) => {
        setHealth(nextHealth);
        setSourceReadiness(normalizeSourceReadiness(nextSourceReadiness));
      })
      .catch((error: Error) => Toast.error(error.message))
      .finally(() => setLoading(false));
  };

  useEffect(() => {
    load();
  }, []);

  const runtime = health?.runtime;
  const capacityRisks = (health?.capacityFindings ?? []).map(capacityRisk);
  const capacityRows = capacityMetricRows(health);
  const bridgeRisks = capacityRisks.filter((item) => item.category === 'NATS -> Kafka');
  const actionItems = opsActionItems(health);

  return (
    <div className="vp-page">
      <PageHeader
        title="运维证据层"
        description="客户主流程外的链路、容量、存储和地图配置证据，用来解释车辆实时、轨迹、统计、导出或告警为何不可用。"
        actions={(
          <Space>
            <Button size="small" icon={<IconCopy />} aria-label="复制容量交接" disabled={!health} onClick={() => copyText(opsCapacityHandoffText(health), '运维容量交接')}>复制容量交接</Button>
            <Button size="small" icon={<IconCopy />} aria-label="复制处置计划" disabled={!health} onClick={() => copyText(opsCapacityActionPlanText(health), '容量处置计划')}>复制处置计划</Button>
            <Button size="small" loading={loading} onClick={load}>刷新</Button>
          </Space>
        )}
      />

      <Card bordered className="vp-ops-evidence-layer-card">
        <div className="vp-ops-evidence-layer-head">
          <div>
            <Typography.Title heading={5} style={{ margin: 0 }}>客户问题解释路径</Typography.Title>
            <Typography.Text type="secondary">运维证据只在客户问题需要解释时打开，默认不参与车辆服务主流程。</Typography.Text>
          </div>
          <Tag color="blue">证据层</Tag>
        </div>
        <div className="vp-ops-evidence-path-grid">
          {customerEvidencePaths.map((item) => (
            <button
              key={item.title}
              type="button"
              className="vp-ops-evidence-path-item"
              aria-label={`客户问题解释路径 ${item.title} ${item.action}`}
              onClick={() => Toast.info(item.evidence)}
            >
              <span className="vp-ops-evidence-path-title">{item.title}</span>
              <span className="vp-ops-evidence-path-action">{item.action}</span>
              <span className="vp-ops-evidence-path-evidence">{item.evidence}</span>
              <span className="vp-ops-evidence-path-detail">{item.detail}</span>
            </button>
          ))}
        </div>
      </Card>

      <div className="vp-kpi-grid">
        <Card bordered loading={loading}>
          <div className="vp-kpi-value">{formatNumber(health?.kafkaLag)}</div>
          <div className="vp-kpi-label">Kafka Lag</div>
        </Card>
        <Card bordered loading={loading}>
          <div className="vp-kpi-value">{formatNumber(health?.activeConnections)}</div>
          <div className="vp-kpi-label">活跃连接</div>
        </Card>
        <Card bordered loading={loading}>
          <div className="vp-kpi-value">{formatNumber(health?.redisOnlineKeys)}</div>
          <div className="vp-kpi-label">Redis 在线 Key</div>
        </Card>
        <Card bordered loading={loading}>
          <div className="vp-kpi-value">{runtime?.platformRelease || '-'}</div>
          <div className="vp-kpi-label">运行版本</div>
        </Card>
      </div>

      <Card bordered title="存储与运行时" loading={loading} style={{ marginTop: 16 }}>
        <div className="vp-alert-flow">
          <div className="vp-alert-flow-item">
            <Tag color={boolColor(health?.tdengineWritable)}>TDengine 写入</Tag>
            <div className="vp-alert-flow-value">{statusText(health?.tdengineWritable)}</div>
            <div>时序历史、RAW 和统计依赖 TDengine 可写状态。</div>
          </div>
          <div className="vp-alert-flow-item">
            <Tag color={boolColor(health?.mysqlWritable)}>MySQL 写入</Tag>
            <div className="vp-alert-flow-value">{statusText(health?.mysqlWritable)}</div>
            <div>车辆身份、绑定、服务配置和中台查询依赖 MySQL。</div>
          </div>
          <div className="vp-alert-flow-item">
            <Tag color="blue">请求超时</Tag>
            <div className="vp-alert-flow-value">{runtime?.requestTimeoutMs ? `${runtime.requestTimeoutMs.toLocaleString()} ms` : '-'}</div>
            <div>接口层读取 Redis、TDengine、MySQL 的统一超时保护。</div>
          </div>
        </div>
      </Card>

      <Card bordered title="地图安全配置" loading={loading} style={{ marginTop: 16 }}>
        <Space wrap>
          <Tag color={runtime?.amapWebJsConfigured ? 'green' : 'orange'}>{runtime?.amapWebJsConfigured ? 'Web JS Key 已配置' : 'Web JS Key 未配置'}</Tag>
          <Tag color={runtime?.amapApiConfigured ? 'green' : 'orange'}>{runtime?.amapApiConfigured ? '服务端 API Key 已配置' : '服务端 API Key 未配置'}</Tag>
          <Tag color={amapSecurityColor(health)}>{amapSecurityText(health)}</Tag>
          <Tag color={runtime?.amapSecurityProxyEnabled ? 'green' : 'grey'}>{runtime?.amapSecurityServiceHost || '代理未启用'}</Tag>
        </Space>
      </Card>

      <Card
        bordered
        title={<Space><span>数据源生产就绪度</span><Button size="small" icon={<IconCopy />} disabled={!sourceReadiness} onClick={() => copyText(sourceReadinessReport(sourceReadiness), '数据源生产就绪度')}>复制就绪度报告</Button></Space>}
        loading={loading}
        style={{ marginTop: 16 }}
      >
        <Table<SourceReadinessRow>
          pagination={false}
          dataSource={sourceReadiness?.sources ?? []}
          rowKey={(row?: SourceReadinessRow) => row?.protocol ?? ''}
          columns={[
            {
              title: '来源',
              width: 150,
              render: (_: unknown, row: SourceReadinessRow) => (
                <Space vertical align="start" spacing={4}>
                  <Tag color={row.severity === 'error' ? 'red' : row.severity === 'warning' ? 'orange' : 'green'}>{row.protocol}</Tag>
                  <Typography.Text type="secondary">{row.role}</Typography.Text>
                </Space>
              )
            },
            {
              title: '就绪状态',
              width: 150,
              render: (_: unknown, row: SourceReadinessRow) => <Tag color={row.severity === 'error' ? 'red' : row.severity === 'warning' ? 'orange' : 'green'}>{row.status}</Tag>
            },
            { title: '在线率', width: 100, render: (_: unknown, row: SourceReadinessRow) => formatRate(row.onlineRate) },
            { title: '在线/总数', width: 120, render: (_: unknown, row: SourceReadinessRow) => `${formatNumber(row.online)} / ${formatNumber(row.total)}` },
            { title: '缺失车辆', width: 110, render: (_: unknown, row: SourceReadinessRow) => formatNumber(row.missingVehicles) },
            { title: '证据', dataIndex: 'evidence' },
            { title: '建议动作', dataIndex: 'action' },
            {
              title: '入口',
              width: 220,
              render: (_: unknown, row: SourceReadinessRow) => (
                <Space wrap>
                  <Button size="small" onClick={() => { window.location.hash = row.realtimeHash; }}>实时</Button>
                  <Button size="small" onClick={() => { window.location.hash = row.vehiclesHash; }}>车辆</Button>
                  <Button size="small" onClick={() => { window.location.hash = row.historyHash; }}>RAW</Button>
                  <Button size="small" onClick={() => { window.location.hash = row.alertHash; }}>告警</Button>
                </Space>
              )
            }
          ]}
        />
      </Card>

      <Card bordered title="消息链路积压" loading={loading} style={{ marginTop: 16 }}>
        <Table<CapacityMetricRow>
          pagination={false}
          dataSource={capacityRows}
          rowKey={(row?: CapacityMetricRow) => row?.key ?? ''}
          columns={[
            { title: '指标', dataIndex: 'name' },
            {
              title: '当前值',
              width: 140,
              render: (_: unknown, row: CapacityMetricRow) => (
                <Typography.Text strong>{formatNumber(row.value)}</Typography.Text>
              )
            },
            {
              title: '状态',
              width: 120,
              render: (_: unknown, row: CapacityMetricRow) => <Tag color={statusColor[row.status]}>{row.status}</Tag>
            },
            { title: '阈值', width: 150, dataIndex: 'threshold' },
            { title: '说明', dataIndex: 'detail' }
          ]}
        />
      </Card>

      <Card bordered title="容量风险处置工作台" loading={loading} style={{ marginTop: 16 }}>
        <div className="vp-ops-action-grid">
          {actionItems.map((item) => (
            <div key={item.key} className="vp-ops-action-item">
              <Space wrap>
                <Tag color={item.level === 'P0' ? 'red' : item.level === 'P1' ? 'orange' : 'green'}>{item.level}</Tag>
                <Tag color="blue">{item.owner}</Tag>
              </Space>
              <Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
              <div className="vp-ops-action-evidence">{item.evidence}</div>
              <Typography.Text type="secondary">{item.action}</Typography.Text>
              <div className="vp-ops-action-acceptance">{item.acceptance}</div>
            </div>
          ))}
        </div>
      </Card>

      {capacityRisks.length ? (
        <Card bordered title="容量与风险发现" style={{ marginTop: 16 }}>
          <div className="vp-risk-grid">
            {capacityRisks.map((item) => (
              <div key={item.key} className="vp-risk-item">
                <Space wrap>
                  <Tag color={item.severity === 'error' ? 'red' : 'orange'}>{item.category}</Tag>
                  <Tag color="grey">{item.finding}</Tag>
                </Space>
                <div className="vp-risk-value">{item.value}</div>
                <Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
                <Typography.Text type="secondary">{item.action}</Typography.Text>
              </div>
            ))}
          </div>
          {bridgeRisks.length ? (
            <div className="vp-risk-runbook">
              <Tag color="blue">桥接处置顺序</Tag>
              <Typography.Text>
                先确认 Kafka broker 监听和磁盘空间，再观察 ACK pending 是否回到 0，最后确认 consumer pending 持续下降。
              </Typography.Text>
            </div>
          ) : null}
        </Card>
      ) : null}

      <Card bordered title="链路健康" loading={loading} style={{ marginTop: 16 }}>
        <Table<LinkHealth>
          pagination={false}
          dataSource={health?.linkHealth ?? []}
          rowKey={(row?: LinkHealth) => `${row?.name ?? ''}-${row?.status ?? ''}`}
          columns={[
            { title: '链路', dataIndex: 'name' },
            { title: '状态', width: 120, render: (_: unknown, row: LinkHealth) => <Tag color={statusColor[row.status] ?? 'grey'}>{row.status}</Tag> },
            { title: '说明', dataIndex: 'detail' }
          ]}
        />
      </Card>
    </div>
  );
}
