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 { OpsHealth, QualityAlertRule, QualityNotificationPlan, QualityNotificationPolicy, QualityPriorityIssue } from '../api/types';
import { PageHeader } from '../components/PageHeader';
import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';
import { qualityIssueLabel } from '../domain/qualityIssue';

function formatEscalationMinutes(minutes?: number) {
  if (!Number.isFinite(minutes) || Number(minutes) <= 0) return '-';
  const value = Number(minutes);
  if (value < 60) return `${value} 分钟升级`;
  const hours = Math.floor(value / 60);
  const rest = value % 60;
  return rest > 0 ? `${hours} 小时 ${rest} 分钟升级` : `${hours} 小时升级`;
}

function formatDurationFromMinutes(minutes?: number | null) {
  if (minutes == null || !Number.isFinite(minutes)) return '-';
  const value = Math.max(0, Math.round(minutes));
  if (value < 60) return `${value.toLocaleString()} 分钟`;
  const hours = Math.floor(value / 60);
  const rest = value % 60;
  return rest > 0 ? `${hours.toLocaleString()} 小时 ${rest} 分钟` : `${hours.toLocaleString()} 小时`;
}

function ruleColor(rule: QualityAlertRule): 'green' | 'orange' | 'red' | 'grey' {
  if ((rule.count ?? 0) <= 0) return 'green';
  if (rule.level === 'P0') return 'red';
  if (rule.level === 'P1') return 'orange';
  return 'grey';
}

function ruleTitle(rule: QualityAlertRule) {
  return rule.title || qualityIssueLabel(rule.issueType);
}

type NotificationExportRow = {
  category: string;
  name: string;
  level: string;
  ownerOrTarget: string;
  triggerOrCondition: string;
  notifyOrChannel: string;
  slaOrEscalation: string;
  acceptance: string;
  count: number | string;
  release: string;
};

type SlaEscalationRow = {
  issue: QualityPriorityIssue;
  policy?: QualityNotificationPolicy;
  elapsedMinutes?: number;
  remainingMinutes?: number;
  status: 'overdue' | 'due_soon' | 'tracking' | 'unknown';
  statusLabel: string;
  statusColor: 'red' | 'orange' | 'green' | 'grey';
};

type NotificationExecutionRow = {
  key: string;
  issue: QualityPriorityIssue;
  rule?: QualityAlertRule;
  policy?: QualityNotificationPolicy;
  slaRow: SlaEscalationRow;
  evidenceText: string;
  evidenceColor: 'green' | 'orange';
};

type NotificationCoverageOwnerRow = {
  owner: string;
  activeRules: number;
  p0Rules: number;
  hitCount: number;
  primaryPolicy?: QualityNotificationPolicy;
};

type NotificationReachabilityRow = {
  owner: string;
  issueCount: number;
  p0IssueCount: number;
  ruleCount: number;
  channel: string;
  target: string;
  escalation: string;
  acceptance: string;
  evidenceComplete: number;
  status: 'ready' | 'risk' | 'blocked';
  statusLabel: string;
  statusColor: 'green' | 'orange' | 'red';
  nextAction: string;
};

const notificationExportColumns: CsvColumn<NotificationExportRow>[] = [
  { title: '类型', value: (row) => row.category },
  { title: '名称', value: (row) => row.name },
  { title: '级别', value: (row) => row.level },
  { title: '责任方', value: (row) => row.ownerOrTarget },
  { title: '触发条件', value: (row) => row.triggerOrCondition },
  { title: '通知方式', value: (row) => row.notifyOrChannel },
  { title: 'SLA/升级', value: (row) => row.slaOrEscalation },
  { title: '验收口径', value: (row) => row.acceptance },
  { title: '当前命中', value: (row) => row.count },
  { title: '运行版本', value: (row) => row.release }
];

function parseIssueTime(value?: string) {
  const normalized = String(value ?? '').trim().replace(' ', 'T');
  if (!normalized) return undefined;
  const ms = Date.parse(normalized);
  return Number.isFinite(ms) ? ms : undefined;
}

function policyForPriority(issue: QualityPriorityIssue, policies: QualityNotificationPolicy[]) {
  const prefix = issue.priority === 'P0' ? 'P0' : 'P1';
  return policies.find((policy) => policy.name.startsWith(prefix)) ?? policies[0];
}

function slaEscalationRows(issues: QualityPriorityIssue[], policies: QualityNotificationPolicy[], nowMs = Date.now()): SlaEscalationRow[] {
  return issues.map<SlaEscalationRow>((issue) => {
    const policy = policyForPriority(issue, policies);
    const lastSeenMs = parseIssueTime(issue.lastSeen);
    if (lastSeenMs == null || !policy?.escalationMinutes) {
      return {
        issue,
        policy,
        status: 'unknown',
        statusLabel: '待确认时间',
        statusColor: 'grey'
      };
    }
    const elapsedMinutes = Math.max(0, (nowMs - lastSeenMs) / 60000);
    const remainingMinutes = policy.escalationMinutes - elapsedMinutes;
    if (remainingMinutes <= 0) {
      return {
        issue,
        policy,
        elapsedMinutes,
        remainingMinutes,
        status: 'overdue',
        statusLabel: `已超时 ${formatDurationFromMinutes(Math.abs(remainingMinutes))}`,
        statusColor: 'red'
      };
    }
    if (remainingMinutes <= 15) {
      return {
        issue,
        policy,
        elapsedMinutes,
        remainingMinutes,
        status: 'due_soon',
        statusLabel: `${formatDurationFromMinutes(remainingMinutes)} 后升级`,
        statusColor: 'orange'
      };
    }
    return {
      issue,
      policy,
      elapsedMinutes,
      remainingMinutes,
      status: 'tracking',
      statusLabel: `${formatDurationFromMinutes(remainingMinutes)} 后升级`,
      statusColor: 'green'
    };
  }).sort((left, right) => {
    const statusWeight: Record<SlaEscalationRow['status'], number> = { overdue: 3, due_soon: 2, tracking: 1, unknown: 0 };
    return statusWeight[right.status] - statusWeight[left.status]
      || (left.remainingMinutes ?? Number.POSITIVE_INFINITY) - (right.remainingMinutes ?? Number.POSITIVE_INFINITY)
      || left.issue.vehicleLabel.localeCompare(right.issue.vehicleLabel);
  });
}

function slaEscalationReport(rows: SlaEscalationRow[], release?: string) {
  const overdue = rows.filter((row) => row.status === 'overdue').length;
  const dueSoon = rows.filter((row) => row.status === 'due_soon').length;
  const tracking = rows.filter((row) => row.status === 'tracking').length;
  const next = rows[0];
  return [
    '【告警SLA升级报告】',
    `运行版本：${release?.trim() || '-'}`,
    `升级态势：已超时 ${overdue.toLocaleString()} / 即将升级 ${dueSoon.toLocaleString()} / 正常跟进 ${tracking.toLocaleString()}`,
    `下一优先：${next ? `[${next.issue.priority}] ${next.issue.vehicleLabel} / ${next.statusLabel}` : '暂无待通知告警'}`,
    '',
    '升级队列：',
    ...(rows.length > 0 ? rows.slice(0, 10).map((row, index) => [
      `${index + 1}. [${row.issue.priority}] ${row.issue.vehicleLabel}`,
      `   状态：${row.statusLabel}`,
      `   策略：${row.policy ? `${row.policy.name} / ${row.policy.target} / ${formatEscalationMinutes(row.policy.escalationMinutes)}` : '-'}`,
      `   已等待：${formatDurationFromMinutes(row.elapsedMinutes)}`,
      `   问题：${qualityIssueLabel(row.issue.issueType)} / ${row.issue.protocol}`,
      `   建议动作：${row.issue.actionLabel}`,
      `   车辆服务：${window.location.origin}${window.location.pathname}${row.issue.vehicleHash}`,
      `   原始记录：${window.location.origin}${window.location.pathname}${row.issue.rawHash}`
    ].join('\n')) : ['暂无待升级告警'])
  ].join('\n');
}

function notificationExecutionRows(
  issues: QualityPriorityIssue[],
  rules: QualityAlertRule[],
  policies: QualityNotificationPolicy[],
  nowMs = Date.now()
): NotificationExecutionRow[] {
  const slaByIssue = new Map(slaEscalationRows(issues, policies, nowMs).map((row) => [
    `${row.issue.priority}-${row.issue.protocol}-${row.issue.issueType}-${row.issue.vehicleLabel}-${row.issue.lastSeen}`,
    row
  ]));
  return issues.map((issue) => {
    const key = `${issue.priority}-${issue.protocol}-${issue.issueType}-${issue.vehicleLabel}-${issue.lastSeen}`;
    const rule = rules.find((item) => item.issueType === issue.issueType);
    const policy = policyForPriority(issue, policies);
    const slaRow = slaByIssue.get(key) ?? {
      issue,
      policy,
      status: 'unknown',
      statusLabel: '待确认时间',
      statusColor: 'grey'
    } as SlaEscalationRow;
    return {
      key,
      issue,
      rule,
      policy,
      slaRow,
      evidenceText: evidenceSummary(issue),
      evidenceColor: evidenceColor(issue)
    };
  }).sort((left, right) => {
    const priorityWeight = (value: string) => value === 'P0' ? 2 : value === 'P1' ? 1 : 0;
    const statusWeight: Record<SlaEscalationRow['status'], number> = { overdue: 3, due_soon: 2, tracking: 1, unknown: 0 };
    return priorityWeight(right.issue.priority) - priorityWeight(left.issue.priority)
      || statusWeight[right.slaRow.status] - statusWeight[left.slaRow.status]
      || left.issue.vehicleLabel.localeCompare(right.issue.vehicleLabel);
  });
}

function notificationExecutionMatrixReport(rows: NotificationExecutionRow[], release?: string) {
  const p0Count = rows.filter((row) => row.issue.priority === 'P0').length;
  const incompleteEvidence = rows.filter((row) => countEvidenceLinks(row.issue) < 4).length;
  const overdueCount = rows.filter((row) => row.slaRow.status === 'overdue').length;
  return [
    '【通知执行矩阵】',
    `运行版本：${release?.trim() || '-'}`,
    `执行态势：P0 ${p0Count.toLocaleString()} / 已超时 ${overdueCount.toLocaleString()} / 证据不完整 ${incompleteEvidence.toLocaleString()}`,
    '',
    ...(rows.length > 0 ? rows.slice(0, 12).map((row, index) => [
      `${index + 1}. [${row.issue.priority}] ${row.issue.vehicleLabel}`,
      `   问题：${qualityIssueLabel(row.issue.issueType)} / ${row.issue.protocol}`,
      `   规则：${row.rule ? `${ruleTitle(row.rule)} / ${row.rule.owner}` : '-'}`,
      `   通知：${row.policy ? `${row.policy.target} / ${row.policy.channel}` : '-'}`,
      `   升级：${row.policy ? formatEscalationMinutes(row.policy.escalationMinutes) : '-'}`,
      `   状态：${row.slaRow.statusLabel}`,
      `   证据：${row.evidenceText}`,
      `   动作：${row.issue.actionLabel} - ${row.issue.actionDetail || '-'}`,
      `   车辆服务：${window.location.origin}${window.location.pathname}${row.issue.vehicleHash}`,
      `   原始记录：${window.location.origin}${window.location.pathname}${row.issue.rawHash}`
    ].join('\n')) : ['暂无待执行通知'])
  ].join('\n');
}

function policyLevel(policy: QualityNotificationPolicy) {
  if (policy.name.startsWith('P0')) return 'P0';
  if (policy.name.startsWith('P1')) return 'P1';
  return '-';
}

function coverageOwnerRows(rules: QualityAlertRule[], policies: QualityNotificationPolicy[]): NotificationCoverageOwnerRow[] {
  const rows = new Map<string, QualityAlertRule[]>();
  for (const rule of rules) {
    if (Number(rule.count ?? 0) <= 0) continue;
    const owner = rule.owner?.trim() || '未分配责任方';
    rows.set(owner, [...(rows.get(owner) ?? []), rule]);
  }
  return Array.from(rows.entries()).map(([owner, ownerRules]) => {
    const p0Rules = ownerRules.filter((rule) => rule.level === 'P0').length;
    const primaryPolicy = policies.find((policy) => policyLevel(policy) === (p0Rules > 0 ? 'P0' : 'P1')) ?? policies[0];
    return {
      owner,
      activeRules: ownerRules.length,
      p0Rules,
      hitCount: ownerRules.reduce((total, rule) => total + Number(rule.count ?? 0), 0),
      primaryPolicy
    };
  }).sort((left, right) => right.hitCount - left.hitCount || right.p0Rules - left.p0Rules || left.owner.localeCompare(right.owner));
}

function notificationReachabilityRows(
  issues: QualityPriorityIssue[],
  rules: QualityAlertRule[],
  policies: QualityNotificationPolicy[],
  slaRows: SlaEscalationRow[]
): NotificationReachabilityRow[] {
  const slaByIssue = new Map(slaRows.map((row) => [
    `${row.issue.priority}-${row.issue.protocol}-${row.issue.issueType}-${row.issue.vehicleLabel}-${row.issue.lastSeen}`,
    row
  ]));
  const ownerMap = new Map<string, { issues: QualityPriorityIssue[]; rules: QualityAlertRule[]; policy?: QualityNotificationPolicy; overdue: number; dueSoon: number }>();

  for (const rule of rules) {
    if (Number(rule.count ?? 0) <= 0) continue;
    const owner = rule.owner?.trim() || '未分配责任方';
    const current = ownerMap.get(owner) ?? { issues: [], rules: [], overdue: 0, dueSoon: 0 };
    current.rules.push(rule);
    ownerMap.set(owner, current);
  }

  for (const issue of issues) {
    const rule = rules.find((item) => item.issueType === issue.issueType);
    const owner = rule?.owner?.trim() || policyForPriority(issue, policies)?.target || '未分配责任方';
    const current = ownerMap.get(owner) ?? { issues: [], rules: [], overdue: 0, dueSoon: 0 };
    current.issues.push(issue);
    const sla = slaByIssue.get(`${issue.priority}-${issue.protocol}-${issue.issueType}-${issue.vehicleLabel}-${issue.lastSeen}`);
    if (sla?.status === 'overdue') current.overdue += 1;
    if (sla?.status === 'due_soon') current.dueSoon += 1;
    if (!current.policy) current.policy = policyForPriority(issue, policies);
    ownerMap.set(owner, current);
  }

  return Array.from(ownerMap.entries()).map(([owner, value]) => {
    const p0IssueCount = value.issues.filter((issue) => issue.priority === 'P0').length;
    const evidenceComplete = value.issues.filter((issue) => countEvidenceLinks(issue) >= 4).length;
    const p0Rules = value.rules.filter((rule) => rule.level === 'P0').length;
    const policy = value.policy
      ?? policies.find((item) => policyLevel(item) === (p0IssueCount > 0 || p0Rules > 0 ? 'P0' : 'P1'))
      ?? policies[0];
    const hasPolicy = Boolean(policy?.target && policy.channel);
    const hasAcceptance = Boolean(policy?.acceptanceCriteria);
    const evidenceMissing = value.issues.length > 0 && evidenceComplete < value.issues.length;
    const blocked = !hasPolicy || value.overdue > 0;
    const risk = !blocked && (value.dueSoon > 0 || evidenceMissing || !hasAcceptance || p0IssueCount > 0);
    const status: NotificationReachabilityRow['status'] = blocked ? 'blocked' : risk ? 'risk' : 'ready';
    const statusColor: NotificationReachabilityRow['statusColor'] = blocked ? 'red' : risk ? 'orange' : 'green';
    return {
      owner,
      issueCount: value.issues.length,
      p0IssueCount,
      ruleCount: value.rules.length,
      channel: policy?.channel || '-',
      target: policy?.target || '-',
      escalation: policy ? formatEscalationMinutes(policy.escalationMinutes) : '-',
      acceptance: policy?.acceptanceCriteria || '-',
      evidenceComplete,
      status,
      statusLabel: blocked ? (value.overdue > 0 ? '已超时' : '策略缺失') : risk ? '需跟进' : '可触达',
      statusColor,
      nextAction: blocked
        ? (value.overdue > 0 ? '立即升级责任人并同步业务侧' : '补齐通知对象与渠道')
        : evidenceMissing
          ? '补齐车辆服务、实时、轨迹、原始记录证据'
          : value.dueSoon > 0
            ? '临近升级，先确认恢复进度'
            : p0IssueCount > 0
              ? '先发送P0通知并记录回执'
              : '按策略持续观察并复核验收'
    };
  }).sort((left, right) => {
    const statusWeight: Record<NotificationReachabilityRow['status'], number> = { blocked: 3, risk: 2, ready: 1 };
    return statusWeight[right.status] - statusWeight[left.status]
      || right.p0IssueCount - left.p0IssueCount
      || right.issueCount - left.issueCount
      || left.owner.localeCompare(right.owner);
  });
}

function notificationReachabilityReport(rows: NotificationReachabilityRow[], release?: string) {
  const blocked = rows.filter((row) => row.status === 'blocked').length;
  const risk = rows.filter((row) => row.status === 'risk').length;
  const ready = rows.filter((row) => row.status === 'ready').length;
  return [
    '【通知可达性检查】',
    `运行版本：${release?.trim() || '-'}`,
    `可达结论：阻断 ${blocked.toLocaleString()} / 需跟进 ${risk.toLocaleString()} / 可触达 ${ready.toLocaleString()}`,
    '',
    ...(rows.length > 0 ? rows.slice(0, 10).map((row, index) => [
      `${index + 1}. ${row.owner} / ${row.statusLabel}`,
      `   待通知：${row.issueCount.toLocaleString()} 条，P0 ${row.p0IssueCount.toLocaleString()} 条，活跃规则 ${row.ruleCount.toLocaleString()} 类`,
      `   通知对象：${row.target}`,
      `   渠道：${row.channel}`,
      `   升级：${row.escalation}`,
      `   证据：${row.evidenceComplete.toLocaleString()}/${row.issueCount.toLocaleString()} 完整`,
      `   验收：${row.acceptance}`,
      `   下一步：${row.nextAction}`
    ].join('\n')) : ['暂无待通知责任方']),
    '',
    '检查口径：',
    '1. 必须有责任方、通知对象和渠道',
    '2. P0 和超时告警优先进入升级队列',
    '3. 通知前至少带上车辆服务、实时、轨迹和原始记录证据',
    '4. 恢复后按验收口径复核，确认告警不再命中'
  ].join('\n');
}

function notificationCoverageReport({
  plan,
  release,
  ownerRows,
  overdueCount,
  dueSoonCount,
  health
}: {
  plan: QualityNotificationPlan | null;
  release?: string;
  ownerRows: NotificationCoverageOwnerRow[];
  overdueCount: number;
  dueSoonCount: number;
  health: OpsHealth | null;
}) {
  const rules = plan?.rules ?? [];
  const policies = plan?.policies ?? [];
  const issues = plan?.priorityIssues ?? [];
  const activeRules = rules.filter((rule) => Number(rule.count ?? 0) > 0);
  const p0Issues = issues.filter((issue) => issue.priority === 'P0').length;
  const missingEvidence = issues.filter((issue) => countEvidenceLinks(issue) < 4).length;
  const unhealthyLinks = (health?.linkHealth ?? []).filter((item) => item.status !== 'ok');
  const policyChannels = Array.from(new Set(policies.map((policy) => policy.channel).filter(Boolean)));
  return [
    '【通知覆盖与升级风险】',
    `运行版本：${release?.trim() || '-'}`,
    `责任覆盖：${ownerRows.length.toLocaleString()} 个责任方，${activeRules.length.toLocaleString()} 类活跃规则`,
    `告警压力：待通知 ${issues.length.toLocaleString()} / P0 ${p0Issues.toLocaleString()} / 证据不完整 ${missingEvidence.toLocaleString()}`,
    `升级风险：已超时 ${overdueCount.toLocaleString()} / 即将升级 ${dueSoonCount.toLocaleString()}`,
    `通知渠道：${policyChannels.length > 0 ? policyChannels.join('；') : '-'}`,
    `链路前置：${unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join('；') : '正常'}`,
    '',
    '责任方覆盖：',
    ...(ownerRows.length > 0 ? ownerRows.slice(0, 8).map((row, index) => [
      `${index + 1}. ${row.owner}`,
      `   活跃规则：${row.activeRules.toLocaleString()}，P0规则：${row.p0Rules.toLocaleString()}，命中：${row.hitCount.toLocaleString()}`,
      `   主策略：${row.primaryPolicy ? `${row.primaryPolicy.name} / ${row.primaryPolicy.target} / ${row.primaryPolicy.channel}` : '-'}`,
      `   升级窗口：${row.primaryPolicy ? formatEscalationMinutes(row.primaryPolicy.escalationMinutes) : '-'}`,
      `   验收：${row.primaryPolicy?.acceptanceCriteria || '-'}`
    ].join('\n')) : ['暂无活跃责任方']),
    '',
    '管理动作：',
    '1. P0 命中先确认责任方、渠道和升级窗口是否明确',
    '2. 证据不完整的车辆先补齐车辆服务、实时、轨迹和原始记录链接',
    '3. 超时或即将升级的告警必须同步业务责任人',
    '4. 链路异常时优先恢复采集、MySQL、TDEngine、Redis、Kafka 等前置能力'
  ].join('\n');
}

function notificationRulesRunbook(plan: QualityNotificationPlan | null, release?: string) {
  const rules = plan?.rules ?? [];
  const policies = plan?.policies ?? [];
  const lines = [
    '【通知规则Runbook】',
    `运行版本：${release?.trim() || '-'}`,
    `活跃规则：${(plan?.activeRuleCount ?? rules.filter((rule) => rule.count > 0).length).toLocaleString()} 类`,
    `P0规则：${(plan?.p0RuleCount ?? rules.filter((rule) => rule.count > 0 && rule.level === 'P0').length).toLocaleString()} 类`,
    '',
    '触发规则：',
    ...rules.map((rule, index) => [
      `${index + 1}. ${ruleTitle(rule)} / ${rule.level} / ${rule.owner}`,
      `   触发：${rule.trigger}`,
      `   通知：${rule.notify}`,
      `   SLA：${rule.sla}`,
      `   当前命中：${Number(rule.count ?? 0).toLocaleString()}`
    ].join('\n')),
    '',
    '通知策略：',
    ...policies.map((policy, index) => [
      `${index + 1}. ${policy.name} / ${policy.target}`,
      `   条件：${policy.condition}`,
      `   渠道：${policy.channel}`,
      `   升级：${formatEscalationMinutes(policy.escalationMinutes)}`,
      `   验收：${policy.acceptanceCriteria || '-'}`
    ].join('\n'))
  ];
  return lines.join('\n');
}

function priorityIssueDigest(plan: QualityNotificationPlan | null, release?: string) {
  const issues = plan?.priorityIssues ?? [];
  return [
    '【当前待通知告警】',
    `运行版本：${release?.trim() || '-'}`,
    `待通知：${issues.length.toLocaleString()} 条`,
    ...issues.map((issue, index) => [
      `${index + 1}. [${issue.priority}] ${issue.vehicleLabel}`,
      `   问题：${qualityIssueLabel(issue.issueType)} / ${issue.protocol}`,
      `   SLA：${issue.sla}`,
      `   建议动作：${issue.actionLabel}`,
      `   详情：${issue.detail || '-'}`,
      `   车辆服务：${window.location.origin}${window.location.pathname}${issue.vehicleHash}`,
      `   实时定位：${window.location.origin}${window.location.pathname}${issue.realtimeHash}`,
      `   轨迹证据：${window.location.origin}${window.location.pathname}${issue.historyHash}`,
      `   原始记录：${window.location.origin}${window.location.pathname}${issue.rawHash}`
    ].join('\n'))
  ].join('\n');
}

function alertEscalationChecklist(plan: QualityNotificationPlan | null, release?: string) {
  const rules = plan?.rules ?? [];
  const policies = plan?.policies ?? [];
  const issues = plan?.priorityIssues ?? [];
  const p0Issues = issues.filter((issue) => issue.priority === 'P0');
  const nextIssue = issues[0];
  const primaryPolicy = policies.find((policy) => policy.name.startsWith('P0')) ?? policies[0];
  const activeRules = rules.filter((rule) => Number(rule.count ?? 0) > 0);
  return [
    '【告警升级值班清单】',
    `运行版本：${release?.trim() || '-'}`,
    `值班结论：${p0Issues.length > 0 ? `P0 ${p0Issues.length.toLocaleString()} 条，需要立即确认` : issues.length > 0 ? `待通知 ${issues.length.toLocaleString()} 条，按策略跟进` : '暂无待通知告警'}`,
    `主通知策略：${primaryPolicy ? `${primaryPolicy.name} / ${primaryPolicy.target} / ${primaryPolicy.channel}` : '-'}`,
    `升级窗口：${primaryPolicy ? formatEscalationMinutes(primaryPolicy.escalationMinutes) : '-'}`,
    `验收口径：${primaryPolicy?.acceptanceCriteria || '-'}`,
    '',
    '活跃规则：',
    ...(activeRules.length > 0 ? activeRules.map((rule, index) => `${index + 1}. [${rule.level}] ${ruleTitle(rule)} / ${rule.owner} / 命中 ${Number(rule.count ?? 0).toLocaleString()} / SLA ${rule.sla}`) : ['暂无活跃规则']),
    '',
    '下一条处置：',
    nextIssue ? [
      `[${nextIssue.priority}] ${nextIssue.vehicleLabel}`,
      `问题：${qualityIssueLabel(nextIssue.issueType)} / ${nextIssue.protocol}`,
      `建议动作：${nextIssue.actionLabel} - ${nextIssue.actionDetail || '-'}`,
      `SLA：${nextIssue.sla}`,
      `证据：${evidenceSummary(nextIssue)}`,
      `车辆服务：${window.location.origin}${window.location.pathname}${nextIssue.vehicleHash}`,
      `实时证据：${window.location.origin}${window.location.pathname}${nextIssue.realtimeHash}`,
      `轨迹证据：${window.location.origin}${window.location.pathname}${nextIssue.historyHash}`,
      `原始记录：${window.location.origin}${window.location.pathname}${nextIssue.rawHash}`
    ].join('\n') : '暂无待处置告警',
    '',
    '值班动作：',
    '1. 先处理 P0，无来源/VIN 缺失/存储不可写优先于普通字段缺失',
    '2. 复制单条通知给责任团队，并同步车辆服务、实时、轨迹和原始记录',
    '3. 超过升级窗口未恢复时，按主通知策略升级到业务责任人',
    '4. 恢复后检查验收口径，并在告警事件页确认不再命中'
  ].join('\n');
}

function alertNotificationDrillPackage(plan: QualityNotificationPlan | null, health: OpsHealth | null, release?: string) {
  const rules = plan?.rules ?? [];
  const policies = plan?.policies ?? [];
  const issues = plan?.priorityIssues ?? [];
  const p0Rules = rules.filter((rule) => rule.level === 'P0');
  const p1Rules = rules.filter((rule) => rule.level === 'P1');
  const p0Issues = issues.filter((issue) => issue.priority === 'P0');
  const firstP0Policy = policies.find((policy) => policy.name.startsWith('P0')) ?? policies[0];
  const firstP1Policy = policies.find((policy) => policy.name.startsWith('P1')) ?? policies[1] ?? policies[0];
  const unhealthyLinks = (health?.linkHealth ?? []).filter((item) => item.status !== 'ok');
  const nextIssue = issues[0];
  return [
    '【告警通知演练包】',
    `运行版本：${release?.trim() || '-'}`,
    `链路状态：${unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join('；') : '正常'}`,
    `规则规模：P0 ${p0Rules.length.toLocaleString()} 类，P1 ${p1Rules.length.toLocaleString()} 类，待通知 ${issues.length.toLocaleString()} 条`,
    `当前优先级：${p0Issues.length > 0 ? `P0 ${p0Issues.length.toLocaleString()} 条` : issues.length > 0 ? 'P1/普通告警待跟进' : '暂无待通知告警'}`,
    '',
    '通知策略核对：',
    `P0策略：${firstP0Policy ? `${firstP0Policy.name} / ${firstP0Policy.target} / ${firstP0Policy.channel} / ${formatEscalationMinutes(firstP0Policy.escalationMinutes)}` : '-'}`,
    `P0验收：${firstP0Policy?.acceptanceCriteria || '-'}`,
    `P1策略：${firstP1Policy ? `${firstP1Policy.name} / ${firstP1Policy.target} / ${firstP1Policy.channel} / ${formatEscalationMinutes(firstP1Policy.escalationMinutes)}` : '-'}`,
    `P1验收：${firstP1Policy?.acceptanceCriteria || '-'}`,
    '',
    '演练步骤：',
    '1. 在告警事件页确认 P0/P1 命中数量与本页一致',
    '2. 对第一条待通知告警打开车辆服务、实时、轨迹、原始记录四类证据',
    '3. 复制单条告警通知给责任团队，记录发送时间',
    '4. 超过升级窗口仍未恢复时，按策略升级到业务责任人',
    '5. 恢复后按验收口径复核，并确认告警不再命中',
    '',
    '演练样例：',
    nextIssue ? [
      `[${nextIssue.priority}] ${nextIssue.vehicleLabel}`,
      `问题：${qualityIssueLabel(nextIssue.issueType)} / ${nextIssue.protocol}`,
      `SLA：${nextIssue.sla}`,
      `通知文本：${nextIssue.notificationText}`,
      `车辆服务：${window.location.origin}${window.location.pathname}${nextIssue.vehicleHash}`,
      `实时证据：${window.location.origin}${window.location.pathname}${nextIssue.realtimeHash}`,
      `轨迹证据：${window.location.origin}${window.location.pathname}${nextIssue.historyHash}`,
      `原始记录：${window.location.origin}${window.location.pathname}${nextIssue.rawHash}`
    ].join('\n') : '暂无待通知样例'
  ].join('\n');
}

function countEvidenceLinks(issue: QualityPriorityIssue) {
  return [issue.vehicleHash, issue.realtimeHash, issue.historyHash, issue.rawHash].filter(Boolean).length;
}

function evidenceSummary(issue: QualityPriorityIssue) {
  const count = countEvidenceLinks(issue);
  return count >= 4 ? '证据完整' : `缺 ${4 - count} 类证据`;
}

function evidenceColor(issue: QualityPriorityIssue): 'green' | 'orange' {
  return countEvidenceLinks(issue) >= 4 ? 'green' : 'orange';
}

function notificationExportRows(plan: QualityNotificationPlan | null, release = ''): NotificationExportRow[] {
  const version = release.trim() || '-';
  const ruleRows = (plan?.rules ?? []).map((rule) => ({
    category: '触发规则',
    name: ruleTitle(rule),
    level: rule.level,
    ownerOrTarget: rule.owner,
    triggerOrCondition: rule.trigger,
    notifyOrChannel: rule.notify,
    slaOrEscalation: rule.sla,
    acceptance: '-',
    count: Number(rule.count ?? 0),
    release: version
  }));
  const policyRows = (plan?.policies ?? []).map((policy) => ({
    category: '通知策略',
    name: policy.name,
    level: policy.name.startsWith('P0') ? 'P0' : policy.name.startsWith('P1') ? 'P1' : '-',
    ownerOrTarget: policy.target,
    triggerOrCondition: policy.condition,
    notifyOrChannel: policy.channel,
    slaOrEscalation: formatEscalationMinutes(policy.escalationMinutes),
    acceptance: policy.acceptanceCriteria || '-',
    count: '-',
    release: version
  }));
  return [...ruleRows, ...policyRows];
}

function navigateHash(hash: string) {
  if (!hash) return;
  window.location.hash = hash;
}

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

export function NotificationRules() {
  const [plan, setPlan] = useState<QualityNotificationPlan | null>(null);
  const [health, setHealth] = useState<OpsHealth | null>(null);
  const [loading, setLoading] = useState(true);

  const load = () => {
    setLoading(true);
    Promise.all([
      api.alertEventNotificationPlan(new URLSearchParams({ limit: '50' })),
      api.opsHealth().catch(() => null)
    ])
      .then(([nextPlan, nextHealth]) => {
        setPlan(nextPlan);
        setHealth(nextHealth);
      })
      .catch((error: Error) => Toast.error(error.message))
      .finally(() => setLoading(false));
  };

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

  const rules = plan?.rules ?? [];
  const policies = plan?.policies ?? [];
  const priorityIssues = plan?.priorityIssues ?? [];
  const activeRuleCount = plan?.activeRuleCount ?? rules.filter((rule) => rule.count > 0).length;
  const p0RuleCount = plan?.p0RuleCount ?? rules.filter((rule) => rule.count > 0 && rule.level === 'P0').length;
  const release = health?.runtime?.platformRelease ?? '';
  const p0IssueCount = priorityIssues.filter((issue) => issue.priority === 'P0').length;
  const evidenceCompleteCount = priorityIssues.filter((issue) => countEvidenceLinks(issue) >= 4).length;
  const primaryOwner = rules.find((rule) => rule.count > 0)?.owner || policies[0]?.target || '-';
  const nextPriorityIssue = priorityIssues[0];
  const slaRows = slaEscalationRows(priorityIssues, policies);
  const overdueCount = slaRows.filter((row) => row.status === 'overdue').length;
  const dueSoonCount = slaRows.filter((row) => row.status === 'due_soon').length;
  const nextSlaRow = slaRows[0];
  const executionRows = notificationExecutionRows(priorityIssues, rules, policies);
  const ownerRows = coverageOwnerRows(rules, policies);
  const reachabilityRows = notificationReachabilityRows(priorityIssues, rules, policies, slaRows);
  const reachabilityBlockedCount = reachabilityRows.filter((row) => row.status === 'blocked').length;
  const reachabilityRiskCount = reachabilityRows.filter((row) => row.status === 'risk').length;
  const totalActiveHits = ownerRows.reduce((total, row) => total + row.hitCount, 0);
  const unhealthyLinkCount = (health?.linkHealth ?? []).filter((item) => item.status !== 'ok').length;
  const notificationCustomerSteps = [
    {
      step: '01',
      title: '发现问题',
      value: `${priorityIssues.length.toLocaleString()} 待通知`,
      detail: '断链、无来源、VIN缺失、字段缺失等问题先进入车辆服务告警队列。',
      action: '告警队列',
      color: priorityIssues.length > 0 ? 'orange' as const : 'green' as const,
      onClick: () => navigateHash('#/alert-events')
    },
    {
      step: '02',
      title: '补齐证据',
      value: `${evidenceCompleteCount}/${priorityIssues.length || 0} 完整`,
      detail: '通知前必须带上车辆服务、实时监控、轨迹回放和原始记录链接。',
      action: '证据矩阵',
      color: evidenceCompleteCount === priorityIssues.length ? 'green' as const : 'orange' as const,
      onClick: () => nextPriorityIssue?.rawHash ? navigateHash(nextPriorityIssue.rawHash) : undefined
    },
    {
      step: '03',
      title: '通知责任人',
      value: `${ownerRows.length.toLocaleString()} 责任方`,
      detail: '规则命中后按责任团队、目标人群和通知渠道形成可复制通知。',
      action: '复制通知',
      color: ownerRows.length > 0 ? 'blue' as const : 'orange' as const,
      onClick: () => copyText(priorityIssueDigest(plan, release), '待通知告警')
    },
    {
      step: '04',
      title: '超时升级',
      value: `${overdueCount}/${dueSoonCount}`,
      detail: '按P0/P1策略监控已超时和即将升级的告警，避免断链无人跟进。',
      action: 'SLA报告',
      color: overdueCount > 0 ? 'red' as const : dueSoonCount > 0 ? 'orange' as const : 'green' as const,
      onClick: () => copyText(slaEscalationReport(slaRows, release), 'SLA升级报告')
    },
    {
      step: '05',
      title: '验收恢复',
      value: release || '-',
      detail: '恢复后按验收口径复核，不再命中规则后关闭本次告警闭环。',
      action: '运行手册',
      color: 'blue' as const,
      onClick: () => copyText(notificationRulesRunbook(plan, release), '通知规则Runbook')
    }
  ];
  const notificationCustomerQuestions = [
    {
      question: '哪些车受影响？',
      value: priorityIssues.length > 0 ? `${priorityIssues.length.toLocaleString()} 辆/次` : '暂无影响',
      evidence: p0IssueCount > 0 ? `P0 ${p0IssueCount.toLocaleString()} 条` : `${evidenceCompleteCount}/${priorityIssues.length || 0} 证据完整`,
      detail: '先给客户受影响车辆、告警级别和可跳转证据，不让客户从规则配置里反推影响范围。',
      action: '影响摘要',
      color: p0IssueCount > 0 ? 'red' as const : priorityIssues.length > 0 ? 'orange' as const : 'green' as const,
      onClick: () => copyText(priorityIssueDigest(plan, release), '待通知告警')
    },
    {
      question: '应该通知谁？',
      value: primaryOwner,
      evidence: policies[0]?.channel || '待配置渠道',
      detail: '按责任团队和通知策略生成可复制文本，包含车辆、原因、证据链接和下一步动作。',
      action: '复制通知',
      color: ownerRows.length > 0 ? 'blue' as const : 'orange' as const,
      onClick: () => copyText(priorityIssueDigest(plan, release), '待通知告警')
    },
    {
      question: '多久没有恢复会升级？',
      value: nextSlaRow?.policy?.escalationMinutes ? formatEscalationMinutes(nextSlaRow.policy.escalationMinutes) : `${overdueCount}/${dueSoonCount}`,
      evidence: `${overdueCount.toLocaleString()} 超时 / ${dueSoonCount.toLocaleString()} 临近`,
      detail: '客户看到的是升级时限和当前风险，后台再按P0/P1策略做超时升级和责任追踪。',
      action: 'SLA报告',
      color: overdueCount > 0 ? 'red' as const : dueSoonCount > 0 ? 'orange' as const : 'green' as const,
      onClick: () => copyText(slaEscalationReport(slaRows, release), 'SLA升级报告')
    },
    {
      question: '恢复后怎么验收？',
      value: policies[0]?.acceptanceCriteria || '按车辆服务证据验收',
      evidence: release || '当前版本',
      detail: '恢复不是只看服务在线，而是看车辆证据、实时数据、轨迹和规则命中是否一起恢复。',
      action: '验收口径',
      color: 'blue' as const,
      onClick: () => copyText(notificationRulesRunbook(plan, release), '通知规则Runbook')
    }
  ];
  const primaryPolicy = policies.find((policy) => policy.name.startsWith('P0')) ?? policies[0];
  const notificationOrchestrationItems = [
    {
      label: '告警触发',
      value: `${activeRuleCount.toLocaleString()} 类活跃规则`,
      detail: `${p0RuleCount.toLocaleString()} 类 P0，当前命中 ${totalActiveHits.toLocaleString()} 次。`,
      color: p0RuleCount > 0 ? 'red' as const : activeRuleCount > 0 ? 'orange' as const : 'green' as const,
      onClick: () => navigateHash('#/alert-events')
    },
    {
      label: '受影响车辆',
      value: `${priorityIssues.length.toLocaleString()} 待通知`,
      detail: p0IssueCount > 0 ? `P0 ${p0IssueCount.toLocaleString()} 条，优先通知业务责任人。` : `${evidenceCompleteCount}/${priorityIssues.length || 0} 证据完整。`,
      color: p0IssueCount > 0 ? 'red' as const : priorityIssues.length > 0 ? 'orange' as const : 'green' as const,
      onClick: () => copyText(priorityIssueDigest(plan, release), '待通知告警')
    },
    {
      label: '通知对象',
      value: primaryPolicy?.target || primaryOwner,
      detail: `渠道：${primaryPolicy?.channel || policies[0]?.channel || '待配置渠道'}。`,
      color: primaryPolicy?.target ? 'blue' as const : 'orange' as const,
      onClick: () => copyText(priorityIssueDigest(plan, release), '待通知告警')
    },
    {
      label: '升级时钟',
      value: overdueCount > 0 ? `已超时 ${overdueCount.toLocaleString()}` : dueSoonCount > 0 ? `临近 ${dueSoonCount.toLocaleString()}` : '正常跟进',
      detail: primaryPolicy ? formatEscalationMinutes(primaryPolicy.escalationMinutes) : '缺少升级策略。',
      color: overdueCount > 0 ? 'red' as const : dueSoonCount > 0 ? 'orange' as const : 'green' as const,
      onClick: () => copyText(slaEscalationReport(slaRows, release), 'SLA升级报告')
    },
    {
      label: '恢复验收',
      value: primaryPolicy?.acceptanceCriteria || '按车辆服务证据验收',
      detail: '恢复后必须复核实时、轨迹、原始记录和规则命中状态。',
      color: primaryPolicy?.acceptanceCriteria ? 'green' as const : 'orange' as const,
      onClick: () => copyText(notificationRulesRunbook(plan, release), '通知规则Runbook')
    }
  ];
  const notificationOrchestrationActions = [
    {
      label: '查看告警',
      action: '告警队列',
      color: priorityIssues.length > 0 ? 'orange' as const : 'green' as const,
      disabled: false,
      onClick: () => navigateHash('#/alert-events')
    },
    {
      label: '复制通知',
      action: '待通知',
      color: priorityIssues.length > 0 ? 'blue' as const : 'grey' as const,
      disabled: priorityIssues.length === 0,
      onClick: () => copyText(priorityIssueDigest(plan, release), '待通知告警')
    },
    {
      label: '复制SLA',
      action: 'SLA报告',
      color: overdueCount > 0 ? 'red' as const : dueSoonCount > 0 ? 'orange' as const : 'green' as const,
      disabled: slaRows.length === 0,
      onClick: () => copyText(slaEscalationReport(slaRows, release), 'SLA升级报告')
    },
    {
      label: '导出演练',
      action: '演练包',
      color: 'blue' as const,
      disabled: false,
      onClick: () => copyText(alertNotificationDrillPackage(plan, health, release), '通知演练包')
    }
  ];
  const exportNotificationRules = () => {
    const rows = notificationExportRows(plan, release);
    if (rows.length === 0) {
      Toast.warning('当前没有可导出的通知规则');
      return;
    }
    downloadCsv(`notification-rules-${release || 'current'}.csv`, buildCsv(notificationExportColumns, rows));
    Toast.success(`已导出 ${rows.length.toLocaleString()} 条通知规则`);
  };

  return (
    <div className="vp-page">
      <PageHeader title="通知闭环" description="面向客户交付告警影响、通知对象、升级时限和恢复验收；规则配置只作为闭环背后的证据。" />
      <Card bordered loading={loading} className="vp-notification-customer-board" bodyStyle={{ padding: 0 }}>
        <div className="vp-notification-customer-summary">
          <Space wrap>
            <Tag color="blue">客户告警闭环</Tag>
            <Tag color={p0IssueCount > 0 ? 'red' : priorityIssues.length > 0 ? 'orange' : 'green'}>
              {p0IssueCount > 0 ? `P0 ${p0IssueCount.toLocaleString()}` : priorityIssues.length > 0 ? `${priorityIssues.length.toLocaleString()} 待通知` : '暂无待通知'}
            </Tag>
            <Tag color={overdueCount > 0 ? 'red' : dueSoonCount > 0 ? 'orange' : 'green'}>{overdueCount.toLocaleString()} 超时 / {dueSoonCount.toLocaleString()} 临近</Tag>
          </Space>
          <Typography.Title heading={5} style={{ margin: 0 }}>从告警触发到通知升级，再到验收恢复</Typography.Title>
          <Typography.Text type="secondary">
            客户需要知道哪些车受影响、证据是否完整、通知给谁、多久升级、恢复后如何验收；规则和策略只是支撑这个闭环的配置。
          </Typography.Text>
          <Space wrap>
            <Button size="small" theme="solid" type="primary" onClick={() => navigateHash('#/alert-events')}>查看告警事件</Button>
            <Button size="small" theme="light" type="primary" disabled={!nextPriorityIssue?.vehicleHash} onClick={() => nextPriorityIssue?.vehicleHash && navigateHash(nextPriorityIssue.vehicleHash)}>下一辆车</Button>
            <Button size="small" theme="light" type="warning" onClick={() => copyText(alertEscalationChecklist(plan, release), '升级值班清单')}>复制值班清单</Button>
            <Button size="small" theme="light" onClick={() => copyText(notificationCoverageReport({ plan, release, ownerRows, overdueCount, dueSoonCount, health }), '通知覆盖报告')}>复制覆盖报告</Button>
          </Space>
        </div>
        <div className="vp-notification-customer-steps">
          {notificationCustomerSteps.map((item) => (
            <button
              key={item.step}
              type="button"
              className="vp-notification-customer-step"
              onClick={item.onClick}
              aria-label={`客户告警闭环 ${item.title} ${item.action}`}
            >
              <span>{item.step}</span>
              <Tag color={item.color}>{item.title}</Tag>
              <strong>{item.value}</strong>
              <small>{item.detail}</small>
              <em>{item.action}</em>
            </button>
          ))}
        </div>
      </Card>
      <section className="vp-notification-orchestration-strip" aria-label="客户通知编排条">
        <div className="vp-notification-orchestration-copy">
          <Space wrap>
            <Tag color="blue">客户通知编排条</Tag>
            <Tag color={p0IssueCount > 0 || overdueCount > 0 ? 'red' : priorityIssues.length > 0 ? 'orange' : 'green'}>
              {priorityIssues.length > 0 ? `${priorityIssues.length.toLocaleString()} 待通知` : '当前稳定'}
            </Tag>
            <Tag color={primaryPolicy?.target ? 'blue' : 'orange'}>{primaryPolicy?.channel || '待配置渠道'}</Tag>
          </Space>
          <Typography.Title heading={5} style={{ margin: 0 }}>
            先把规则命中转成可执行的通知编排：触发条件、受影响车辆、责任方、渠道、SLA 和验收口径在一行里完成确认。
          </Typography.Title>
          <Typography.Text type="secondary">
            客户需要的是谁受影响、谁处理、多久升级和如何证明恢复；规则表保留为后面的运行证据。
          </Typography.Text>
        </div>
        <div className="vp-notification-orchestration-grid">
          {notificationOrchestrationItems.map((item) => (
            <button
              key={item.label}
              type="button"
              className="vp-notification-orchestration-item"
              onClick={item.onClick}
              aria-label={`客户通知编排 ${item.label} ${item.value}`}
            >
              <Tag color={item.color}>{item.label}</Tag>
              <strong>{item.value}</strong>
              <span>{item.detail}</span>
            </button>
          ))}
        </div>
        <div className="vp-notification-orchestration-actions">
          {notificationOrchestrationActions.map((item) => (
            <button
              key={item.label}
              type="button"
              className="vp-notification-orchestration-action"
              disabled={item.disabled}
              onClick={item.onClick}
              aria-label={`客户通知编排动作 ${item.label} ${item.action}`}
            >
              <Tag color={item.color}>{item.label}</Tag>
              <span>{item.action}</span>
            </button>
          ))}
        </div>
      </section>
      <Card bordered loading={loading} title="客户通知常问" style={{ marginBottom: 16 }}>
        <div className="vp-notification-question-board">
          <div className="vp-notification-question-summary">
            <Space wrap>
              <Tag color="blue">客户口径</Tag>
              <Tag color={priorityIssues.length > 0 ? 'orange' : 'green'}>
                {priorityIssues.length > 0 ? `${priorityIssues.length.toLocaleString()} 待解释` : '当前稳定'}
              </Tag>
            </Space>
            <Typography.Title heading={5} style={{ margin: 0 }}>把通知规则翻译成客户能理解的影响、责任、升级和恢复口径</Typography.Title>
            <Typography.Text type="secondary">
              客户不需要先理解规则配置，先回答受影响车辆、通知对象、升级时限和恢复验收。
            </Typography.Text>
          </div>
          <div className="vp-notification-question-grid">
            {notificationCustomerQuestions.map((item) => (
              <button
                key={item.question}
                type="button"
                className="vp-notification-question-item"
                onClick={item.onClick}
                aria-label={`客户通知常问 ${item.question} ${item.action}`}
              >
                <Space wrap>
                  <Tag color={item.color}>{item.question}</Tag>
                  <Tag color="grey">{item.evidence}</Tag>
                </Space>
                <strong>{item.value}</strong>
                <small>{item.detail}</small>
                <em>{item.action}</em>
              </button>
            ))}
          </div>
        </div>
      </Card>
      <div className="vp-kpi-grid">
        <Card bordered loading={loading}>
          <div className="vp-kpi-value">{activeRuleCount.toLocaleString()}</div>
          <div className="vp-kpi-label">活跃规则</div>
        </Card>
        <Card bordered loading={loading}>
          <div className="vp-kpi-value">{p0RuleCount.toLocaleString()}</div>
          <div className="vp-kpi-label">P0规则</div>
        </Card>
        <Card bordered loading={loading}>
          <div className="vp-kpi-value">{policies.length.toLocaleString()}</div>
          <div className="vp-kpi-label">通知策略</div>
        </Card>
        <Card bordered loading={loading}>
          <div className="vp-kpi-value">{priorityIssues.length.toLocaleString()}</div>
          <div className="vp-kpi-label">待通知告警</div>
        </Card>
        <Card bordered loading={loading}>
          <div className="vp-kpi-value">{release || '-'}</div>
          <div className="vp-kpi-label">运行版本</div>
        </Card>
      </div>

      <Card
        bordered
        loading={loading}
        title={<Space><span>通知覆盖与升级风险</span><Button size="small" aria-label="复制通知覆盖报告" icon={<IconCopy />} onClick={() => copyText(notificationCoverageReport({ plan, release, ownerRows, overdueCount, dueSoonCount, health }), '通知覆盖报告')}>复制覆盖报告</Button></Space>}
        style={{ marginTop: 16 }}
      >
        <div className="vp-notification-coverage-board">
          <div className="vp-notification-coverage-summary">
            <Space wrap>
              <Tag color={p0IssueCount > 0 || overdueCount > 0 ? 'red' : dueSoonCount > 0 ? 'orange' : 'green'}>
                {p0IssueCount > 0 || overdueCount > 0 ? '高优先级跟进' : dueSoonCount > 0 ? '临近升级' : '覆盖稳定'}
              </Tag>
              <Tag color={unhealthyLinkCount > 0 ? 'orange' : 'green'}>{unhealthyLinkCount > 0 ? '链路需关注' : '链路正常'}</Tag>
            </Space>
            <Typography.Text strong>{ownerRows.length.toLocaleString()} 个责任方</Typography.Text>
            <Typography.Text type="secondary">
              将告警规则、通知策略、证据完整度和 SLA 升级风险按责任团队归并，便于值班人确认是否能真正通知到人。
            </Typography.Text>
          </div>
          <div className="vp-notification-coverage-grid">
            {[
              { label: '责任覆盖', value: ownerRows.length.toLocaleString(), detail: `${activeRuleCount.toLocaleString()} 类活跃规则已映射责任方。`, color: ownerRows.length > 0 ? 'green' as const : 'orange' as const },
              { label: '活跃命中', value: totalActiveHits.toLocaleString(), detail: '来自当前告警规则命中数量，用于判断通知压力。', color: totalActiveHits > 0 ? 'orange' as const : 'green' as const },
              { label: 'P0压力', value: p0IssueCount.toLocaleString(), detail: `${p0RuleCount.toLocaleString()} 类 P0 规则，优先进入升级队列。`, color: p0IssueCount > 0 ? 'red' as const : 'green' as const },
              { label: '升级风险', value: `${overdueCount}/${dueSoonCount}`, detail: '已超时 / 即将升级，直接影响通知闭环时效。', color: overdueCount > 0 ? 'red' as const : dueSoonCount > 0 ? 'orange' as const : 'green' as const },
              { label: '链路前置', value: unhealthyLinkCount.toLocaleString(), detail: '采集、存储、缓存等前置链路异常会影响告警判定。', color: unhealthyLinkCount > 0 ? 'orange' as const : 'green' as const }
            ].map((item) => (
              <div key={item.label} className="vp-notification-coverage-item">
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <Typography.Text type="secondary">{item.detail}</Typography.Text>
              </div>
            ))}
          </div>
        </div>
        <div className="vp-notification-owner-list">
          {ownerRows.length > 0 ? ownerRows.slice(0, 6).map((row) => (
            <div key={row.owner} className="vp-notification-owner-item">
              <div>
                <Space wrap>
                  <Tag color={row.p0Rules > 0 ? 'red' : 'orange'}>{row.owner}</Tag>
                  <Typography.Text strong>命中 {row.hitCount.toLocaleString()}</Typography.Text>
                </Space>
                <Typography.Text type="secondary">
                  {row.activeRules.toLocaleString()} 类活跃规则，{row.p0Rules.toLocaleString()} 类 P0。
                </Typography.Text>
              </div>
              <Space wrap>
                <Tag color={row.primaryPolicy?.name.startsWith('P0') ? 'red' : 'blue'}>{row.primaryPolicy?.name || '未匹配策略'}</Tag>
                <Tag color="blue">{row.primaryPolicy?.channel || '-'}</Tag>
                <Tag color="grey">{row.primaryPolicy ? formatEscalationMinutes(row.primaryPolicy.escalationMinutes) : '-'}</Tag>
              </Space>
            </div>
          )) : (
            <Tag color="green">暂无活跃告警责任方</Tag>
          )}
        </div>
      </Card>

      <Card
        bordered
        loading={loading}
        title={<Space><span>通知可达性检查</span><Button size="small" aria-label="复制通知可达性检查" icon={<IconCopy />} onClick={() => copyText(notificationReachabilityReport(reachabilityRows, release), '通知可达性检查')}>复制可达性检查</Button></Space>}
        style={{ marginTop: 16 }}
      >
        <div className="vp-notification-reachability-board">
          <div className="vp-notification-reachability-summary">
            <Space wrap>
              <Tag color={reachabilityBlockedCount > 0 ? 'red' : reachabilityRiskCount > 0 ? 'orange' : 'green'}>
                {reachabilityBlockedCount > 0 ? '存在阻断' : reachabilityRiskCount > 0 ? '需要跟进' : '可触达'}
              </Tag>
              <Tag color="blue">{reachabilityRows.length.toLocaleString()} 个责任方</Tag>
            </Space>
            <Typography.Text strong>
              阻断 {reachabilityBlockedCount.toLocaleString()} / 风险 {reachabilityRiskCount.toLocaleString()}
            </Typography.Text>
            <Typography.Text type="secondary">
              按责任方检查通知对象、渠道、升级窗口、证据完整度和验收口径，避免告警已经触发但无人收到或无法闭环。
            </Typography.Text>
          </div>
          <div className="vp-notification-reachability-grid">
            {reachabilityRows.length > 0 ? reachabilityRows.slice(0, 6).map((row) => (
              <div key={row.owner} className="vp-notification-reachability-item">
                <Space wrap>
                  <Tag color={row.statusColor}>{row.statusLabel}</Tag>
                  <Tag color={row.p0IssueCount > 0 ? 'red' : 'blue'}>{row.owner}</Tag>
                </Space>
                <strong>{row.issueCount.toLocaleString()} 条待通知</strong>
                <Typography.Text type="secondary">
                  对象：{row.target}；渠道：{row.channel}；升级：{row.escalation}
                </Typography.Text>
                <Space wrap>
                  <Tag color={row.evidenceComplete === row.issueCount ? 'green' : 'orange'}>证据 {row.evidenceComplete}/{row.issueCount}</Tag>
                  <Tag color={row.acceptance === '-' ? 'orange' : 'green'}>{row.acceptance === '-' ? '缺验收' : '有验收'}</Tag>
                  <Tag color={row.p0IssueCount > 0 ? 'red' : 'grey'}>P0 {row.p0IssueCount}</Tag>
                </Space>
                <Typography.Text>{row.nextAction}</Typography.Text>
              </div>
            )) : (
              <Tag color="green">暂无待检查通知责任方</Tag>
            )}
          </div>
        </div>
      </Card>

      <Card
        bordered
        loading={loading}
        title={<Space><span>告警处置队列</span><Button size="small" aria-label="复制待通知汇总" disabled={priorityIssues.length === 0} icon={<IconCopy />} onClick={() => copyText(priorityIssueDigest(plan, release), '待通知告警')}>复制待通知汇总</Button><Button size="small" aria-label="复制升级值班清单" icon={<IconCopy />} onClick={() => copyText(alertEscalationChecklist(plan, release), '升级值班清单')}>复制升级值班清单</Button><Button size="small" aria-label="复制通知演练包" icon={<IconCopy />} onClick={() => copyText(alertNotificationDrillPackage(plan, health, release), '通知演练包')}>复制通知演练包</Button></Space>}
        style={{ marginTop: 16 }}
      >
        <div className="vp-alert-command-board">
          <div className="vp-alert-command-grid">
            {[
              { label: '待通知告警', value: priorityIssues.length.toLocaleString(), color: priorityIssues.length > 0 ? 'orange' as const : 'green' as const },
              { label: 'P0优先', value: p0IssueCount.toLocaleString(), color: p0IssueCount > 0 ? 'red' as const : 'green' as const },
              { label: '证据完整', value: `${evidenceCompleteCount}/${priorityIssues.length || 0}`, color: evidenceCompleteCount === priorityIssues.length ? 'green' as const : 'orange' as const },
              { label: '主责任方', value: primaryOwner, color: 'blue' as const }
            ].map((item) => (
              <div key={item.label} className="vp-alert-command-item">
                <Tag color={item.color}>{item.label}</Tag>
                <div>{item.value}</div>
              </div>
            ))}
          </div>
          <div className="vp-alert-next-action">
            <div className="vp-current-service-title">下一步处置</div>
            {nextPriorityIssue ? (
              <>
                <Typography.Text strong>{nextPriorityIssue.vehicleLabel}</Typography.Text>
                <Typography.Text type="secondary">{nextPriorityIssue.actionDetail || nextPriorityIssue.actionLabel}</Typography.Text>
                <Space wrap>
                  <Tag color={nextPriorityIssue.priority === 'P0' ? 'red' : 'orange'}>{nextPriorityIssue.priority}</Tag>
                  <Tag color={evidenceColor(nextPriorityIssue)}>{evidenceSummary(nextPriorityIssue)}</Tag>
                  <Tag color="blue">{nextPriorityIssue.sla}</Tag>
                </Space>
                <Space wrap>
                  <Button size="small" aria-label="下一步车辆服务" disabled={!nextPriorityIssue.vehicleHash} onClick={() => navigateHash(nextPriorityIssue.vehicleHash)}>车辆服务</Button>
                  <Button size="small" aria-label="下一步实时证据" disabled={!nextPriorityIssue.realtimeHash} onClick={() => navigateHash(nextPriorityIssue.realtimeHash)}>实时证据</Button>
                  <Button size="small" aria-label="下一步轨迹证据" disabled={!nextPriorityIssue.historyHash} onClick={() => navigateHash(nextPriorityIssue.historyHash)}>轨迹证据</Button>
                  <Button size="small" aria-label="下一步原始记录" disabled={!nextPriorityIssue.rawHash} onClick={() => navigateHash(nextPriorityIssue.rawHash)}>原始记录</Button>
                </Space>
              </>
            ) : (
              <Tag color="green">暂无待通知告警</Tag>
            )}
          </div>
        </div>
      </Card>

      <Card
        bordered
        loading={loading}
        title={<Space><span>通知执行矩阵</span><Button size="small" aria-label="复制通知执行矩阵" icon={<IconCopy />} disabled={executionRows.length === 0} onClick={() => copyText(notificationExecutionMatrixReport(executionRows, release), '通知执行矩阵')}>复制执行矩阵</Button></Space>}
        style={{ marginTop: 16 }}
      >
        <Table<NotificationExecutionRow>
          pagination={false}
          dataSource={executionRows.slice(0, 10)}
          rowKey={(row?: NotificationExecutionRow) => row?.key ?? ''}
          columns={[
            { title: '优先级', width: 90, render: (_: unknown, row: NotificationExecutionRow) => <Tag color={row.issue.priority === 'P0' ? 'red' : 'orange'}>{row.issue.priority}</Tag> },
            { title: '车辆', width: 220, render: (_: unknown, row: NotificationExecutionRow) => row.issue.vehicleLabel },
            { title: '问题', width: 130, render: (_: unknown, row: NotificationExecutionRow) => qualityIssueLabel(row.issue.issueType) },
            { title: '触发规则', width: 180, render: (_: unknown, row: NotificationExecutionRow) => row.rule ? `${ruleTitle(row.rule)} / ${row.rule.owner}` : '-' },
            { title: '通知对象', width: 210, render: (_: unknown, row: NotificationExecutionRow) => row.policy?.target || '-' },
            { title: '渠道', width: 150, render: (_: unknown, row: NotificationExecutionRow) => row.policy?.channel || '-' },
            { title: '升级窗口', width: 120, render: (_: unknown, row: NotificationExecutionRow) => row.policy ? formatEscalationMinutes(row.policy.escalationMinutes) : '-' },
            { title: 'SLA状态', width: 150, render: (_: unknown, row: NotificationExecutionRow) => <Tag color={row.slaRow.statusColor}>{row.slaRow.statusLabel}</Tag> },
            { title: '证据', width: 110, render: (_: unknown, row: NotificationExecutionRow) => <Tag color={row.evidenceColor}>{row.evidenceText}</Tag> },
            {
              title: '操作',
              render: (_: unknown, row: NotificationExecutionRow) => (
                <Space wrap>
                  <Button size="small" disabled={!row.issue.vehicleHash} onClick={() => navigateHash(row.issue.vehicleHash)}>车辆服务</Button>
                  <Button size="small" disabled={!row.issue.rawHash} onClick={() => navigateHash(row.issue.rawHash)}>原始记录</Button>
                  <Button size="small" icon={<IconCopy />} onClick={() => copyText(row.issue.notificationText, '告警通知')}>复制通知</Button>
                </Space>
              )
            }
          ]}
        />
      </Card>

      <Card
        bordered
        loading={loading}
        title={<Space><span>SLA升级态势</span><Button size="small" aria-label="复制SLA升级报告" icon={<IconCopy />} disabled={slaRows.length === 0} onClick={() => copyText(slaEscalationReport(slaRows, release), 'SLA升级报告')}>复制SLA升级报告</Button></Space>}
        style={{ marginTop: 16 }}
      >
        <div className="vp-sla-board">
          <div className="vp-sla-summary">
            {[
              { label: '已超时', value: overdueCount.toLocaleString(), color: overdueCount > 0 ? 'red' as const : 'green' as const },
              { label: '即将升级', value: dueSoonCount.toLocaleString(), color: dueSoonCount > 0 ? 'orange' as const : 'green' as const },
              { label: '待通知', value: slaRows.length.toLocaleString(), color: slaRows.length > 0 ? 'blue' as const : 'green' as const },
              { label: '下一优先', value: nextSlaRow?.statusLabel || '-', color: nextSlaRow?.statusColor ?? 'grey' as const }
            ].map((item) => (
              <div key={item.label} className="vp-sla-summary-item">
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
              </div>
            ))}
          </div>
          <Table<SlaEscalationRow>
            pagination={false}
            dataSource={slaRows.slice(0, 8)}
            rowKey={(row?: SlaEscalationRow) => `${row?.issue.priority ?? ''}-${row?.issue.vehicleLabel ?? ''}-${row?.issue.lastSeen ?? ''}`}
            columns={[
              { title: '状态', width: 150, render: (_: unknown, row: SlaEscalationRow) => <Tag color={row.statusColor}>{row.statusLabel}</Tag> },
              { title: '车辆', width: 220, render: (_: unknown, row: SlaEscalationRow) => row.issue.vehicleLabel },
              { title: '优先级', width: 90, render: (_: unknown, row: SlaEscalationRow) => <Tag color={row.issue.priority === 'P0' ? 'red' : 'orange'}>{row.issue.priority}</Tag> },
              { title: '策略', width: 210, render: (_: unknown, row: SlaEscalationRow) => row.policy?.name || '-' },
              { title: '已等待', width: 120, render: (_: unknown, row: SlaEscalationRow) => formatDurationFromMinutes(row.elapsedMinutes) },
              { title: '责任方', width: 190, render: (_: unknown, row: SlaEscalationRow) => row.policy?.target || '-' },
              {
                title: '处置',
                render: (_: unknown, row: SlaEscalationRow) => (
                  <Space wrap>
                    <Button size="small" disabled={!row.issue.vehicleHash} onClick={() => navigateHash(row.issue.vehicleHash)}>车辆服务</Button>
                    <Button size="small" disabled={!row.issue.rawHash} onClick={() => navigateHash(row.issue.rawHash)}>原始记录</Button>
                    <Button size="small" icon={<IconCopy />} onClick={() => copyText(row.issue.notificationText, '告警通知')}>复制通知</Button>
                  </Space>
                )
              }
            ]}
          />
        </div>
      </Card>

      <Card
        bordered
        title={<Space><span>规则运行手册</span><Button size="small" aria-label="复制通知规则Runbook" icon={<IconCopy />} onClick={() => copyText(notificationRulesRunbook(plan, release), '通知规则Runbook')}>复制通知规则Runbook</Button><Button size="small" aria-label="导出通知规则CSV" onClick={exportNotificationRules}>导出规则 CSV</Button><Button size="small" loading={loading} onClick={load}>刷新</Button></Space>}
      >
        <div className="vp-alert-flow">
          {[
            { label: '触发', value: `${activeRuleCount} 类规则`, detail: '规则从车辆服务质量、来源断链、字段缺失和容量风险中生成。' },
            { label: '通知', value: `${policies.length} 套策略`, detail: '策略定义目标人群、渠道、升级窗口和验收口径。' },
            { label: '闭环', value: `${p0RuleCount} 类 P0`, detail: 'P0 问题必须有证据链接、恢复时间和验收结果。' }
          ].map((item) => (
            <div key={item.label} className="vp-alert-flow-item">
              <Tag color="blue">{item.label}</Tag>
              <div className="vp-alert-flow-value">{item.value}</div>
              <div>{item.detail}</div>
            </div>
          ))}
        </div>
      </Card>

      <Card bordered title="触发规则" loading={loading} style={{ marginTop: 16 }}>
        <Table<QualityAlertRule>
          pagination={false}
          dataSource={rules}
          rowKey="issueType"
          columns={[
            { title: '规则', width: 180, render: (_: unknown, row: QualityAlertRule) => ruleTitle(row) },
            { title: '级别', width: 90, render: (_: unknown, row: QualityAlertRule) => <Tag color={ruleColor(row)}>{row.level}</Tag> },
            { title: '责任团队', width: 130, dataIndex: 'owner' },
            { title: '当前命中', width: 110, render: (_: unknown, row: QualityAlertRule) => Number(row.count ?? 0).toLocaleString() },
            { title: '触发条件', dataIndex: 'trigger' },
            { title: '通知动作', dataIndex: 'notify' },
            { title: 'SLA', width: 120, dataIndex: 'sla' }
          ]}
        />
      </Card>

      <Card
        bordered
        loading={loading}
        title="当前待通知告警"
        style={{ marginTop: 16 }}
      >
        <Table<QualityPriorityIssue>
          pagination={false}
          dataSource={priorityIssues}
          rowKey={(row?: QualityPriorityIssue) => `${row?.priority ?? ''}-${row?.protocol ?? ''}-${row?.issueType ?? ''}-${row?.vehicleLabel ?? ''}-${row?.lastSeen ?? ''}`}
          columns={[
            { title: '优先级', width: 90, render: (_: unknown, row: QualityPriorityIssue) => <Tag color={row.priority === 'P0' ? 'red' : 'orange'}>{row.priority}</Tag> },
            { title: '车辆', width: 210, dataIndex: 'vehicleLabel' },
            { title: '问题', width: 130, render: (_: unknown, row: QualityPriorityIssue) => qualityIssueLabel(row.issueType) },
            { title: 'SLA', width: 120, dataIndex: 'sla' },
            { title: '证据完整度', width: 120, render: (_: unknown, row: QualityPriorityIssue) => <Tag color={evidenceColor(row)}>{evidenceSummary(row)}</Tag> },
            { title: '建议动作', width: 160, dataIndex: 'actionLabel' },
            { title: '最后时间', width: 170, dataIndex: 'lastSeen' },
            {
              title: '说明',
              render: (_: unknown, row: QualityPriorityIssue) => (
                <Typography.Text ellipsis={{ showTooltip: true }}>{row.detail || row.actionDetail || '-'}</Typography.Text>
              )
            },
            {
              title: '证据',
              width: 360,
              render: (_: unknown, row: QualityPriorityIssue) => (
                <Space spacing={4} wrap>
                  <Button size="small" disabled={!row.vehicleHash} onClick={() => navigateHash(row.vehicleHash)}>车辆服务</Button>
                  <Button size="small" disabled={!row.realtimeHash} onClick={() => navigateHash(row.realtimeHash)}>实时</Button>
                  <Button size="small" disabled={!row.historyHash} onClick={() => navigateHash(row.historyHash)}>轨迹</Button>
                  <Button size="small" disabled={!row.rawHash} onClick={() => navigateHash(row.rawHash)}>原始</Button>
                  <Button size="small" aria-label="复制单条告警通知" icon={<IconCopy />} onClick={() => copyText(row.notificationText, '告警通知')}>复制通知</Button>
                </Space>
              )
            }
          ]}
        />
      </Card>

      <Card bordered title="通知策略" loading={loading} style={{ marginTop: 16 }}>
        <div className="vp-notification-policy-list">
          {policies.map((policy: QualityNotificationPolicy) => (
            <div key={policy.name} className="vp-notification-policy">
              <div>
                <Space>
                  <Tag color={policy.name.startsWith('P0') ? 'red' : policy.name.startsWith('P1') ? 'orange' : 'grey'}>{policy.name}</Tag>
                  <strong>{policy.target}</strong>
                </Space>
                <div className="vp-alert-policy-detail">{policy.condition}</div>
                <div className="vp-alert-policy-detail">验收：{policy.acceptanceCriteria || '-'}</div>
              </div>
              <Space wrap>
                <Tag color="red">{formatEscalationMinutes(policy.escalationMinutes)}</Tag>
                <Tag color="blue">{policy.channel}</Tag>
              </Space>
            </div>
          ))}
        </div>
      </Card>
    </div>
  );
}
