import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { afterEach, expect, test, vi } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { VehicleRealtimeRow } from '../../api/types';
import { api } from '../../api/client';
import MonitorPage from './MonitorPage';
import { ROUTER_FUTURE } from '../routing/routerConfig';

const vehicles = [{
  vin: 'LTEST000000000001', plate: '粤A12345', protocols: ['JT808'], primaryProtocol: 'JT808',
  locationAvailable: true, longitude: 113.26, latitude: 23.13, speedAvailable: true, speedKmh: 42,
  socAvailable: false, socPercent: 0, mileageAvailable: true, totalMileageKm: 1234, todayMileageAvailable: true, todayMileageKm: 18.6,
  lastSeen: '2026-07-14T01:00:00Z', online: true, sourceCount: 1, onlineSourceCount: 1
}, {
  vin: 'LTEST000000000002', plate: '粤B67890', protocols: ['JT808'], primaryProtocol: 'JT808',
  locationAvailable: true, longitude: 113.28, latitude: 23.15, speedAvailable: true, speedKmh: 0,
  socAvailable: false, socPercent: 0, mileageAvailable: true, totalMileageKm: 2234, todayMileageAvailable: false, todayMileageKm: 0,
  lastSeen: '2026-07-14T01:00:00Z', online: true, sourceCount: 1, onlineSourceCount: 1
}] as VehicleRealtimeRow[];
const monitorMap = { clusters: [], points: [], total: 2 };
const fleetMapRenderSpy = vi.hoisted(() => vi.fn());
const vehicleCardArgsSpy = vi.hoisted(() => vi.fn());
const monitorDataArgsSpy = vi.hoisted(() => vi.fn());
const qrToDataURLSpy = vi.hoisted(() => vi.fn());
const monitorQueryFlags = vi.hoisted(() => ({ isLoading: false, isFetching: false, isPlaceholderData: false }));
const vehicleCardFixture = vi.hoisted(() => ({ detail: undefined as unknown }));
const originalClipboard = Object.getOwnPropertyDescriptor(navigator, 'clipboard');

vi.mock('../map/FleetMap', () => ({
  FleetMap: ({ selectedVin, onSelectVin, initialViewport }: { selectedVin?: string; onSelectVin?: (vin: string) => void; initialViewport?: { zoom: number; bounds: string } }) => {
    fleetMapRenderSpy(selectedVin, initialViewport);
    return <div data-testid="fleet-map" data-selected-vin={selectedVin ?? ''} data-initial-zoom={initialViewport?.zoom ?? ''} data-initial-bounds={initialViewport?.bounds ?? ''}>
      <button type="button" onClick={() => onSelectVin?.('LTEST000000000002')}>选择地图车辆</button>
    </div>;
  }
}));

vi.mock('qrcode', () => ({ default: { toDataURL: qrToDataURLSpy } }));

vi.mock('../hooks/useMonitorData', () => ({
  MAX_MONITOR_SEARCH_TERMS: 100,
  MONITOR_REFRESH: { selected: 10_000, fleet: 15_000, summary: 30_000 },
  parseMonitorSearchTerms: (value: string) => Array.from(new Set(value.split(/[\s,，、;；]+/).map((item) => item.trim().toLocaleUpperCase()).filter(Boolean))).slice(0, 100),
  monitorQueryParams: (filters: { keyword: string; protocol: string; status: string }, limit: number) => {
    const params = new URLSearchParams({ limit: String(limit) });
    const terms = Array.from(new Set(filters.keyword.split(/[\s,，、;；]+/).map((item) => item.trim().toLocaleUpperCase()).filter(Boolean))).slice(0, 100);
    if (terms.length === 1) params.set('keyword', terms[0]);
    if (terms.length > 1) params.set('keywords', terms.join(','));
    if (filters.protocol) params.set('protocol', filters.protocol);
    if (filters.status) params.set('status', filters.status);
    return params;
  },
  monitorFilterScope: (filters: { keyword: string; protocol: string; status: string }) => {
    const params = new URLSearchParams();
    if (filters.keyword) params.set('keyword', filters.keyword);
    if (filters.protocol) params.set('protocol', filters.protocol);
    if (filters.status) params.set('status', filters.status);
    return params.toString();
  },
  useMonitorData: (...args: unknown[]) => {
    monitorDataArgsSpy(...args);
    return {
      summary: { data: { totalVehicles: 2, locationVehicles: 2, noLocationVehicles: 0, onlineVehicles: 2, offlineVehicles: 0, drivingVehicles: 1, idleVehicles: 1, frameToday: 10 } },
      vehicles: { data: { items: vehicles, total: 2 }, isError: false, ...monitorQueryFlags },
      map: { data: monitorMap, isPlaceholderData: monitorQueryFlags.isPlaceholderData },
      selectedVehicle: { data: { items: [] } }
    };
  },
  useMonitorVehicleCard: (...args: unknown[]) => {
    vehicleCardArgsSpy(...args);
    return { detail: { data: vehicleCardFixture.detail }, activeAlerts: {}, address: {} };
  }
}));

afterEach(() => {
  cleanup();
  monitorQueryFlags.isLoading = false;
  monitorQueryFlags.isFetching = false;
  monitorQueryFlags.isPlaceholderData = false;
  vehicleCardFixture.detail = undefined;
  vehicleCardArgsSpy.mockClear();
  monitorDataArgsSpy.mockClear();
  qrToDataURLSpy.mockReset();
  if (originalClipboard) Object.defineProperty(navigator, 'clipboard', originalClipboard);
  else Reflect.deleteProperty(navigator, 'clipboard');
  vi.restoreAllMocks();
});

test('never labels the latest historical mileage row as today when today has no mileage evidence', async () => {
  vehicleCardFixture.detail = {
    sources: ['JT808'],
    sourceStatus: [],
    mileage: {
      items: [{ vin: vehicles[1].vin, plate: vehicles[1].plate, date: '2026-07-16', startMileageKm: 2100, endMileageKm: 2234, dailyMileageKm: 134, source: 'JT808' }],
      total: 1,
      limit: 20,
      offset: 0
    }
  };
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  fireEvent.click(screen.getByRole('button', { name: /粤B67890 LTEST000000000002/ }));

  const todayMetric = (await screen.findByText('今日里程')).parentElement;
  expect(todayMetric).toHaveTextContent('今日里程—');
  expect(todayMetric).not.toHaveTextContent('134');
});

test('starts without a selection and supports expand, collapse, reselection, and clear', async () => {
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  const view = render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);
  const workspace = view.container.querySelector('.v2-monitor-workspace')!;
  const monitorPage = view.container.querySelector('.v2-monitor-page')!;
  const firstVehicle = screen.getByRole('button', { name: /粤A12345 LTEST000000000001/ });
  const secondVehicle = screen.getByRole('button', { name: /粤B67890 LTEST000000000002/ });

  expect(firstVehicle).toHaveClass('semi-button', 'v2-vehicle-row');
  expect(view.container.querySelector('.v2-filterbar')).toHaveClass('semi-card');
  const kpis = screen.getByRole('list', { name: '车辆整体统计' });
  expect(kpis.closest('.v2-monitor-summary-rail')).toHaveClass('semi-card', 'v2-workspace-metric-rail', 'is-queue');
  expect(Array.from(kpis.querySelectorAll(':scope > [role="listitem"] > small')).map((item) => item.textContent)).toEqual([
    '车辆总数', '当前在线', '行驶车辆', '当前离线', '静止车辆', '告警车辆'
  ]);
  expect(kpis.querySelectorAll(':scope > [role="listitem"]')).toHaveLength(6);
  expect(kpis.querySelector(':scope > [role="listitem"]:nth-child(1)')).toHaveClass('is-primary');
  expect(kpis.querySelector(':scope > [role="listitem"]:nth-child(2)')).toHaveClass('is-primary', 'is-success');
  expect(kpis.querySelector(':scope > [role="listitem"]:nth-child(4)')).toHaveClass('is-secondary');
  const summarySupport = view.container.querySelector('.v2-monitor-summary-support');
  expect(summarySupport).toHaveTextContent('今日上报10条数据活跃度');
  expect(summarySupport).toHaveTextContent('无实时位置0辆辅助排查');
  expect(view.container.querySelector('.v2-kpis')).not.toBeInTheDocument();
  expect(view.container.querySelector('.v2-event-strip')).toHaveClass('semi-card');
  expect(view.container.querySelector('.v2-monitor-live-tag')).toHaveClass('semi-tag');
  const liveStatus = screen.getByLabelText('实时数据状态');
  expect(liveStatus).toHaveTextContent('数据已同步');
  expect(liveStatus).toHaveTextContent('实时数据2 辆已载入');
  expect(liveStatus).toHaveTextContent('地图载荷0 个车辆点');
  expect(liveStatus).toHaveTextContent('智能刷新重点 10s · 车队 15s · 统计 30s');
  expect(liveStatus).toHaveTextContent('最近同步等待数据');
  expect(workspace).not.toHaveClass('is-detail-open');
  expect(workspace).not.toHaveClass('is-detail-collapsed');
  expect(monitorPage).toHaveClass('is-map-mode');
  expect(firstVehicle).not.toHaveClass('is-selected');
  expect(screen.queryByRole('button', { name: '取消选择车辆' })).not.toBeInTheDocument();
  expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-selected-vin', '');
  expect(screen.getByText('车辆总数')).toBeInTheDocument();
  expect(screen.getByText('无实时位置')).toBeInTheDocument();
  expect(screen.getByRole('status', { name: '搜索和筛选条件修改后自动生效，已启用 0 个条件' })).toHaveTextContent('自动生效');
  expect(screen.getByRole('button', { name: /清空/ })).toBeDisabled();
  expect(screen.queryByRole('button', { name: '筛选' })).not.toBeInTheDocument();
  expect(screen.queryByText('接入车辆')).not.toBeInTheDocument();

  fireEvent.click(firstVehicle);
  expect(await screen.findByRole('region', { name: '粤A12345车辆详情' })).toBeInTheDocument();
  expect(workspace).toHaveClass('is-detail-open');
  expect(monitorPage).toHaveClass('is-map-mode');
  expect(firstVehicle).toHaveClass('is-selected');
  expect(screen.getByRole('button', { name: '取消选择车辆' })).toBeInTheDocument();
  expect(view.container.querySelector('.v2-vehicle-detail')).toHaveClass('semi-card');
  expect(screen.getByRole('region', { name: '粤A12345车辆详情' })).toBeInTheDocument();
  expect(screen.getByRole('navigation', { name: '车辆快捷操作' })).toBeInTheDocument();
  for (const name of ['最新上报', '实时状态', '车辆信息']) {
    expect(screen.getByRole('heading', { name, level: 5 })).toBeInTheDocument();
  }
  expect(screen.getByText('身份、车型与来源覆盖')).toBeInTheDocument();
  expect(screen.getByText('1/1 在线').closest('.semi-tag')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-report-summary')).toHaveTextContent('平台接收时间');
  expect(view.container.querySelector('.v2-report-summary')).toHaveTextContent('主协议JT/T 808');
  expect(view.container.querySelector('.v2-report-summary .v2-protocol-tag')).toHaveClass('semi-tag-cyan-light');
  expect(view.container.querySelectorAll('.v2-detail-actions a')).toHaveLength(4);
  expect(Array.from(view.container.querySelectorAll('.v2-detail-actions a')).map((item) => item.textContent)).toEqual([
    '单车详情', '轨迹回放', '历史数据', '里程查询'
  ]);
  expect(view.container.querySelector('.v2-detail-protocols .v2-protocol-tag')).toHaveTextContent('JT/T 808');
  expect(screen.getByRole('list', { name: '车辆实时状态指标' })).toHaveClass('v2-workspace-metric-list');
  expect(view.container.querySelector('.v2-monitor-detail-metric-rail')).toHaveClass('semi-card', 'v2-workspace-metric-rail', 'is-queue');
  expect(view.container.querySelectorAll('.v2-monitor-detail-metric-rail .v2-workspace-metric-list > [role="listitem"]')).toHaveLength(4);
  expect(view.container.querySelector('.v2-monitor-detail-metric-rail .v2-workspace-metric-list > span:first-child')).toHaveTextContent('速度42');
  expect(view.container.querySelector('.v2-monitor-detail-metric-rail .v2-workspace-metric-list > span:nth-child(2)')).toHaveTextContent('今日里程18.6km');
  expect(view.container.querySelector('.v2-monitor-detail-state')).toHaveTextContent('车辆状态行驶当前告警0条');
  expect(view.container.querySelector('.v2-status-text')).toHaveClass('semi-tag');
  expect(screen.getByRole('button', { name: '取消选择车辆' })).toHaveClass('semi-button');
  expect(screen.getByRole('button', { name: '查看总里程全部来源' })).toHaveClass('semi-button', 'v2-workspace-metric-action');
  expect(screen.getByRole('button', { name: '查看全部位置来源' })).toHaveClass('semi-button', 'v2-detail-source-link');
  expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-selected-vin', 'LTEST000000000001');
  expect(vehicleCardArgsSpy).toHaveBeenLastCalledWith('LTEST000000000001', vehicles[0], true);
  expect(screen.getByText('SOC').parentElement).toHaveTextContent('SOC—');
  const mapRendersAfterSelection = fleetMapRenderSpy.mock.calls.length;
  const cardCallsAfterSelection = vehicleCardArgsSpy.mock.calls.length;

  fireEvent.click(screen.getByRole('button', { name: '收起车辆详情' }));
  expect(workspace).toHaveClass('is-detail-collapsed');
  expect(firstVehicle).toHaveClass('is-selected');
  expect(screen.getByRole('button', { name: '展开车辆详情' })).toBeInTheDocument();
  expect(view.container.querySelector('.v2-detail-peek')).toHaveClass('semi-card');
  expect(fleetMapRenderSpy).toHaveBeenCalledTimes(mapRendersAfterSelection);
  expect(vehicleCardArgsSpy).toHaveBeenCalledTimes(cardCallsAfterSelection);

  fireEvent.click(secondVehicle);
  expect(workspace).toHaveClass('is-detail-open');
  expect(secondVehicle).toHaveClass('is-selected');
  expect(screen.queryByRole('button', { name: '展开车辆详情' })).not.toBeInTheDocument();

  fireEvent.click(screen.getByRole('button', { name: '取消选择车辆' }));
  expect(workspace).not.toHaveClass('is-detail-open');
  expect(workspace).not.toHaveClass('is-detail-collapsed');
  expect(secondVehicle).not.toHaveClass('is-selected');
  expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-selected-vin', '');

  fireEvent.click(screen.getByRole('button', { name: '选择地图车辆' }));
  expect(workspace).toHaveClass('is-detail-open');
  expect(secondVehicle).toHaveClass('is-selected');
});

test('returns the monitor scroll owner to the command bar when changing views', async () => {
  vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: vehicles, total: 2, limit: 50, offset: 0 });
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  const view = render(
    <main className="v2-content" data-testid="monitor-scroll-owner">
      <QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>
    </main>
  );
  const scrollOwner = screen.getByTestId('monitor-scroll-owner');
  const monitorPage = view.container.querySelector('.v2-monitor-page')!;

  scrollOwner.scrollTop = 180;
  fireEvent.click(screen.getByRole('tab', { name: /列表/ }));
  expect(scrollOwner.scrollTop).toBe(0);
  expect(monitorPage).toHaveClass('is-list-mode');

  scrollOwner.scrollTop = 120;
  fireEvent.click(screen.getByRole('tab', { name: /地图/ }));
  expect(scrollOwner.scrollTop).toBe(0);
  expect(monitorPage).toHaveClass('is-map-mode');
});

test('restores URL-backed monitor context and carries it into every vehicle workflow', async () => {
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  const entry = '/monitor?keyword=%E7%B2%A4A12345&protocol=JT808&status=online&selectedVin=LTEST000000000001&detail=collapsed&zoom=13&bounds=113.100000%2C23.000000%2C113.400000%2C23.300000';
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE} initialEntries={[entry]}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  expect(screen.getByRole('textbox', { name: '搜索车辆' })).toHaveValue('粤A12345');
  expect(screen.getByRole('combobox', { name: '协议' })).toHaveTextContent('JT808');
  expect(screen.getByRole('combobox', { name: '在线状态' })).toHaveTextContent('在线');
  expect(screen.getByRole('button', { name: '展开车辆详情' })).toBeInTheDocument();
  expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-initial-zoom', '13');
  expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-initial-bounds', '113.100000,23.000000,113.400000,23.300000');

  fireEvent.click(screen.getByRole('button', { name: '展开车辆详情' }));
  expect(await screen.findByRole('navigation', { name: '车辆快捷操作' })).toBeInTheDocument();
  for (const name of ['单车详情', '轨迹回放', '历史数据', '里程查询']) {
    const target = new URL(screen.getByRole('link', { name }).getAttribute('href')!, 'https://vehicle-platform.invalid');
    const monitorReturn = target.searchParams.get('monitorReturn');
    expect(monitorReturn).toContain('/monitor?');
    expect(monitorReturn).toContain('selectedVin=LTEST000000000001');
    expect(monitorReturn).toContain('zoom=13');
  }
});

test('shows active filter count and clears all filter fields in one action', () => {
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/monitor?keyword=%E7%B2%A4A12345&protocol=JT808&status=online']}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  expect(screen.getByRole('status', { name: '搜索和筛选条件修改后自动生效，已启用 3 个条件' })).toHaveTextContent('自动生效3');
  const clear = screen.getByRole('button', { name: /清空/ });
  expect(clear).toBeEnabled();

  fireEvent.click(clear);

  expect(screen.getByRole('textbox', { name: '搜索车辆' })).toHaveValue('');
  expect(screen.getByRole('combobox', { name: '协议' })).toHaveTextContent('全部协议');
  expect(screen.getByRole('combobox', { name: '在线状态' })).toHaveTextContent('全部状态');
  expect(screen.getByRole('status', { name: '搜索和筛选条件修改后自动生效，已启用 0 个条件' })).toHaveTextContent('自动生效');
  expect(clear).toBeDisabled();
});

test('switches to a lightweight realtime list and resolves addresses only on demand', async () => {
  const row = vehicles[0];
  vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [row], total: 1, limit: 50, offset: 0 });
  const reverseGeocode = vi.spyOn(api, 'reverseGeocode').mockResolvedValue({ provider: 'AMap', longitude: 113.26, latitude: 23.13, formattedAddress: '广东省广州市天河区测试路' });
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  const view = render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);
  const modeGroup = screen.getByRole('tablist', { name: '监控视图' });
  const mapMode = within(modeGroup).getByRole('tab', { name: /地图/ });
  const listMode = within(modeGroup).getByRole('tab', { name: /列表/ });
  expect(mapMode).toHaveAttribute('aria-selected', 'true');
  expect(mapMode).toHaveClass('is-active');
  expect(listMode).toHaveAttribute('aria-selected', 'false');
  fireEvent.click(listMode);
  expect(mapMode).toHaveAttribute('aria-selected', 'false');
  expect(listMode).toHaveAttribute('aria-selected', 'true');
  expect(listMode).toHaveClass('is-active');
  expect(await screen.findByText('车辆实时列表')).toBeInTheDocument();
  expect(screen.getByRole('heading', { name: '车辆实时列表', level: 5 })).toBeInTheDocument();
  expect(view.container.querySelector('.v2-monitor-table-panel')).toHaveClass('semi-card');
  expect(view.container.querySelector('.v2-monitor-table.semi-table-wrapper')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-monitor-table-scroll > table')).not.toBeInTheDocument();
  expect(screen.getByText('覆盖全部授权车辆；缺失值显示“—”，地址按需解析')).toBeInTheDocument();
  expect(await screen.findAllByText('粤A12345')).toHaveLength(1);
  expect(screen.getAllByText('42')).toHaveLength(1);
  expect(screen.getAllByText(/18\.6/)).toHaveLength(1);
  expect(screen.getAllByText(/1,234/)).toHaveLength(1);
  expect(screen.getByText('JT/T 808')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-monitor-protocol .v2-protocol-tag')).toHaveClass('semi-tag-cyan-light');
  expect(screen.getAllByText(/113\.260000/)).toHaveLength(1);
  expect(screen.queryByText('推荐来源')).not.toBeInTheDocument();
  expect(screen.getByText('当日里程')).toBeInTheDocument();
  expect(screen.getByText('协议来源')).toBeInTheDocument();
  expect(screen.queryByText('最新上报')).not.toBeInTheDocument();
  expect(reverseGeocode).not.toHaveBeenCalled();

  expect(screen.getByRole('button', { name: '解析粤A12345位置' })).toHaveClass('semi-button', 'v2-monitor-address-action');
  fireEvent.click(screen.getByRole('button', { name: '解析粤A12345位置' }));
  expect(await screen.findByText('广东省广州市天河区测试路')).toBeInTheDocument();
  expect(screen.getByText(/解析于 \d{2}:\d{2}/)).toBeInTheDocument();
  expect(reverseGeocode).toHaveBeenCalledTimes(1);
  expect(queryClient.getQueryCache().findAll({ queryKey: ['monitor', 'list-address'] })).toHaveLength(1);
  expect(screen.queryByText('综合状态')).not.toBeInTheDocument();
  expect(screen.queryByText('三路正常')).not.toBeInTheDocument();
  expect(screen.queryByTestId('fleet-map')).not.toBeInTheDocument();

  fireEvent.click(screen.getByRole('tab', { name: /地图/ }));
  await waitFor(() => expect(queryClient.getQueryCache().findAll({ queryKey: ['monitor', 'list-address'] })).toHaveLength(0));
  view.unmount();
});

test('keeps authorized vehicles without realtime coordinates in the list without geocoding them', async () => {
  const row = {
    ...vehicles[0],
    vin: 'LNOLOCATION0000001',
    plate: '粤A无定位',
    protocols: [],
    primaryProtocol: '',
    locationAvailable: false,
    longitude: 0,
    latitude: 0,
    speedAvailable: false,
    speedKmh: 0,
    mileageAvailable: false,
    totalMileageKm: 0,
    todayMileageAvailable: false,
    todayMileageKm: 0,
    lastSeen: '',
    online: false,
    sourceCount: 0,
    onlineSourceCount: 0
  } satisfies VehicleRealtimeRow;
  vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [row], total: 1, limit: 50, offset: 0 });
  const reverseGeocode = vi.spyOn(api, 'reverseGeocode');
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  fireEvent.click(screen.getByRole('tab', { name: /列表/ }));

  expect(await screen.findByText('粤A无定位')).toBeInTheDocument();
  expect(screen.getByText('暂无实时位置')).toBeInTheDocument();
  expect(screen.queryByRole('button', { name: /解析粤A无定位位置/ })).not.toBeInTheDocument();
  expect(reverseGeocode).not.toHaveBeenCalled();
});

test('pauses selected-vehicle polling in list mode and resumes it when returning to the map', () => {
  vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: vehicles, total: 2, limit: 50, offset: 0 });
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  fireEvent.click(screen.getByRole('button', { name: /粤A12345 LTEST000000000001/ }));
  expect(monitorDataArgsSpy).toHaveBeenLastCalledWith(expect.any(Object), expect.any(Object), 'LTEST000000000001', true, true);

  fireEvent.click(screen.getByRole('tab', { name: /列表/ }));
  expect(monitorDataArgsSpy).toHaveBeenLastCalledWith(expect.any(Object), expect.any(Object), '', false, false);

  fireEvent.click(screen.getByRole('tab', { name: /地图/ }));
  expect(monitorDataArgsSpy).toHaveBeenLastCalledWith(expect.any(Object), expect.any(Object), 'LTEST000000000001', true, true);
});

test('loads the QR generator only after opening the mobile entry', async () => {
  qrToDataURLSpy.mockResolvedValue('data:image/png;base64,vehicle-monitor');
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  expect(qrToDataURLSpy).not.toHaveBeenCalled();
  fireEvent.click(screen.getByRole('button', { name: /手机端/ }));

  expect(await screen.findByRole('img', { name: '全局监控手机端二维码' })).toHaveAttribute('src', 'data:image/png;base64,vehicle-monitor');
  expect(qrToDataURLSpy).toHaveBeenCalledTimes(1);
  expect(qrToDataURLSpy.mock.calls[0][0]).toMatch(/\/monitor$/);
  expect(qrToDataURLSpy.mock.calls[0][0]).not.toContain('token');
});

test('closes the mobile entry with Escape and reports clipboard success', async () => {
  qrToDataURLSpy.mockResolvedValue('data:image/png;base64,vehicle-monitor');
  const writeText = vi.fn().mockResolvedValue(undefined);
  Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText } });
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  fireEvent.click(screen.getByRole('button', { name: /手机端/ }));
  expect(await screen.findByRole('dialog', { name: '手机端全局监控' })).toBeInTheDocument();
  expect(screen.getByRole('button', { name: '关闭手机端入口' })).toBeInTheDocument();
  expect(await screen.findByRole('img', { name: '全局监控手机端二维码' })).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '复制访问地址' }));
  expect(await screen.findByRole('button', { name: '已复制访问地址' })).toBeInTheDocument();
  expect(writeText).toHaveBeenCalledWith(expect.stringMatching(/\/monitor$/));

  fireEvent.keyDown(document.body, { key: 'Escape', code: 'Escape', keyCode: 27 });
  await waitFor(() => expect(screen.queryByRole('dialog', { name: '手机端全局监控' })).not.toBeInTheDocument());
});

test('shows a retry action when on-demand QR generation fails', async () => {
  qrToDataURLSpy.mockRejectedValueOnce(new Error('canvas unavailable')).mockResolvedValue('data:image/png;base64,recovered');
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  fireEvent.click(screen.getByRole('button', { name: /手机端/ }));
  expect(await screen.findByRole('alert')).toHaveTextContent('二维码生成失败');
  fireEvent.click(screen.getByRole('button', { name: '重新生成' }));

  expect(await screen.findByRole('img', { name: '全局监控手机端二维码' })).toHaveAttribute('src', 'data:image/png;base64,recovered');
  expect(qrToDataURLSpy).toHaveBeenCalledTimes(2);
});

test('mounts only the mobile list representation and removes its viewport listener', async () => {
  const addEventListener = vi.fn();
  const removeEventListener = vi.fn();
  vi.spyOn(window, 'matchMedia').mockReturnValue({
    matches: true,
    media: '(max-width: 760px)',
    onchange: null,
    addEventListener,
    removeEventListener,
    addListener: vi.fn(),
    removeListener: vi.fn(),
    dispatchEvent: vi.fn(() => false)
  } as unknown as MediaQueryList);
  vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [vehicles[0]], total: 1, limit: 50, offset: 0 });
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  const view = render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  const filterFields = view.container.querySelector('#monitor-filter-fields');
  expect(filterFields).toHaveClass('is-mobile-collapsed');
  const expandFilters = screen.getByRole('button', { name: '展开车辆筛选，当前 0 个条件' });
  expect(expandFilters).toHaveAttribute('aria-expanded', 'false');
  fireEvent.click(expandFilters);
  expect(filterFields).not.toHaveClass('is-mobile-collapsed');
  const collapseFilters = screen.getByRole('button', { name: '收起车辆筛选，当前 0 个条件' });
  expect(collapseFilters).toHaveAttribute('aria-expanded', 'true');
  fireEvent.click(collapseFilters);
  expect(filterFields).toHaveClass('is-mobile-collapsed');

  fireEvent.click(screen.getByRole('tab', { name: /列表/ }));
  expect(await screen.findByText('车辆实时列表')).toBeInTheDocument();
  await waitFor(() => expect(view.container.querySelectorAll('.v2-monitor-mobile-cards .v2-monitor-mobile-card.semi-card')).toHaveLength(1));
  expect(view.container.querySelector('.v2-monitor-table-scroll')).not.toBeInTheDocument();
  expect(screen.getByRole('button', { name: '在地图中定位粤A12345' })).toHaveTextContent('地图');
  expect(view.container.querySelector('.v2-monitor-mobile-card footer')).not.toBeInTheDocument();
  expect(addEventListener).toHaveBeenCalledWith('change', expect.any(Function));

  view.unmount();
  expect(removeEventListener).toHaveBeenCalledWith('change', expect.any(Function));
});

test('pastes, deduplicates, and submits multiple plates as one batch search', async () => {
  const vehicleRealtime = vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: vehicles, total: 2, limit: 50, offset: 0 });
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  const input = screen.getByRole('textbox', { name: '搜索车辆' });
  fireEvent.paste(input, { clipboardData: { getData: () => '粤a12345\n粤B67890，粤A12345' } });
  expect(input).toHaveValue('粤A12345，粤B67890');
  expect(screen.getAllByText('已找到 2/2')).toHaveLength(3);

  fireEvent.click(screen.getByRole('tab', { name: /列表/ }));
  await waitFor(() => {
    const params = vehicleRealtime.mock.calls[vehicleRealtime.mock.calls.length - 1]?.[0];
    expect(params?.get('keywords')).toBe('粤A12345,粤B67890');
    expect(params?.has('keyword')).toBe(false);
  });
});

test('opens an explicit batch editor and applies copied plate rows', async () => {
  const vehicleRealtime = vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: vehicles, total: 2, limit: 50, offset: 0 });
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  fireEvent.click(screen.getByRole('button', { name: '批量' }));
  const editor = screen.getByRole('textbox', { name: /每行一个车牌/ });
  fireEvent.change(editor, { target: { value: '粤a12345\n粤B67890\n粤A12345' } });
  expect(screen.getByText('2', { selector: '.v2-batch-search-summary strong' })).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '应用搜索（2）' }));

  expect(screen.queryByRole('dialog', { name: '批量搜索车辆' })).not.toBeInTheDocument();
  expect(screen.getByRole('textbox', { name: '搜索车辆' })).toHaveValue('粤A12345，粤B67890');
  fireEvent.click(screen.getByRole('tab', { name: /列表/ }));
  await waitFor(() => {
    const params = vehicleRealtime.mock.calls[vehicleRealtime.mock.calls.length - 1]?.[0];
    expect(params?.get('keywords')).toBe('粤A12345,粤B67890');
  });
});

test('hides stale fleet rows while a pasted batch scope is still loading', () => {
  monitorQueryFlags.isLoading = true;
  monitorQueryFlags.isPlaceholderData = true;
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  fireEvent.paste(screen.getByRole('textbox', { name: '搜索车辆' }), { clipboardData: { getData: () => '粤A12345\n粤B67890' } });
  expect(screen.getByText('正在查找 2 辆车')).toBeInTheDocument();
  expect(screen.getByText('查询中')).toBeInTheDocument();
  expect(screen.queryByRole('button', { name: /粤A12345 LTEST000000000001/ })).not.toBeInTheDocument();
});

test('keeps same-filter fleet rows visible while a viewport placeholder refreshes', () => {
  monitorQueryFlags.isPlaceholderData = true;
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  expect(screen.getByRole('button', { name: /粤A12345 LTEST000000000001/ })).toBeInTheDocument();
  expect(screen.queryByText('加载车辆')).not.toBeInTheDocument();
  expect(screen.getByTestId('fleet-map')).toBeInTheDocument();
});

test('reports pasted plates that have no exact realtime match', () => {
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);

  fireEvent.paste(screen.getByRole('textbox', { name: '搜索车辆' }), { clipboardData: { getData: () => '粤A12345\n粤Z99999' } });
  expect(screen.getByText('已找到 1/2')).toBeInTheDocument();
  expect(screen.getAllByText('已找到 1/2 · 未找到 1 辆')).toHaveLength(2);
  expect(screen.getAllByTitle('未找到：粤Z99999')).toHaveLength(2);
});
