import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { act, cleanup, fireEvent, render, screen, waitFor } 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 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' }));

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

afterEach(() => {
  cleanup();
  auth.role = 'admin';
  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('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('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; }));

  renderPage();
  expect((await screen.findAllByText('旧车牌')).length).toBeGreaterThan(0);
  expect(await screen.findByText('OLDVIN-evidence')).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(await screen.findByText('NEWVIN-evidence')).toBeInTheDocument();
  await waitFor(() => expect(screen.queryByRole('status')).not.toBeInTheDocument());
});

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();
  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([]);
  renderPage();

  expect(await screen.findByRole('columnheader', { name: '速度 (km/h)' })).toBeInTheDocument();
  expect(screen.queryByRole('columnheader', { name: 'SOC (%)' })).not.toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: '列设置' }));

  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();
  expect(screen.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(screen.queryByRole('dialog', { name: '列显示设置' })).not.toBeInTheDocument();
});

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'));
  renderPage();

  expect((await screen.findAllByText('旧车牌')).length).toBeGreaterThan(0);
  expect(screen.getByText('只读 · 导出需操作员权限')).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();

  expect(await screen.findByText(/customer-a · 1 辆/)).toBeInTheDocument();
  expect(screen.getByText('导出任务')).toBeInTheDocument();
  expect(screen.getByRole('button', { name: /创建导出/ })).toBeInTheDocument();
  expect(mocks.historyExports).toHaveBeenCalled();
});

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);
  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();

  const mileageToggle = screen.getAllByTitle('点击取消显示').find((button) => button.textContent?.includes('总里程'));
  expect(mileageToggle).toBeDefined();
  fireEvent.click(mileageToggle!);
  await waitFor(() => expect((mocks.historySeries.mock.calls[mocks.historySeries.mock.calls.length - 1]?.[0] as URLSearchParams).get('metrics')).toBe('speedKmh'));
});
