import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { MemoryRouter, useLocation } from 'react-router-dom';
import { afterEach, expect, test, vi } from 'vitest';
import OperationsPage from './OperationsPage';

const mocks = vi.hoisted(() => ({
  opsHealth: vi.fn(), sourceReadiness: vi.fn(), session: vi.fn(), vehicleCoverage: vi.fn(),
  vehicleSourceDiagnostic: vi.fn(), updateVehicleSourcePolicy: vi.fn(),
  reconciliationSummary: vi.fn(), reconciliationIssues: vi.fn(), reconciliationIssue: vi.fn(), updateReconciliationIssue: vi.fn()
}));
const layout = vi.hoisted(() => ({ mobile: false }));
vi.mock('../../api/client', () => ({ api: mocks }));
vi.mock('../hooks/useMobileLayout', () => ({ useMobileLayout: () => layout.mobile }));

afterEach(() => { cleanup(); layout.mobile = false; Object.values(mocks).forEach((mock) => mock.mockReset()); });

function LocationProbe() {
  const location = useLocation();
  return <output data-testid="operations-location">{location.pathname}{location.search}</output>;
}

function renderOperations(client: QueryClient, initialEntries = ['/operations']) {
  return render(<MemoryRouter initialEntries={initialEntries}><QueryClientProvider client={client}><OperationsPage /><LocationProbe /></QueryClientProvider></MemoryRouter>);
}

function seedSession() {
  mocks.session.mockResolvedValue({ name: '平台管理员', role: 'admin', userType: 'admin', authMode: 'enforce', menuKeys: ['operations'] });
  mocks.reconciliationSummary.mockResolvedValue({
    active: 1, pending: 1, confirmed: 0, recovered: 2, overSla: 1,
    byRule: [{ name: 'POSITION_DRIFT', count: 1 }], bySeverity: [{ name: 'major', count: 1 }],
    trend: [{ date: '2026-07-16', detected: 1, new: 1, active: 1, recovered: 0 }],
    lastRunAt: '2026-07-16 02:15:00', asOf: '2026-07-16 10:00:00'
  });
  mocks.reconciliationIssues.mockResolvedValue({
    items: [{
      id: 'issue-1', ruleCode: 'POSITION_DRIFT', category: 'location', severity: 'major', status: 'pending',
      vin: 'VIN001', plate: '粤A00001', protocolA: 'GB32960', protocolB: 'JT808', title: '多来源实时位置漂移',
      summary: '两个来源相差 1286 米', evidence: { distanceM: 1286 }, firstSeenAt: '2026-07-16 08:00:00',
      lastSeenAt: '2026-07-16 10:00:00', occurrenceCount: 3, recoveredAt: '', resolutionNote: '', resolvedBy: '', version: 1
    }],
    total: 1, limit: 50, offset: 0
  });
  mocks.reconciliationIssue.mockResolvedValue({
    id: 'issue-1', ruleCode: 'POSITION_DRIFT', category: 'location', severity: 'major', status: 'pending',
    vin: 'VIN001', plate: '粤A00001', protocolA: 'GB32960', protocolB: 'JT808', title: '多来源实时位置漂移',
    summary: '两个来源相差 1286 米', evidence: { distanceM: 1286 }, firstSeenAt: '2026-07-16 08:00:00',
    lastSeenAt: '2026-07-16 10:00:00', occurrenceCount: 3, recoveredAt: '', resolutionNote: '', resolvedBy: '', version: 1,
    actions: [{ id: 1, action: 'detect', fromStatus: '', toStatus: 'pending', actor: 'reconciliation-evaluator', note: '规则首次发现差异', createdAt: '2026-07-16 08:00:00' }]
  });
}

test('renders reconciliation queue, loads evidence on demand and records review conclusion', async () => {
  seedSession();
  mocks.opsHealth.mockResolvedValue({
    linkHealth: [{ name: 'MySQL realtime', status: 'ok', detail: '主库连接正常' }], kafkaLag: 0, activeConnections: 10, capacityFindings: [], redisOnlineKeys: 5,
    tdengineWritable: true, mysqlWritable: true,
    runtime: { platformRelease: 'test-release', dataMode: 'production', requestTimeoutMs: 5000, amapSecurityProxyEnabled: true, amapSecurityCodeExposed: false }
  });
  mocks.sourceReadiness.mockResolvedValue({ totalVehicles: 1, boundVehicles: 1, identityRequiredVehicles: 0, onlineVehicles: 1, sources: [] });
  mocks.updateReconciliationIssue.mockResolvedValue({
    ...(await mocks.reconciliationIssue()),
    status: 'confirmed_source_a',
    resolutionNote: '来源 A 原始报文可信',
    resolvedBy: '平台管理员',
    version: 2
  });
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  renderOperations(client);

  expect(await screen.findByText('质量问题队列')).toBeInTheDocument();
  expect(screen.getByRole('heading', { name: '运行与数据质量', level: 5 })).toBeInTheDocument();
  expect(screen.getByLabelText('运维质量操作')).toHaveClass('v2-workspace-command-bar', 'v2-ops-command-bar');
  expect(screen.getByRole('button', { name: '刷新全局证据' })).toHaveClass('v2-workspace-mobile-tool-button', 'is-muted');
  const navigation = screen.getByRole('navigation', { name: '运维质量视图' });
  expect(navigation).toHaveClass('v2-ops-navigation');
  expect(within(navigation).getByText('每日自动对账')).toBeInTheDocument();
  expect(await within(navigation).findByText('1 项待复核')).toBeInTheDocument();
  expect(screen.getByRole('tab', { name: '差异处置 1', selected: true })).toHaveClass('semi-button');
  expect(screen.getByRole('tabpanel', { name: '差异处置' })).toHaveAttribute('tabindex', '0');
  expect(document.querySelector('.v2-ops-page')).toHaveClass('is-reconciliation');
  expect(screen.queryByText('先选择一辆车')).not.toBeInTheDocument();
  expect(screen.getByText('质量问题队列').closest('.semi-card')).toHaveClass('v2-reconcile-center');
  expect(screen.getByText('质量问题队列').closest('.v2-workspace-panel-header')).toHaveClass('v2-reconcile-heading');
  const reconciliationMetrics = screen.getByRole('list', { name: '差异处置队列概览' });
  expect(reconciliationMetrics.closest('.semi-card')).toHaveClass('v2-workspace-metric-rail', 'v2-reconcile-metric-rail');
  expect(within(reconciliationMetrics).getAllByRole('listitem')).toHaveLength(4);
  expect(within(reconciliationMetrics).getByText('待复核').closest('[role="listitem"]')).toHaveClass('is-primary');
  expect(within(reconciliationMetrics).getByText('SLA 超时').closest('[role="listitem"]')).toHaveClass('is-danger');
  expect(within(reconciliationMetrics).getByText('已自动恢复').closest('[role="listitem"]')).toHaveClass('is-secondary', 'is-success');
  expect(screen.getByText('当前筛选').closest('.v2-workspace-metric-context')).toHaveTextContent('1');
  expect(document.querySelectorAll('.v2-reconcile-kpi-card')).toHaveLength(0);
  expect(screen.queryByText('活跃问题', { exact: true })).not.toBeInTheDocument();
  expect(screen.queryByText('近 30 天趋势')).not.toBeInTheDocument();
  const trendButton = screen.getByRole('button', { name: '30 天趋势' });
  expect(trendButton).toHaveClass('v2-workspace-mobile-tool-button', 'is-muted');
  expect(screen.getByRole('button', { name: '刷新结果' })).toHaveClass('v2-workspace-mobile-tool-button', 'is-muted');
  expect(trendButton).toHaveAttribute('aria-expanded', 'false');
  fireEvent.click(trendButton);
  const trendDialog = await screen.findByRole('dialog', { name: '30 天差异趋势' });
  expect(trendDialog.closest('.semi-sidesheet')).toHaveClass('v2-workspace-detail-sidesheet', 'v2-reconcile-trend-sidesheet');
  expect(within(trendDialog).getByText('趋势明细').closest('.semi-card')).toHaveClass('v2-reconcile-trend');
  expect(within(trendDialog).getByText('趋势明细').closest('.v2-workspace-panel-header')).toHaveClass('is-compact');
  expect(within(trendDialog).getByRole('list', { name: '30 天差异趋势摘要' })).toBeInTheDocument();
  expect(trendButton).toHaveAttribute('aria-expanded', 'true');
  fireEvent.click(within(trendDialog).getByRole('button', { name: '关闭 30 天差异趋势' }));
  expect(trendButton).toHaveAttribute('aria-expanded', 'false');
  expect(document.querySelector('.v2-reconcile-table.semi-table-wrapper')).toBeInTheDocument();
  expect(screen.getByLabelText('运维质量操作')).toHaveTextContent('上海时间');
  expect(screen.getAllByText('2026-07-16 18:00:00').length).toBeGreaterThan(0);
  expect(document.querySelector('.v2-platform-time.v2-ops-time')).toBeInTheDocument();
  expect((await screen.findAllByText('多来源实时位置漂移')).length).toBeGreaterThan(0);
  expect(document.querySelector('.v2-reconcile-mobile-card.semi-card')).not.toBeInTheDocument();
  expect(document.querySelector('.v2-reconcile-table-wrap > table')).not.toBeInTheDocument();
  const reconcileRow = screen.getByTestId('reconcile-row-issue-1');
  expect(reconcileRow).toHaveAttribute('role', 'button');
  expect(reconcileRow).toHaveAttribute('aria-expanded', 'false');
  fireEvent.keyDown(reconcileRow, { key: ' ' });
  expect(await screen.findByText('规则证据')).toBeInTheDocument();
  expect(reconcileRow).toHaveAttribute('aria-expanded', 'true');
  const detailDialog = screen.getByRole('dialog', { name: '差异证据与处置' });
  expect(detailDialog.closest('.semi-sidesheet')).toHaveClass('v2-workspace-detail-sidesheet', 'v2-reconcile-detail-sidesheet');
  expect(within(detailDialog).getByRole('list', { name: '差异证据与处置摘要' })).toBeInTheDocument();
  expect(screen.getByText('规则证据').closest('.semi-card')).toHaveClass('v2-reconcile-evidence');
  expect(screen.getByText('复核结论').closest('.semi-card')).toHaveClass('v2-reconcile-review');
  expect(screen.getByText('处理履历').closest('.semi-card')).toHaveClass('v2-reconcile-actions');
  expect(screen.getByText('规则证据').closest('.v2-workspace-panel-header')).toHaveClass('is-compact');
  expect(document.querySelector('.v2-reconcile-detail-heading.v2-workspace-panel-header')).not.toBeInTheDocument();
  expect(screen.getByText('复核提示')).toBeInTheDocument();
  expect(screen.getByText('坐标差距')).toBeInTheDocument();
  expect(screen.getByText('distanceM')).toBeInTheDocument();
  expect(screen.getByText('1,286 m')).toBeInTheDocument();
  fireEvent.click(within(detailDialog).getByRole('button', { name: '关闭差异证据与处置' }));
  expect(reconcileRow).toHaveAttribute('aria-expanded', 'false');
  fireEvent.click(reconcileRow);
  expect(await screen.findByText('规则证据')).toBeInTheDocument();
  fireEvent.click(screen.getByRole('radio', { name: '采信 GB32960' }));
  fireEvent.change(screen.getByLabelText('说明（必填）'), { target: { value: '来源 A 原始报文可信' } });
  fireEvent.click(screen.getByRole('button', { name: '保存结论并记录履历' }));
  await waitFor(() => expect(mocks.updateReconciliationIssue).toHaveBeenCalledWith('issue-1', {
    version: 1, status: 'confirmed_source_a', note: '来源 A 原始报文可信'
  }));
});

test('renders only the compact mobile reconciliation surface and defers secondary controls', async () => {
  layout.mobile = true;
  seedSession();
  mocks.opsHealth.mockResolvedValue({
    linkHealth: [], kafkaLag: 0, activeConnections: 10, capacityFindings: [], redisOnlineKeys: 5,
    tdengineWritable: true, mysqlWritable: true,
    runtime: { platformRelease: 'test-release', dataMode: 'production', requestTimeoutMs: 5000, amapSecurityProxyEnabled: true, amapSecurityCodeExposed: false }
  });
  mocks.sourceReadiness.mockResolvedValue({ totalVehicles: 1, boundVehicles: 1, identityRequiredVehicles: 0, onlineVehicles: 1, sources: [] });
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  renderOperations(client);

  expect(await screen.findByText('多来源实时位置漂移')).toBeInTheDocument();
  expect(document.querySelector('.v2-reconcile-mobile-card.semi-card')).toBeInTheDocument();
  expect(screen.getByRole('region', { name: '差异队列，可上下滚动' })).toHaveAttribute('tabindex', '0');
  expect(document.querySelector('.v2-reconcile-table.semi-table-wrapper')).not.toBeInTheDocument();
  expect(mocks.reconciliationIssues).toHaveBeenCalledWith(expect.objectContaining({ limit: 20 }), expect.any(AbortSignal));
  const mobileIssueAction = screen.getByRole('button', { name: '查看 粤A00001 差异证据' });
  expect(mobileIssueAction).toHaveAttribute('aria-expanded', 'false');
  fireEvent.click(mobileIssueAction);
  expect(await screen.findByRole('dialog', { name: '差异证据与处置' })).toBeInTheDocument();
  expect(document.querySelector('.v2-reconcile-detail-sidesheet .v2-reconcile-detail.is-sheet')).toBeInTheDocument();
  expect(document.querySelector('.v2-reconcile-detail-sidesheet')).toHaveClass('semi-sidesheet-bottom');
  expect(document.querySelector('.v2-reconcile-detail-sidesheet')).toHaveClass('v2-workspace-detail-sidesheet');
  expect(document.querySelector('.v2-reconcile-detail-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(88dvh, 780px)' });
  expect(await screen.findByRole('button', { name: '关闭差异证据与处置' })).toBeInTheDocument();
  expect(mobileIssueAction).toHaveAttribute('aria-expanded', 'true');
  fireEvent.click(screen.getByRole('button', { name: '关闭差异证据与处置' }));
  expect(mobileIssueAction).toHaveAttribute('aria-expanded', 'false');

  const toolbar = document.querySelector('.v2-reconcile-toolbar');
  expect(toolbar).toHaveClass('is-mobile-collapsed');
  expect(screen.getByRole('button', { name: /修改筛选差异：活跃差异 · 1 条/ })).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: /修改筛选差异/ }));
  expect(toolbar).not.toHaveClass('is-mobile-collapsed');

  expect(screen.queryByText('近 30 天趋势')).not.toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '30 天趋势' }));
  expect(await screen.findByRole('dialog', { name: '30 天差异趋势' })).toBeInTheDocument();
  expect(document.querySelector('.v2-reconcile-trend-sidesheet')).toHaveClass('semi-sidesheet-bottom', 'v2-workspace-detail-sidesheet');
  expect(document.querySelector('.v2-reconcile-trend-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(72dvh, 620px)' });
  const trend = screen.getByText('趋势明细').closest('.v2-reconcile-trend');
  expect(trend).toBeInTheDocument();
  expect(screen.getByText(/最近运行/)).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '关闭 30 天差异趋势' }));
  expect(screen.queryByText('趋势明细')).not.toBeInTheDocument();
});

test('reconciles service identities with bound and identity-required vehicles', async () => {
  seedSession();
  mocks.opsHealth.mockResolvedValue({
    linkHealth: [{ name: 'MySQL realtime', status: 'ok', detail: '主库连接正常' }], kafkaLag: 0, activeConnections: 10, capacityFindings: [], redisOnlineKeys: 5,
    tdengineWritable: true, mysqlWritable: true,
    runtime: { platformRelease: 'test-release', dataMode: 'production', requestTimeoutMs: 5000, amapSecurityProxyEnabled: true, amapSecurityCodeExposed: false }
  });
  mocks.sourceReadiness.mockResolvedValue({
    totalVehicles: 1024, boundVehicles: 1024, identityRequiredVehicles: 11, onlineVehicles: 234,
    kafkaLag: 0, activeConnections: 10, redisOnlineKeys: 5, platformRelease: 'test-release', sources: [{
      protocol: 'GB32960', role: '仪表盘里程与整车状态', total: 1024, online: 234, severity: 'ok',
      evidence: '实时来源覆盖稳定', action: '保持在线率监测', acceptance: '在线率与上报周期持续满足阈值'
    }]
  });
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  renderOperations(client, ['/operations?view=health']);

  expect(screen.getByRole('tab', { name: '全局健康', selected: true })).toBeInTheDocument();
  expect(screen.getByTestId('operations-location')).toHaveTextContent('/operations?view=health');
  expect(await screen.findByText('1024 / 234')).toBeInTheDocument();
  expect(within(screen.getByRole('navigation', { name: '运维质量视图' })).getByText('15 秒刷新运行证据')).toBeInTheDocument();
  expect(within(screen.getByRole('navigation', { name: '运维质量视图' })).getByText('运行正常')).toBeInTheDocument();
  for (const key of [['ops-health-v2'], ['ops-source-readiness-v2']]) {
    const query = client.getQueryCache().find({ queryKey: key });
    const liveOptions = query?.options as { refetchIntervalInBackground?: boolean; refetchOnWindowFocus?: boolean };
    expect(liveOptions.refetchIntervalInBackground).toBe(false);
    expect(liveOptions.refetchOnWindowFocus).toBe(true);
  }
  expect(screen.getByText('车辆总数 / 在线')).toBeInTheDocument();
  expect(screen.getByText('协议就绪 / 总数')).toBeInTheDocument();
  expect(screen.getByText('全部协议达到当前口径')).toBeInTheDocument();
  expect(screen.getByText('已绑定 1024 · 待绑定 11')).toBeInTheDocument();
  expect(screen.getByRole('heading', { name: '运行概览', level: 5 })).toBeInTheDocument();
  expect(screen.getByText('车辆总数 / 在线').closest('[role="listitem"]')).toHaveClass('is-secondary');
  expect(screen.getByRole('list', { name: '运行健康指标' })).toHaveClass('v2-workspace-metric-list');
  expect(screen.getByText('Kafka Lag').closest('.v2-workspace-metric-context')).toHaveClass('v2-workspace-metric-context');
  expect(screen.getByText('全部正常')).toBeInTheDocument();
  expect(screen.getByText('运行时安全').closest('.semi-card')).toHaveClass('v2-ops-runtime');
  expect(screen.getByText('协议来源就绪度').closest('.semi-card')).toHaveClass('v2-ops-sources');
  expect(screen.getByRole('heading', { name: '数据链路', level: 5 })).toBeInTheDocument();
  expect(screen.getByRole('heading', { name: '运行时安全', level: 5 })).toBeInTheDocument();
  expect(screen.getByRole('heading', { name: '协议来源就绪度', level: 5 })).toBeInTheDocument();
  expect(document.querySelector('.v2-ops-link-card')).toHaveTextContent('MySQL 实时连接MySQL realtime主库连接正常');
  expect(document.querySelector('.v2-ops-link-card.semi-card')).not.toBeInTheDocument();
  expect(document.querySelector('.v2-ops-runtime-descriptions.semi-descriptions')).toBeInTheDocument();
  expect(screen.getByText('容量检查通过').closest('.semi-empty')).toHaveClass('v2-ops-capacity-empty');
  expect(document.querySelector('.v2-ops-source-list.semi-card-group')).toBeInTheDocument();
  expect(screen.getByText('GB32960').closest('.semi-card')).toHaveClass('v2-ops-source-card');
  expect(screen.getByLabelText('GB32960 在线覆盖率')).toBeInTheDocument();
  expect(screen.getByText('22.9% 在线')).toBeInTheDocument();
  expect(screen.getByText('验收标准').closest('.semi-card')).toHaveClass('v2-ops-source-card');
  expect(screen.queryByText('单车多来源诊断')).not.toBeInTheDocument();
  fireEvent.click(screen.getByRole('tab', { name: '单车诊断' }));
  expect(screen.getByTestId('operations-location')).toHaveTextContent('/operations?view=diagnostic');
  expect(within(screen.getByRole('navigation', { name: '运维质量视图' })).getByText('按需读取单车来源')).toBeInTheDocument();
  expect(within(screen.getByRole('navigation', { name: '运维质量视图' })).getByText('精确诊断')).toBeInTheDocument();
  expect(screen.getByRole('heading', { name: '诊断车辆', level: 5 })).toBeInTheDocument();
  expect(screen.getByRole('heading', { name: '诊断车辆', level: 5 }).closest('.semi-card')).toHaveClass('v2-source-filter-panel', 'v2-workspace-filter-panel');
  expect(screen.getByText('先选择一辆车').closest('.semi-empty')).toHaveClass('v2-source-empty');
  expect(screen.getByText('单车多来源诊断').closest('.semi-card')).toHaveClass('v2-source-diagnostic');
  expect(screen.queryByText('统一车辆视角')).not.toBeInTheDocument();
});

test('normalizes nullable health evidence arrays without crashing the workspace', async () => {
  seedSession();
  mocks.opsHealth.mockResolvedValue({
    linkHealth: null, kafkaLag: 0, activeConnections: 10, capacityFindings: null, redisOnlineKeys: 5,
    tdengineWritable: true, mysqlWritable: true,
    runtime: { platformRelease: 'test-release', dataMode: 'production', requestTimeoutMs: 5000, amapSecurityProxyEnabled: true, amapSecurityCodeExposed: false }
  });
  mocks.sourceReadiness.mockResolvedValue({
    totalVehicles: 1024, boundVehicles: 1024, identityRequiredVehicles: 0, onlineVehicles: 234,
    kafkaLag: 0, activeConnections: 10, redisOnlineKeys: 5, platformRelease: 'test-release', sources: null
  });
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  renderOperations(client, ['/operations?view=health']);

  expect(await screen.findByRole('heading', { name: '运行概览', level: 5 })).toBeInTheDocument();
  expect(await screen.findAllByText('暂无协议来源')).toHaveLength(2);
  expect(screen.getByText('暂无链路探针')).toBeInTheDocument();
  expect(screen.getByText('容量检查通过')).toBeInTheDocument();
  expect(screen.queryByText('当前模块暂时无法显示')).not.toBeInTheDocument();
});

test('includes protocol readiness in the global health verdict and prioritizes its evidence', async () => {
  seedSession();
  mocks.opsHealth.mockResolvedValue({
    linkHealth: [{ name: 'MySQL realtime', status: 'ok', detail: '主库连接正常' }], kafkaLag: 0, activeConnections: 10, capacityFindings: [], redisOnlineKeys: 5,
    tdengineWritable: true, mysqlWritable: true,
    runtime: { platformRelease: 'test-release', dataMode: 'production', requestTimeoutMs: 5000, amapSecurityProxyEnabled: true, amapSecurityCodeExposed: false }
  });
  mocks.sourceReadiness.mockResolvedValue({
    totalVehicles: 1024, boundVehicles: 1024, identityRequiredVehicles: 11, onlineVehicles: 234,
    sources: [
      {
        protocol: 'GB32960', role: '仪表盘里程与整车状态', total: 494, online: 123, severity: 'warning',
        status: '覆盖不足', evidence: '在线覆盖偏低', action: '核对接入范围', acceptance: '缺失车辆下降'
      },
      {
        protocol: 'YUTONG_MQTT', role: '宇通补充来源', total: 50, online: 12, severity: 'error',
        status: '链路异常', evidence: '投影停滞', action: '恢复投影与回补', acceptance: '日里程恢复'
      }
    ]
  });
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  renderOperations(client, ['/operations?view=health']);

  expect(await screen.findByText('需要处理')).toBeInTheDocument();
  expect(screen.getByText('协议覆盖 2 项 · 链路运行 0 项')).toBeInTheDocument();
  expect(screen.getByText('0 / 2')).toBeInTheDocument();
  expect(screen.getByText('2 个协议需要处置')).toBeInTheDocument();
  expect(within(screen.getByRole('navigation', { name: '运维质量视图' })).getByText('2 项关注')).toBeInTheDocument();
  const sourcePanel = screen.getByRole('heading', { name: '协议来源就绪度', level: 5 }).closest('.v2-ops-sources');
  const infrastructureGrid = document.querySelector('.v2-ops-grid');
  expect(sourcePanel).toHaveClass('has-attention');
  expect(sourcePanel).not.toBeNull();
  expect(infrastructureGrid).not.toBeNull();
  expect(Boolean(sourcePanel!.compareDocumentPosition(infrastructureGrid!) & Node.DOCUMENT_POSITION_FOLLOWING)).toBe(true);
});

test('keeps health evidence visible when source readiness fails and retries that section', async () => {
  seedSession();
  mocks.opsHealth.mockResolvedValue({
    linkHealth: [], kafkaLag: 0, activeConnections: 10, capacityFindings: [], redisOnlineKeys: 5,
    tdengineWritable: true, mysqlWritable: true,
    runtime: { platformRelease: 'test-release', dataMode: 'production', requestTimeoutMs: 5000, amapSecurityProxyEnabled: true, amapSecurityCodeExposed: false }
  });
  mocks.sourceReadiness.mockRejectedValueOnce(new Error('来源就绪度暂时不可用')).mockResolvedValueOnce({
    totalVehicles: 1024, boundVehicles: 1024, identityRequiredVehicles: 11, onlineVehicles: 234,
    kafkaLag: 0, activeConnections: 10, redisOnlineKeys: 5, platformRelease: 'test-release', sources: []
  });
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  renderOperations(client);

  fireEvent.click(screen.getByRole('tab', { name: '全局健康' }));
  expect(await screen.findByText('来源就绪度暂时不可用')).toBeInTheDocument();
  expect(screen.getByText('协议来源证据不可用')).toBeInTheDocument();
  expect(screen.getByText('证据缺失')).toBeInTheDocument();
  expect(screen.getByLabelText('运维质量操作')).toHaveTextContent('上海时间 · 版本 test-release');
  expect(screen.getByRole('heading', { name: '数据链路', level: 5 })).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: /重试/ }));

  expect(await screen.findByText('1024 / 234')).toBeInTheDocument();
  expect(screen.queryByText('来源就绪度暂时不可用')).not.toBeInTheDocument();
  expect(mocks.sourceReadiness).toHaveBeenCalledTimes(2);
});

test('fuzzy searches a vehicle and renders all source diagnosis evidence', async () => {
  seedSession();
  mocks.opsHealth.mockResolvedValue({
    linkHealth: [], kafkaLag: 0, activeConnections: 10, capacityFindings: [], redisOnlineKeys: 5,
    tdengineWritable: true, mysqlWritable: true,
    runtime: { platformRelease: 'test-release', dataMode: 'production', requestTimeoutMs: 5000, amapSecurityProxyEnabled: true, amapSecurityCodeExposed: false }
  });
  mocks.sourceReadiness.mockResolvedValue({ totalVehicles: 1, boundVehicles: 1, identityRequiredVehicles: 0, onlineVehicles: 1, sources: [] });
  mocks.vehicleCoverage.mockResolvedValue({ items: [{ vin: 'VIN001', plate: '粤A00001', protocols: ['JT808'], missingProtocols: [], sourceStatus: [], sourceCount: 2, onlineSourceCount: 2, online: true, lastSeen: '', bindingStatus: 'bound' }], total: 1, limit: 20, offset: 0 });
  const diagnostic = {
    evidence: {
      vin: 'VIN001', plate: '粤A00001', mileageDate: '2026-07-16', recommendedLocationProtocol: 'JT808',
      recommendedLocationLabel: 'G7', locationConflict: true, conflictDistanceM: 233,
      locationSources: [{
        protocol: 'JT808', sourceLabel: 'G7', providerOverride: '', terminalLabel: '终端 133****0001', sourceKind: 'PLATFORM', sourceRef: 'a'.repeat(64),
        selectedWithinProtocol: true, recommended: true, enabled: true, priority: 20, policyRemark: '保持当前优先级', online: true, qualityStatus: 'OK', qualityReason: '',
        longitude: 113.1, latitude: 23.1, speedKmh: 30, totalMileageKm: 1000, eventTime: '2026-07-16 10:00:00',
        receivedAt: '2026-07-16 10:00:01', firstSeenAt: '2026-03-01 08:00:00', reportIntervalSec: 10, reportSampleCount: 1000,
        selectionReason: '当前融合推荐'
      }],
      mileageSources: [], comparison: { locationMaxDistanceM: 233, totalMileageDeltaKm: 0, dailyMileageDeltaKm: 0, reportTimeDeltaSeconds: 0 }, asOf: ''
    },
    policy: { vin: 'VIN001', version: 1, updatedBy: 'system', updatedAt: '', audit: [] },
    recommendationReason: '当前推荐 G7', refreshHint: '下一次有效上报后重新选举'
  };
  mocks.vehicleSourceDiagnostic.mockResolvedValue(diagnostic);
  mocks.updateVehicleSourcePolicy.mockResolvedValue(diagnostic);
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  renderOperations(client);
  fireEvent.click(screen.getByRole('tab', { name: '单车诊断' }));
  fireEvent.change(screen.getByLabelText('按车牌或 VIN 搜索诊断车辆'), { target: { value: '粤A' } });
  const candidateButton = await waitFor(() => {
    const button = document.querySelector<HTMLButtonElement>('.v2-source-candidates button');
    expect(button).toBeTruthy();
    return button!;
  });
  fireEvent.click(candidateButton);
  expect(await screen.findByText('当前推荐 G7')).toBeInTheDocument();
  expect(screen.getByText('1 个来源')).toBeInTheDocument();
  expect(screen.getByRole('group', { name: '车辆来源诊断概览' })).toHaveClass('v2-source-summary-region');
  const sourceSummary = screen.getByRole('list', { name: '车辆来源诊断概览' });
  expect(sourceSummary.closest('.v2-source-summary-rail')).toHaveClass('v2-workspace-metric-rail', 'is-queue');
  expect(sourceSummary.querySelectorAll(':scope > [role="listitem"]')).toHaveLength(4);
  expect(sourceSummary).toHaveTextContent('来源状态1 / 1位置冲突 233m');
  expect(sourceSummary).toHaveTextContent('当前推荐G7JT808');
  expect(sourceSummary).toHaveTextContent('车辆粤A00001VIN001');
  expect(sourceSummary).toHaveTextContent('策略版本v1尚无人工调整');
  expect(sourceSummary.querySelector(':scope > [role="listitem"]')).toHaveClass('is-warning', 'is-primary');
  expect(screen.getByText('推荐说明').closest('.semi-card')).toHaveClass('v2-source-recommendation');
  expect(screen.getByText('最近策略审计').closest('.semi-card')).toHaveClass('v2-source-audit');
  expect(screen.getByText('终端 133****0001')).toBeInTheDocument();
  expect(screen.getByText('10s')).toBeInTheDocument();
  expect(document.querySelector('.v2-source-table.semi-table-wrapper')).toBeInTheDocument();
  expect(document.querySelector('.v2-source-table-wrap > table')).not.toBeInTheDocument();
  expect(document.querySelector('.v2-source-mobile-list')).not.toBeInTheDocument();
  expect(screen.getByRole('columnheader', { name: '来源 / 终端' })).toBeInTheDocument();
  expect(screen.getByRole('columnheader', { name: '运维策略' })).toBeInTheDocument();
  expect(screen.getByText('优先级 20')).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '维护策略' }));
  expect(await screen.findByRole('dialog', { name: '车辆来源策略' })).toBeInTheDocument();
  expect(document.querySelector('.v2-source-policy-sidesheet .semi-sidesheet-inner')).toBeInTheDocument();
  fireEvent.change(screen.getByLabelText('G7 提供方'), { target: { value: 'G7s' } });
  const save = screen.getByRole('button', { name: '保存策略' });
  expect(save).toBeDisabled();
  expect(screen.getByLabelText('G7 策略备注')).toHaveValue('保持当前优先级');
  fireEvent.change(screen.getByLabelText('G7 提供方核验依据'), { target: { value: 'GPS 运维终端清单 2026-07-16' } });
  fireEvent.click(save);
  await waitFor(() => expect(mocks.updateVehicleSourcePolicy).toHaveBeenCalledWith('VIN001', {
    version: 1,
    sourceRef: 'a'.repeat(64),
    providerName: 'G7s',
    providerEvidence: 'GPS 运维终端清单 2026-07-16',
    enabled: true,
    priority: 20,
    remark: '保持当前优先级'
  }));
  await waitFor(() => expect(mocks.vehicleSourceDiagnostic).toHaveBeenCalledWith('VIN001', expect.any(AbortSignal)));
});

test('allows provider maintenance but keeps canonical source policy read only', async () => {
  layout.mobile = true;
  seedSession();
  mocks.opsHealth.mockResolvedValue({
    linkHealth: [], kafkaLag: 0, activeConnections: 10, capacityFindings: [], redisOnlineKeys: 5,
    tdengineWritable: true, mysqlWritable: true,
    runtime: { platformRelease: 'test-release', dataMode: 'production', requestTimeoutMs: 5000, amapSecurityProxyEnabled: true, amapSecurityCodeExposed: false }
  });
  mocks.sourceReadiness.mockResolvedValue({ totalVehicles: 1, boundVehicles: 1, identityRequiredVehicles: 0, onlineVehicles: 1, sources: [] });
  mocks.vehicleCoverage.mockResolvedValue({ items: [{ vin: 'VIN-CANONICAL', plate: '沪A00001', protocols: ['JT808'], missingProtocols: [], sourceStatus: [], sourceCount: 1, onlineSourceCount: 1, online: true, lastSeen: '', bindingStatus: 'bound' }], total: 1, limit: 20, offset: 0 });
  const diagnostic = {
    evidence: {
      vin: 'VIN-CANONICAL', plate: '沪A00001', mileageDate: '', recommendedLocationProtocol: 'JT808',
      recommendedLocationLabel: 'JT808', locationConflict: false,
      locationSources: [{
        protocol: 'JT808', sourceLabel: 'JT808', providerOverride: '', terminalLabel: '', sourceKind: 'CANONICAL', sourceRef: 'b'.repeat(64),
        selectedWithinProtocol: true, recommended: true, enabled: true, priority: 100, policyRemark: '', online: true, qualityStatus: 'OK', qualityReason: '',
        longitude: 113.1, latitude: 23.1, eventTime: '2026-07-16 10:00:00', receivedAt: '2026-07-16 10:00:01',
        selectionReason: '协议融合快照只能维护提供方'
      }],
      mileageSources: [], comparison: { locationMaxDistanceM: 0, totalMileageDeltaKm: 0, dailyMileageDeltaKm: 0, reportTimeDeltaSeconds: 0 }, asOf: ''
    },
    policy: { vin: 'VIN-CANONICAL', version: 1, updatedBy: 'system', updatedAt: '', audit: [] },
    recommendationReason: '当前推荐 JT808 的协议融合结果', refreshHint: '等待下一次上报'
  };
  mocks.vehicleSourceDiagnostic.mockResolvedValue(diagnostic);
  mocks.updateVehicleSourcePolicy.mockResolvedValue(diagnostic);
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  renderOperations(client);
  fireEvent.click(screen.getByRole('tab', { name: '单车诊断' }));
  const filterToggle = screen.getByRole('button', { name: '修改诊断车辆：尚未选择车辆' });
  expect(filterToggle).toHaveAttribute('aria-expanded', 'false');
  fireEvent.click(filterToggle);
  const filterDialog = await screen.findByRole('dialog', { name: '诊断车辆筛选' });
  expect(document.querySelector('.v2-source-mobile-filter-sidesheet')).toHaveClass('semi-sidesheet-bottom');
  expect(document.querySelector('.v2-source-mobile-filter-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(78dvh, 660px)' });
  expect(within(filterDialog).getByText('输入车牌或 VIN 开始查找')).toBeInTheDocument();
  fireEvent.change(within(filterDialog).getByLabelText('按车牌或 VIN 搜索诊断车辆'), { target: { value: '沪A' } });
  const candidateButton = await waitFor(() => {
    const button = filterDialog.querySelector<HTMLButtonElement>('.v2-source-candidates button');
    expect(button).toBeTruthy();
    return button!;
  });
  fireEvent.click(candidateButton);

  expect(await screen.findByText('协议融合快照只能维护提供方')).toBeInTheDocument();
  const selectedToggle = screen.getByRole('button', { name: '修改诊断车辆：已选 沪A00001' });
  expect(selectedToggle).toHaveAttribute('aria-expanded', 'false');
  expect(filterDialog).toHaveClass('semi-sidesheet-animation-content_hide_bottom');
  fireEvent.click(selectedToggle);
  const selectedSearch = within(filterDialog).getByLabelText('按车牌或 VIN 搜索诊断车辆');
  expect(selectedSearch).toHaveValue('沪A00001');
  fireEvent.change(selectedSearch, { target: { value: '其他车辆' } });
  fireEvent.click(within(filterDialog).getByRole('button', { name: '关闭诊断车辆筛选' }));
  fireEvent.click(selectedToggle);
  expect(selectedSearch).toHaveValue('沪A00001');
  fireEvent.click(within(filterDialog).getByRole('button', { name: '关闭诊断车辆筛选' }));
  expect(document.querySelector('.v2-source-table')).not.toBeInTheDocument();
  expect(document.querySelector('.v2-source-mobile-card.semi-card')).toBeInTheDocument();
  expect(document.querySelector('.v2-source-mobile-descriptions.semi-descriptions')).toBeInTheDocument();
  expect(screen.getByRole('group', { name: '车辆来源诊断概览' })).toHaveClass('v2-source-summary-region');
  expect(screen.getByRole('list', { name: '车辆来源诊断概览' }).closest('.v2-source-summary-rail')).toHaveClass('v2-workspace-metric-rail', 'is-queue');
  expect(screen.getByRole('list', { name: '车辆来源诊断概览' }).querySelectorAll(':scope > [role="listitem"]')).toHaveLength(4);
  expect(screen.getByText('优先级 100')).toBeInTheDocument();
  expect(screen.queryByLabelText('JT808 优先级')).not.toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '维护提供方' }));
  expect(await screen.findByRole('dialog', { name: '车辆来源策略' })).toBeInTheDocument();
  expect(await screen.findByLabelText('JT808 优先级')).toBeDisabled();
  expect(screen.getByLabelText('JT808 策略备注')).toBeDisabled();
  expect(screen.getByRole('checkbox', { name: '启用' })).toBeDisabled();
  expect(screen.getByLabelText('JT808 提供方')).toBeEnabled();
  fireEvent.change(screen.getByLabelText('JT808 提供方'), { target: { value: '东方北斗' } });
  fireEvent.change(screen.getByLabelText('JT808 提供方核验依据'), { target: { value: 'GPS 运维终端清单 2026-07-16' } });
  fireEvent.click(screen.getByRole('button', { name: '保存提供方' }));
  await waitFor(() => expect(mocks.updateVehicleSourcePolicy).toHaveBeenCalledWith('VIN-CANONICAL', {
    version: 1,
    sourceRef: 'b'.repeat(64),
    providerName: '东方北斗',
    providerEvidence: 'GPS 运维终端清单 2026-07-16',
    enabled: true,
    priority: 100,
    remark: ''
  }));
});
