import { afterEach, expect, test, vi } from 'vitest';
import { createMileageExportStream, downloadMileageWorkbook } from './mileageExport';
import { createMileageWorkbook } from './mileageWorkbook';

afterEach(() => vi.unstubAllGlobals());

test('creates a styled numeric mileage workbook with formulas and frozen panes', async () => {
  const workbook = await createMileageWorkbook({
    dateFrom: '2026-07-13',
    dateTo: '2026-07-14',
    dates: ['2026-07-13', '2026-07-14'],
    vehicles: [
      { vin: 'LTEST000000000001', plate: '粤A12345' },
      { vin: 'LTEST000000000002', plate: '粤A54321' }
    ],
    mileageRows: [
      { vin: 'LTEST000000000001', plate: '粤A12345', date: '2026-07-13', startMileageKm: 100, endMileageKm: 188.7, dailyMileageKm: 88.7, source: 'GB32960' },
      { vin: 'LTEST000000000001', plate: '粤A12345', date: '2026-07-14', startMileageKm: 188.7, endMileageKm: 293.3, dailyMileageKm: 104.6, source: 'GB32960' }
    ],
    sources: [
      { protocol: 'GB32960', label: '国标 GB32960', mileageType: '仪表盘里程' },
      { protocol: 'JT808', label: '交通部 JT/T 808', mileageType: 'GPS 里程' }
    ],
    exportedAt: new Date('2026-07-15T08:00:00+08:00')
  });
  const sheet = workbook.getWorksheet('里程查询')!;

  expect(sheet.getCell('A1').value).toBe('车辆里程查询明细');
  expect(sheet.getCell('C6').value).toBeInstanceOf(Date);
  expect(sheet.getCell('C6').alignment).toMatchObject({ vertical: 'middle', horizontal: 'center' });
  expect(sheet.getCell('D6').alignment).toMatchObject({ vertical: 'middle', horizontal: 'center' });
  expect(sheet.getCell('A6').alignment).toMatchObject({ vertical: 'middle', horizontal: 'left' });
  expect(sheet.getCell('E6').alignment).toMatchObject({ vertical: 'middle', horizontal: 'right' });
  expect(sheet.getCell('C7').value).toBe(88.7);
  expect(sheet.getCell('E7').value).toMatchObject({ formula: 'SUM(C7:D7)', result: 193.3 });
  expect(sheet.getCell('E7').numFmt).toBe('#,##0.0" km"');
  expect(sheet.views[0]).toMatchObject({ state: 'frozen', xSplit: 2, ySplit: 6, showGridLines: false });
  expect(sheet.autoFilter).toEqual({ from: { row: 6, column: 1 }, to: { row: 8, column: 5 } });
  expect((sheet as unknown as { conditionalFormattings: unknown[] }).conditionalFormattings).toHaveLength(1);
  expect((await workbook.xlsx.writeBuffer()).byteLength).toBeGreaterThan(5_000);
});

test('does not build or download a workbook after cancellation', async () => {
  const controller = new AbortController();
  controller.abort();
  await expect(downloadMileageWorkbook({
    dateFrom: '2026-07-13',
    dateTo: '2026-07-14',
    dates: ['2026-07-13', '2026-07-14'],
    vehicles: [],
    mileageRows: [],
    sources: []
  }, controller.signal)).rejects.toMatchObject({ name: 'AbortError' });
});

test('refuses a main-thread workbook fallback when module workers are unavailable', async () => {
  vi.stubGlobal('Worker', undefined);
  await expect(downloadMileageWorkbook({
    dateFrom: '2026-07-13',
    dateTo: '2026-07-14',
    dates: ['2026-07-13', '2026-07-14'],
    vehicles: [],
    mileageRows: [],
    sources: []
  })).rejects.toThrow('当前浏览器不支持后台 Excel 导出');
});

test('terminates the workbook worker when an active export is cancelled', async () => {
  const terminate = vi.fn();
  const postMessage = vi.fn();
  class WorkerMock {
    onmessage: ((event: MessageEvent) => void) | null = null;
    onerror: ((event: ErrorEvent) => void) | null = null;
    postMessage = postMessage;
    terminate = terminate;
  }
  vi.stubGlobal('Worker', WorkerMock);
  const controller = new AbortController();
  const exportPromise = downloadMileageWorkbook({
    dateFrom: '2026-07-13',
    dateTo: '2026-07-14',
    dates: ['2026-07-13', '2026-07-14'],
    vehicles: [],
    mileageRows: [{ vin: 'LTEST000000000001', plate: '粤A12345', date: '2026-07-13', startMileageKm: 100, endMileageKm: 188.7, dailyMileageKm: 88.7, source: 'GB32960' }],
    sources: []
  }, controller.signal);

  expect(postMessage).toHaveBeenCalledTimes(3);
  expect(postMessage.mock.calls.map(([message]) => message.type)).toEqual(['start', 'rows', 'finish']);
  expect(postMessage.mock.calls[1][0].rows).toEqual([{ vin: 'LTEST000000000001', date: '2026-07-13', dailyMileageKm: 88.7 }]);
  controller.abort();
  await expect(exportPromise).rejects.toMatchObject({ name: 'AbortError' });
  expect(terminate).toHaveBeenCalledTimes(1);
});

test('releases a streaming export before finish when the route aborts', async () => {
  const terminate = vi.fn();
  const postMessage = vi.fn();
  class WorkerMock {
    onmessage: ((event: MessageEvent) => void) | null = null;
    onerror: ((event: ErrorEvent) => void) | null = null;
    postMessage = postMessage;
    terminate = terminate;
  }
  vi.stubGlobal('Worker', WorkerMock);
  const controller = new AbortController();
  const stream = createMileageExportStream({ dateFrom: '2026-01-01', dateTo: '2026-12-31', dates: [], sources: [] }, controller.signal);
  stream.appendRows([{ vin: 'VIN001', date: '2026-01-01', dailyMileageKm: 12.5 }]);

  expect(postMessage.mock.calls.map(([message]) => message.type)).toEqual(['start', 'rows']);
  controller.abort();

  expect(terminate).toHaveBeenCalledTimes(1);
  await expect(stream.finish([{ vin: 'VIN001', plate: '粤A00001' }])).rejects.toMatchObject({ name: 'AbortError' });
});
