import { describe, expect, it, vi } from 'vitest';
import {
  loadRouteModule,
  routePreloadBudget,
  routePreloadCandidates,
  routeSectionForPath,
  scheduleIdleRoutePreloads,
  shouldPreloadRouteOnIntent,
  shouldPreloadRoutes
} from './routeModules';

describe('route module preloading', () => {
  async function flushPreloadQueue() {
    for (let index = 0; index < 6; index += 1) await Promise.resolve();
  }

  it('maps nested and parameterized URLs to the correct lazy module', () => {
    expect(routeSectionForPath('/monitor')).toBe('monitor');
    expect(routeSectionForPath('/vehicles/LTEST001?tab=telemetry')).toBe('vehicles');
    expect(routeSectionForPath('/alerts/rules#editor')).toBe('alerts');
    expect(routeSectionForPath('/not-a-platform-route')).toBeUndefined();
  });

  it('retries one transient route import failure before exposing an error boundary', async () => {
    const loader = vi.fn()
      .mockRejectedValueOnce(new TypeError('Failed to fetch dynamically imported module'))
      .mockResolvedValue({ default: () => null });

    await expect(loadRouteModule(loader, 'history', { timeoutMs: 100, retryDelayMs: 0 })).resolves.toBeDefined();
    expect(loader).toHaveBeenCalledTimes(2);
  });

  it('turns a permanently pending route import into a bounded chunk error', async () => {
    const loader = vi.fn(() => new Promise<never>(() => undefined));

    await expect(loadRouteModule(loader, 'statistics', { timeoutMs: 5 })).rejects.toMatchObject({
      name: 'RouteChunkLoadTimeoutError',
      message: expect.stringContaining('statistics')
    });
    expect(loader).toHaveBeenCalledTimes(1);
  });

  it('skips background route preloads on save-data and 2G connections', () => {
    const visible = { visibilityState: 'visible' as const, deviceMemory: 8 };
    expect(shouldPreloadRoutes({ saveData: true, effectiveType: '4g' }, visible)).toBe(false);
    expect(shouldPreloadRoutes({ effectiveType: 'slow-2g' }, visible)).toBe(false);
    expect(shouldPreloadRoutes({ effectiveType: '2g' }, visible)).toBe(false);
    expect(shouldPreloadRoutes({ effectiveType: '3g' }, visible)).toBe(true);
    expect(shouldPreloadRoutes({ effectiveType: '4g' }, visible)).toBe(true);
    expect(shouldPreloadRoutes({ effectiveType: '4g' }, { ...visible, visibilityState: 'hidden' })).toBe(false);
    expect(shouldPreloadRouteOnIntent({ saveData: true, effectiveType: '4g' })).toBe(false);
    expect(shouldPreloadRouteOnIntent({ effectiveType: '2g' })).toBe(false);
  });

  it('limits speculative modules by memory/network budget and route likelihood', () => {
    const visible = { visibilityState: 'visible' as const };
    expect(routePreloadBudget({ effectiveType: '4g' }, { ...visible, deviceMemory: 8 })).toBe(2);
    expect(routePreloadBudget({ effectiveType: '3g' }, { ...visible, deviceMemory: 8 })).toBe(1);
    expect(routePreloadBudget({ effectiveType: '4g' }, { ...visible, deviceMemory: 4 })).toBe(1);
    expect(routePreloadBudget({ effectiveType: '4g' }, { ...visible, deviceMemory: 2 })).toBe(0);
    expect(routePreloadCandidates('/monitor')).toEqual(['vehicles', 'tracks']);
    expect(routePreloadCandidates('/statistics?dateFrom=2026-07-01')).toEqual(['history', 'vehicles']);
  });

  it('warms only likely routes in sequential idle slots and stops after cleanup', async () => {
    const callbacks: Array<() => void> = [];
    const cancelled: number[] = [];
    const schedule = vi.fn((callback: () => void) => {
      const index = callbacks.push(callback) - 1;
      return () => { cancelled.push(index); };
    });
    const preload = vi.fn(async (_pathname: string) => undefined);
    const cleanup = scheduleIdleRoutePreloads({
      activePathname: '/monitor',
      connection: { effectiveType: '4g' },
      runtime: { visibilityState: 'visible', deviceMemory: 8 },
      preload,
      schedule,
      isVisible: () => true
    });

    expect(schedule).toHaveBeenCalledTimes(1);
    callbacks[0]();
    await flushPreloadQueue();
    expect(preload.mock.calls.map(([path]) => path)).toEqual(['/vehicles']);
    expect(schedule).toHaveBeenCalledTimes(2);

    callbacks[1]();
    await flushPreloadQueue();
    expect(preload.mock.calls.map(([path]) => path)).toEqual(['/vehicles', '/tracks']);
    expect(schedule).toHaveBeenCalledTimes(2);

    cleanup();
    expect(cancelled).toContain(1);
  });

  it('does not start or continue speculative work in a background tab', async () => {
    const schedule = vi.fn((_callback: () => void) => () => undefined);
    scheduleIdleRoutePreloads({
      activePathname: '/monitor',
      connection: { effectiveType: '4g' },
      runtime: { visibilityState: 'hidden', deviceMemory: 8 },
      schedule
    });
    expect(schedule).not.toHaveBeenCalled();

    const callbacks: Array<() => void> = [];
    const preload = vi.fn(async () => undefined);
    let visible = true;
    scheduleIdleRoutePreloads({
      activePathname: '/monitor',
      connection: { effectiveType: '4g' },
      runtime: { visibilityState: 'visible', deviceMemory: 8 },
      preload,
      schedule: (callback) => { callbacks.push(callback); return () => undefined; },
      isVisible: () => visible
    });
    visible = false;
    callbacks[0]();
    await flushPreloadQueue();
    expect(preload).not.toHaveBeenCalled();
  });
});
