import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { afterEach, expect, test, vi } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import type { HistoryDataResponse, HistorySeriesResponse } from '../../api/types';
import { buildMonitorPath, withMonitorReturn } from '../routing/monitorContext';
import { ROUTER_FUTURE } from '../routing/routerConfig';
import HistoryPage, { formatHistoryDateTime } from './HistoryPage';

const mocks = vi.hoisted(() => ({
  historyMetricCatalog: vi.fn(),
  historyData: vi.fn(),
  historySeries: vi.fn(),
  historyExports: vi.fn(),
  createHistoryExport: vi.fn(),
  downloadHistoryExport: vi.fn()
}));
const auth = vi.hoisted(() => ({ role: 'admin' }));
const layout = vi.hoisted(() => ({ mobile: false }));

vi.mock('../../api/client', () => ({ api: mocks }));
vi.mock('../auth/AuthGate', () => ({ usePlatformSession: () => ({ session: { role: auth.role } }) }));
vi.mock('../hooks/useMobileLayout', () => ({ useMobileLayout: () => layout.mobile }));

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

const metric = { key: 'speedKmh', label: '速度', unit: 'km/h', category: 'location', valueType: 'number', defaultVisible: true };
const optionalMetric = { key: 'socPercent', label: 'SOC', unit: '%', category: 'location', valueType: 'number', defaultVisible: false };

function historyData(vin: string, plate: string, asOf: string): HistoryDataResponse {
  return {
    category: 'location', columns: [metric], total: 1, limit: 50, offset: 0, asOf,
    summary: { resultRows: 1, vehicleCount: 1, sources: ['JT808'], queryDurationMs: 12 },
    rows: [{ id: `${vin}-row`, vin, plate, protocol: 'JT808', deviceTime: '2026-07-16 04:00:00', serverTime: '2026-07-16 04:00:01', quality: 'normal', qualityReason: '坐标、速度、里程及设备/服务时间通过基础校验', evidenceId: `${vin}-evidence`, values: { speedKmh: 32 } }]
  };
}

function historySeries(vin: string, plate: string, asOf: string): HistorySeriesResponse {
  return {
    metrics: [metric], dateFrom: '2026-07-16T00:00:00Z', dateTo: '2026-07-16T05:00:00Z', asOf,
    summary: { rawPointCount: 2, bucketCount: 2, returnedPointCount: 2, seriesCount: 1, grainSeconds: 60, targetPoints: 240, expectedBucketCount: 2, missingBucketCount: 0, queryDurationMs: 8, complete: true, evidence: 'test series' },
    series: [{ vin, plate, protocol: 'JT808', metric: 'speedKmh', label: '速度', unit: 'km/h', aggregation: 'avg', points: [{ time: '2026-07-16 04:00:00', value: 30, min: 30, max: 30, count: 1 }, { time: '2026-07-16 04:01:00', value: 32, min: 32, max: 32, count: 1 }] }]
  };
}

function renderPage(initialEntry = '/history?keywords=OLDVIN&dateFrom=2026-07-16T00%3A00&dateTo=2026-07-16T05%3A00') {
  const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={[initialEntry]}><HistoryPage /></MemoryRouter></QueryClientProvider>);
  return { ...view, client };
}

test('renders protocol timestamps in Asia/Shanghai while preserving local values', () => {
  expect(formatHistoryDateTime('2026-07-18T02:33:00Z')).toBe('2026-07-18 10:33:00');
  expect(formatHistoryDateTime('2026-07-18T02:33:00+08:00')).toBe('2026-07-18 02:33:00');
  expect(formatHistoryDateTime('2026-07-18 02:33:00')).toBe('2026-07-18 02:33:00');
});

test('preserves the exact monitor return after changing history filters', async () => {
  mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
  mocks.historyData.mockResolvedValue(historyData('OLDVIN', '旧车牌', 'old-as-of'));
  mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
  mocks.historyExports.mockResolvedValue([]);
  const monitorPath = buildMonitorPath({
    mode: 'list', keyword: '粤A12345', protocol: '', status: '', selectedVin: 'OLDVIN', detailOpen: false,
    viewport: { zoom: 12.5, bounds: '' }, listOffset: 0, listLimit: 50, hasViewport: true
  });

  renderPage(withMonitorReturn('/history?keywords=OLDVIN&dateFrom=2026-07-16T00%3A00&dateTo=2026-07-16T05%3A00', monitorPath));
  const returnLink = await screen.findByRole('link', { name: /返回全局监控/ });
  expect(returnLink).toHaveAttribute('href', monitorPath);

  fireEvent.change(screen.getByPlaceholderText('车牌 / VIN，多台用逗号分隔'), { target: { value: 'NEWVIN' } });
  fireEvent.click(screen.getByRole('button', { name: '查询' }));

  expect(await screen.findByRole('link', { name: /返回全局监控/ })).toHaveAttribute('href', monitorPath);
});

test('renders the shared empty state when the API returns nullable empty collections', async () => {
  mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
  mocks.historyExports.mockResolvedValue([]);
  mocks.historyData.mockResolvedValue({
    category: 'location',
    columns: null,
    rows: null,
    summary: null,
    total: 0,
    limit: 50,
    offset: 0,
    asOf: '2026-07-18 00:53:00'
  } as unknown as HistoryDataResponse);

  const view = renderPage('/history?keywords=LTESTNOEXIST00001&dateFrom=2026-07-18T00%3A00&dateTo=2026-07-18T00%3A53');

  expect(await screen.findByText('当前条件没有历史记录')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-empty.v2-state-surface')).toBeInTheDocument();
  expect(screen.queryByText('当前模块暂时无法显示')).not.toBeInTheDocument();
});

test('clears stale rows, charts, and evidence as soon as the history scope changes', async () => {
  let resolveNewData!: (value: HistoryDataResponse) => void;
  let resolveNewSeries!: (value: HistorySeriesResponse) => void;
  mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
  mocks.historyExports.mockResolvedValue([]);
  mocks.historyData.mockImplementation((params: URLSearchParams) => params.get('keywords') === 'OLDVIN'
    ? Promise.resolve(historyData('OLDVIN', '旧车牌', 'old-as-of'))
    : new Promise<HistoryDataResponse>((resolve) => { resolveNewData = resolve; }));
  mocks.historySeries.mockImplementation((params: URLSearchParams) => params.get('keywords') === 'OLDVIN'
    ? Promise.resolve(historySeries('OLDVIN', '旧车牌', 'old-as-of'))
    : new Promise<HistorySeriesResponse>((resolve) => { resolveNewSeries = resolve; }));

  const view = renderPage();
  expect((await screen.findAllByText('旧车牌')).length).toBeGreaterThan(0);
  expect(screen.queryByText('OLDVIN-evidence')).not.toBeInTheDocument();
  for (const className of ['v2-history-filter-card', 'v2-history-summary-rail', 'v2-history-table-card']) {
    expect(view.container.querySelector(`.${className}.semi-card`)).toBeInTheDocument();
  }
  expect(view.container.querySelector('.v2-history-discovery-shell')).toContainElement(screen.getByLabelText('历史数据查询操作'));
  expect(screen.getByLabelText('历史数据查询操作')).toHaveTextContent('遥测证据检索');
  expect(screen.getByLabelText('历史数据查询操作')).toHaveTextContent('上海时区');
  expect(view.container.querySelector('.v2-history-summary-rail')).toHaveAttribute('aria-label', '历史数据查询统计信息');
  expect(view.container.querySelector('.v2-history-summary-secondary.semi-card-group')).toBeInTheDocument();
  expect(view.container.querySelectorAll('.v2-history-summary-card.semi-card')).toHaveLength(4);
  expect(view.container.querySelector('.v2-history-summary-card.is-primary .v2-history-summary-value')).toHaveTextContent('1');
  expect(screen.getByText('1 / 1 字段').closest('.semi-tag')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-result-actions.v2-workspace-panel-actions')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-evidence')).not.toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-workspace')).not.toHaveClass('is-inspector-open');
  expect(screen.queryByRole('heading', { level: 5, name: '聚合趋势' })).not.toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-trend')).not.toBeInTheDocument();
  expect(screen.getByRole('button', { name: /展开趋势/ })).toHaveAttribute('aria-expanded', 'false');
  expect(screen.queryByRole('heading', { level: 5, name: '导出任务' })).not.toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-data-table.semi-table-wrapper')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-table-scroll > table')).not.toBeInTheDocument();
  const desktopHistoryScroll = view.container.querySelector<HTMLElement>('.v2-history-table-scroll');
  expect(desktopHistoryScroll).toHaveAttribute('tabindex', '0');
  expect(desktopHistoryScroll).toHaveAttribute('aria-label', '历史明细表格，可使用左右方向键浏览业务字段');
  const desktopTableScroller = desktopHistoryScroll?.querySelector<HTMLElement>('.semi-table-body');
  expect(desktopTableScroller).toBeInTheDocument();
  const scrollBy = vi.fn();
  Object.defineProperty(desktopTableScroller!, 'scrollBy', { configurable: true, value: scrollBy });
  fireEvent.keyDown(desktopHistoryScroll!, { key: 'ArrowRight' });
  expect(scrollBy).toHaveBeenCalledWith({ left: 260, behavior: 'smooth' });
  fireEvent.keyDown(desktopHistoryScroll!, { key: 'ArrowLeft' });
  expect(scrollBy).toHaveBeenCalledWith({ left: -260, behavior: 'smooth' });
  expect(screen.getByRole('columnheader', { name: '设备时间' })).toHaveClass('semi-table-cell-fixed-left', 'v2-history-device-time-column');
  expect(screen.getByRole('columnheader', { name: '车牌' })).toHaveClass('semi-table-cell-fixed-left', 'v2-history-plate-column');
  expect(view.container.querySelector('th.v2-history-selection-column')).toHaveClass('semi-table-cell-fixed-left');
  expect(view.container.querySelector('.v2-history-mobile-list')).not.toBeInTheDocument();
  const desktopHistoryRow = screen.getByTestId('history-row-OLDVIN-row');
  expect(desktopHistoryRow).toHaveAttribute('role', 'button');
  expect(desktopHistoryRow).toHaveAttribute('aria-expanded', 'false');
  fireEvent.keyDown(desktopHistoryRow, { key: 'Enter' });
  expect(desktopHistoryRow).toHaveAttribute('aria-expanded', 'true');
  expect(await screen.findByText('OLDVIN-evidence')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-evidence-descriptions.semi-descriptions')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-evidence-status')).toHaveTextContent('证据判读正常');
  expect(view.container.querySelector('.v2-evidence-values.semi-descriptions')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-quality-tag.semi-tag')).toHaveTextContent('正常');
  expect(view.container.querySelector('.v2-history-protocol-tag.semi-tag')).toHaveTextContent('JT/T 808');
  expect(view.container.querySelector('.v2-history-workspace')).toHaveClass('is-inspector-open');
  fireEvent.click(screen.getByRole('button', { name: '关闭数据详情' }));
  expect(desktopHistoryRow).toHaveAttribute('aria-expanded', 'false');
  expect(view.container.querySelector('.v2-history-workspace')).not.toHaveClass('is-inspector-open');
  fireEvent.click(desktopHistoryRow);
  expect(desktopHistoryRow).toHaveAttribute('aria-expanded', 'true');
  fireEvent.click(screen.getByRole('button', { name: /展开趋势/ }));
  await waitFor(() => expect(mocks.historySeries).toHaveBeenCalledTimes(1));
  expect(screen.getByRole('heading', { level: 5, name: '聚合趋势' })).toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-trend')).toHaveClass('is-expanded');
  expect((await screen.findByText('覆盖 100.0%')).closest('.semi-tag')).toBeInTheDocument();
  expect((await screen.findByText('时间窗完整')).closest('.semi-tag')).toBeInTheDocument();

  fireEvent.change(screen.getByPlaceholderText('车牌 / VIN，多台用逗号分隔'), { target: { value: 'NEWVIN' } });
  fireEvent.click(screen.getByRole('button', { name: '查询' }));

  expect(await screen.findByRole('status')).toHaveTextContent('正在加载当前筛选范围的历史数据');
  expect(screen.queryByText('旧车牌')).not.toBeInTheDocument();
  expect(screen.queryByText('OLDVIN-evidence')).not.toBeInTheDocument();
  expect(screen.getByText('正在聚合时间序列…')).toBeInTheDocument();

  await act(async () => {
    resolveNewData(historyData('NEWVIN', '新车牌', 'new-as-of'));
    resolveNewSeries(historySeries('NEWVIN', '新车牌', 'new-as-of'));
  });
  expect((await screen.findAllByText('新车牌')).length).toBeGreaterThan(0);
  expect(screen.queryByText('NEWVIN-evidence')).not.toBeInTheDocument();
  fireEvent.click(screen.getByTestId('history-row-NEWVIN-row'));
  expect(await screen.findByText('NEWVIN-evidence')).toBeInTheDocument();
  await waitFor(() => expect(screen.queryByRole('status')).not.toBeInTheDocument());
});

test('uses one focused Semi bottom SideSheet for the complete mobile history query', async () => {
  layout.mobile = true;
  mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
  mocks.historyData.mockResolvedValue(historyData('MOBILEVIN', '粤A移动历史', 'mobile-as-of'));
  mocks.historySeries.mockResolvedValue(historySeries('MOBILEVIN', '粤A移动历史', 'mobile-as-of'));
  mocks.historyExports.mockResolvedValue([]);
  renderPage('/history');

  await screen.findByText('选择车辆开始查询');
  const trigger = screen.getByRole('button', { name: '修改查询范围：请选择车辆' });
  expect(trigger).toHaveAttribute('aria-expanded', 'false');
  expect(screen.queryByRole('button', { name: '展开趋势' })).not.toBeInTheDocument();
  expect(screen.queryByRole('button', { name: '打开历史数据工具' })).not.toBeInTheDocument();
  expect(screen.queryByRole('button', { name: 'Previous' })).not.toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '选择车辆并查询' }));
  expect(trigger).toHaveAttribute('aria-expanded', 'true');

  const dialog = await screen.findByRole('dialog', { name: '历史查询范围' });
  expect(document.querySelector('.v2-history-filter-sidesheet')).toHaveClass('semi-sidesheet-bottom');
  expect(document.querySelector('.v2-history-filter-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(82dvh, 700px)' });
  expect(within(dialog).getByPlaceholderText('车牌 / VIN，多台用逗号分隔')).toBeInTheDocument();
  expect(within(dialog).getByLabelText('开始时间')).toBeInTheDocument();
  expect(within(dialog).getByLabelText('结束时间')).toBeInTheDocument();
  expect(within(dialog).getByRole('combobox', { name: '数据类型' })).toBeInTheDocument();
  expect(within(dialog).getByRole('combobox', { name: '数据来源' })).toBeInTheDocument();
  fireEvent.click(within(dialog).getByRole('button', { name: '重置条件' }));
  expect(screen.getByRole('dialog', { name: '历史查询范围' })).toBeInTheDocument();
  fireEvent.change(within(dialog).getByPlaceholderText('车牌 / VIN，多台用逗号分隔'), { target: { value: 'MOBILEVIN' } });
  fireEvent.click(within(dialog).getByRole('button', { name: '应用并查询' }));

  await waitFor(() => {
    const calls = mocks.historyData.mock.calls;
    expect((calls[calls.length - 1]?.[0] as URLSearchParams | undefined)?.get('keywords')).toBe('MOBILEVIN');
  });
  expect(screen.getByRole('button', { name: '修改查询范围：1 辆 · 位置数据' })).toHaveAttribute('aria-expanded', 'false');
  expect(screen.getByRole('dialog', { name: '历史查询范围' })).toHaveClass('semi-sidesheet-animation-content_hide_bottom');
});

test('renders only selectable Semi history cards on mobile', async () => {
  layout.mobile = true;
  mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
  mocks.historyData.mockResolvedValue(historyData('MOBILEVIN', '粤A移动历史', 'mobile-as-of'));
  mocks.historySeries.mockResolvedValue(historySeries('MOBILEVIN', '粤A移动历史', 'mobile-as-of'));
  mocks.historyExports.mockResolvedValue([]);
  const view = renderPage('/history?keywords=MOBILEVIN&dateFrom=2026-07-16T00%3A00&dateTo=2026-07-16T05%3A00');

  const action = await screen.findByRole('button', { name: '查看 粤A移动历史 2026-07-16 04:00:00 数据详情' });
  expect(action).toHaveClass('semi-button', 'v2-history-mobile-action');
  expect(action.closest('.semi-card')).toHaveClass('v2-history-mobile-card');
  expect(view.container.querySelector('.v2-history-data-table')).not.toBeInTheDocument();
  expect(action).toHaveAttribute('aria-pressed', 'false');
  expect(action).toHaveAttribute('aria-expanded', 'false');
  expect(view.container.querySelector('.v2-history-table-scroll.is-mobile-scroll')).toHaveAttribute('aria-label', '历史明细列表，可上下滚动');
  expect(view.container.querySelector('.v2-history-table-scroll.is-mobile-scroll')).toHaveAttribute('tabindex', '0');
  expect(action.closest('.v2-history-mobile-card')?.querySelector('.v2-history-protocol-tag.semi-tag')).toHaveTextContent('JT/T 808');
  expect(view.container.querySelector('.v2-history-metric-scroll')).not.toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-summary-rail')).toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-result-actions')).toHaveClass('is-mobile-actions');
  expect(screen.queryByText('MOBILEVIN-evidence')).not.toBeInTheDocument();
  expect((mocks.historyData.mock.calls[0]?.[0] as URLSearchParams).get('limit')).toBe('20');
  expect(screen.queryByLabelText('每页数量')).not.toBeInTheDocument();
  expect(screen.queryByLabelText('表格密度')).not.toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-side')).not.toBeInTheDocument();
  expect(mocks.historyExports).not.toHaveBeenCalled();
  expect(screen.getByRole('button', { name: '展开趋势' })).toHaveTextContent('趋势');
  expect(screen.getByRole('button', { name: '展开趋势' })).toHaveClass('v2-workspace-mobile-tool-button', 'is-muted');
  expect(screen.getByRole('button', { name: '打开历史数据工具' })).toHaveTextContent('工具');
  expect(screen.getByRole('button', { name: '打开历史数据工具' })).toHaveClass('v2-workspace-mobile-tool-button', 'is-muted');
  expect(screen.queryByText('坐标、速度、里程及设备/服务时间通过基础校验')).not.toBeInTheDocument();
  fireEvent.click(action);
  expect(action).toHaveAttribute('aria-pressed', 'true');
  expect(action).toHaveAttribute('aria-expanded', 'true');
  expect(await screen.findByRole('dialog', { name: '历史数据详情' })).toBeInTheDocument();
  expect(document.querySelector('.v2-history-detail-sidesheet')).toHaveClass('semi-sidesheet-bottom', 'v2-workspace-detail-sidesheet');
  expect(document.querySelector('.v2-history-detail-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(82dvh, 720px)' });
  expect(document.querySelector('.v2-history-detail-sidesheet .v2-workspace-config-title > .semi-tag')).toHaveTextContent('正常');
  expect(await screen.findByText('MOBILEVIN-evidence')).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '关闭历史数据详情' }));
  expect(action).toHaveAttribute('aria-pressed', 'false');
  expect(action).toHaveAttribute('aria-expanded', 'false');
  expect(document.body.style.overflow).not.toBe('hidden');
  expect(screen.queryByText('MOBILEVIN-evidence')).not.toBeInTheDocument();

  const toolsButton = screen.getByRole('button', { name: '打开历史数据工具' });
  fireEvent.click(toolsButton);
  await waitFor(() => expect(toolsButton).toHaveAttribute('aria-expanded', 'true'));
  fireEvent.click(screen.getByRole('menuitem', { name: /导出任务/ }));
  expect(await screen.findByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument();
  expect(document.querySelector('.v2-history-export-sidesheet')).toHaveClass('semi-sidesheet-bottom', 'v2-workspace-task-sidesheet');
  expect(document.querySelector('.v2-history-export-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(82dvh, 720px)' });
  await waitFor(() => expect(mocks.historyExports).toHaveBeenCalledTimes(1));
  expect(screen.getByRole('list', { name: '历史数据导出任务摘要' })).toHaveTextContent('生成中0排队与执行可下载0已完成任务需处理0失败任务');
  expect(screen.getByRole('button', { name: '刷新导出任务' })).toBeInTheDocument();
  expect(screen.getByRole('button', { name: '完成' })).toBeInTheDocument();
  expect(screen.getByText('暂无导出任务')).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '关闭历史数据导出任务' }));
  expect(toolsButton).toHaveAttribute('aria-expanded', 'false');
  expect(document.body.style.overflow).not.toBe('hidden');
  expect(screen.queryByText('暂无导出任务')).not.toBeInTheDocument();

  fireEvent.click(toolsButton);
  fireEvent.click(screen.getByRole('menuitem', { name: /字段设置/ }));
  expect(await screen.findByRole('dialog', { name: '列显示设置' })).toBeInTheDocument();
  expect(document.querySelector('.v2-history-column-sidesheet')).toHaveClass('semi-sidesheet-bottom');
  expect(document.querySelector('.v2-history-column-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(68dvh, 580px)' });
  fireEvent.click(screen.getByRole('button', { name: '关闭列显示设置' }));
  expect(toolsButton).toHaveAttribute('aria-expanded', 'false');
});

test('releases export job state immediately after leaving the history page', async () => {
  mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
  mocks.historyData.mockResolvedValue(historyData('OLDVIN', '旧车牌', 'old-as-of'));
  mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
  mocks.historyExports.mockResolvedValue([{ id: 'export-running', name: '运行中导出', status: 'running', progress: 30, format: 'csv', category: 'location', keywords: ['OLDVIN'], rowCount: 0, totalRows: 100, processedRows: 30, fileSizeBytes: 0, createdAt: '2026-07-16T04:00:00Z', updatedAt: '2026-07-16T04:00:01Z', evidence: 'test export' }]);

  const { client, unmount } = renderPage();
  fireEvent.click(await screen.findByRole('button', { name: '查看导出任务' }));
  expect(await screen.findByText('运行中导出')).toBeInTheDocument();
  expect(client.getQueryCache().find({ queryKey: ['history-exports'] })).toBeDefined();

  unmount();
  await waitFor(() => expect(client.getQueryCache().find({ queryKey: ['history-exports'] })).toBeUndefined());
});

test('opens a working column visibility panel and preserves non-hideable identity columns', async () => {
  const data = historyData('OLDVIN', '旧车牌', 'old-as-of');
  data.columns = [metric, optionalMetric];
  data.rows[0].values.socPercent = 88;
  mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric, optionalMetric] });
  mocks.historyData.mockResolvedValue(data);
  mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
  mocks.historyExports.mockResolvedValue([]);
  const view = renderPage();

  expect(await screen.findByRole('columnheader', { name: '速度 (km/h)' })).toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-time')).toHaveTextContent('2026-07-16 04:00:00');
  expect(screen.queryByRole('columnheader', { name: 'SOC (%)' })).not.toBeInTheDocument();
  expect(view.container.querySelector('.v2-history-metric-scroll')).not.toBeInTheDocument();
  expect(screen.getByText('1 / 2 字段').closest('.semi-tag')).toBeInTheDocument();
  const columnSettingsButton = screen.getByRole('button', { name: '列设置' });
  fireEvent.click(columnSettingsButton);

  expect(screen.getByRole('dialog', { name: '列显示设置' })).toBeInTheDocument();
  fireEvent.change(screen.getByPlaceholderText('搜索中文、状态含义或字段 key'), { target: { value: 'soc' } });
  expect(screen.getByRole('checkbox', { name: /SOC/ })).toBeInTheDocument();
  expect(screen.queryByRole('checkbox', { name: /速度/ })).not.toBeInTheDocument();
  fireEvent.change(screen.getByPlaceholderText('搜索中文、状态含义或字段 key'), { target: { value: '' } });
  fireEvent.click(screen.getByRole('checkbox', { name: /速度/ }));
  expect(screen.queryByRole('columnheader', { name: '速度 (km/h)' })).not.toBeInTheDocument();
  const historyTable = document.querySelector<HTMLElement>('.v2-history-table-scroll table');
  expect(historyTable).toBeInTheDocument();
  expect(within(historyTable!).getByRole('columnheader', { name: 'VIN' })).toBeInTheDocument();

  fireEvent.click(screen.getByRole('button', { name: '显示全部' }));
  expect(screen.getByRole('columnheader', { name: '速度 (km/h)' })).toBeInTheDocument();
  expect(screen.getByRole('columnheader', { name: 'SOC (%)' })).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '恢复默认' }));
  expect(screen.getByRole('columnheader', { name: '速度 (km/h)' })).toBeInTheDocument();
  expect(screen.queryByRole('columnheader', { name: 'SOC (%)' })).not.toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '关闭列显示设置' }));
  expect(columnSettingsButton).toHaveAttribute('aria-expanded', 'false');
  expect(document.body.style.overflow).not.toBe('hidden');
});

test('keeps history readable without mounting operator-only export requests for a viewer', async () => {
  auth.role = 'viewer';
  mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
  mocks.historyData.mockResolvedValue(historyData('OLDVIN', '旧车牌', 'old-as-of'));
  mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
  const view = renderPage();

  expect((await screen.findAllByText('旧车牌')).length).toBeGreaterThan(0);
  expect(screen.getByText('只读').closest('.semi-tag')).toHaveClass('v2-history-permission-tag');
  expect(view.container.querySelector('.v2-history-toolbar-actions')).toBeInTheDocument();
  expect(screen.queryByRole('button', { name: /创建导出/ })).not.toBeInTheDocument();
  expect(screen.queryByText('导出任务')).not.toBeInTheDocument();
  expect(mocks.historyExports).not.toHaveBeenCalled();
  expect(mocks.createHistoryExport).not.toHaveBeenCalled();
});

test('shows only the authenticated customer export workspace with owner and scope metadata', async () => {
  auth.role = 'customer';
  mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
  mocks.historyData.mockResolvedValue(historyData('OLDVIN', '旧车牌', 'old-as-of'));
  mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
  mocks.historyExports.mockResolvedValue([{
    id: 'customer-export', name: '历史数据_20260716_customer-a', status: 'completed', progress: 100, format: 'csv', category: 'location',
    keywords: ['OLDVIN'], vehicleVins: ['OLDVIN'], dateFrom: '2026-07-16T00:00', dateTo: '2026-07-16T05:00',
    ownerName: '客户甲', ownerUsername: 'customer-a', ownerRole: 'customer', ownerUserType: 'customer',
    rowCount: 10, totalRows: 10, processedRows: 10, fileSizeBytes: 1024, downloadUrl: '/api/v2/exports/customer-export/download',
    createdAt: '2026-07-16T05:00:00Z', updatedAt: '2026-07-16T05:00:01Z', completedAt: '2026-07-16T05:00:01Z', evidence: 'owner scoped'
  }]);
  renderPage();

  fireEvent.click(await screen.findByRole('button', { name: '查看导出任务' }));
  expect(await screen.findByText(/customer-a · 1 辆/)).toBeInTheDocument();
  expect(screen.getByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument();
  expect(screen.getByRole('list', { name: '历史数据导出任务摘要' })).toHaveTextContent('生成中0排队与执行可下载1已完成任务需处理0失败任务');
  expect(screen.getByText('最近更新')).toBeInTheDocument();
  expect(screen.getByText('2026-07-16 13:00:01')).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '刷新导出任务' }));
  await waitFor(() => expect(mocks.historyExports).toHaveBeenCalledTimes(2));
  expect(screen.getByRole('button', { name: /创建导出/ })).toBeInTheDocument();
});

test('uses selected chartable fields for trends and omits empty RAW evidence UI', async () => {
  const totalMileageMetric = { key: 'totalMileageKm', label: '总里程', unit: 'km', category: 'location', valueType: 'number', defaultVisible: true };
  const data = historyData('OLDVIN', '旧车牌', 'old-as-of');
  data.columns = [metric, totalMileageMetric];
  data.rows[0].evidenceId = undefined;
  data.rows[0].values.totalMileageKm = 1234;
  mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric, totalMileageMetric] });
  mocks.historyData.mockResolvedValue(data);
  mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
  mocks.historyExports.mockResolvedValue([]);
  renderPage();

  expect((await screen.findAllByText('旧车牌')).length).toBeGreaterThan(0);
  expect(mocks.historySeries).not.toHaveBeenCalled();
  fireEvent.click(screen.getByRole('button', { name: /展开趋势/ }));
  await waitFor(() => expect(mocks.historySeries).toHaveBeenCalled());
  expect((mocks.historySeries.mock.calls[mocks.historySeries.mock.calls.length - 1]?.[0] as URLSearchParams).get('metrics')).toBe('speedKmh,totalMileageKm');
  expect(screen.queryByText('RAW 证据')).not.toBeInTheDocument();

  fireEvent.click(screen.getByRole('button', { name: '列设置' }));
  fireEvent.click(screen.getByRole('checkbox', { name: '总里程 (km)' }));
  await waitFor(() => expect((mocks.historySeries.mock.calls[mocks.historySeries.mock.calls.length - 1]?.[0] as URLSearchParams).get('metrics')).toBe('speedKmh'));
});
