import { afterEach, describe, expect, it, vi } from 'vitest';

describe('progress websocket client', () => {
  afterEach(() => {
    vi.useRealTimers();
    vi.restoreAllMocks();
    vi.resetModules();
    vi.unstubAllGlobals();
  });

  it('connects using the configured URL, reports open state, and sends heartbeat pings', async () => {
    vi.useFakeTimers();
    const instances: any[] = [];
    class FakeWebSocket {
      static CONNECTING = 0;
      static OPEN = 1;
      readyState = FakeWebSocket.OPEN;
      onopen?: () => void;
      onmessage?: (event: MessageEvent) => void;
      onclose?: () => void;
      onerror?: () => void;
      constructor(public url: string) {
        instances.push(this);
      }
      close = vi.fn();
      send = vi.fn();
    }
    vi.stubGlobal('WebSocket', FakeWebSocket);

    const { progressWS } = await import('./websocket');
    const statusCallback = vi.fn();
    const unsubscribeStatus = progressWS.onStatus(statusCallback);
    progressWS.connect();
    instances[0].onopen?.();

    expect(instances[0].url).toContain('/ws/progress');
    expect(progressWS.isConnected()).toBe(true);
    expect(statusCallback).toHaveBeenCalledWith('connected');
    expect(instances[0].send).toHaveBeenCalledWith('ping');

    vi.advanceTimersByTime(15000);
    expect(instances[0].send).toHaveBeenCalledTimes(2);

    unsubscribeStatus();
    progressWS.disconnect();
  });

  it('subscribes and unsubscribes progress callbacks', async () => {
    const { progressWS } = await import('./websocket');
    const callback = vi.fn();

    const unsubscribe = progressWS.onProgress(callback);
    (progressWS as any).callbacks.forEach((cb: any) => cb({ type: 'status', message: 'ok' }));
    unsubscribe();
    (progressWS as any).callbacks.forEach((cb: any) => cb({ type: 'status', message: 'again' }));

    expect(callback).toHaveBeenCalledTimes(1);
    expect(callback).toHaveBeenCalledWith({ type: 'status', message: 'ok' });
  });

  it('notifies connection status changes and schedules reconnect on close', async () => {
    vi.useFakeTimers();
    const instances: any[] = [];
    class FakeWebSocket {
      static CONNECTING = 0;
      static OPEN = 1;
      readyState = FakeWebSocket.OPEN;
      onopen?: () => void;
      onmessage?: (event: MessageEvent) => void;
      onclose?: () => void;
      onerror?: () => void;
      constructor(public url: string) {
        instances.push(this);
      }
      close = vi.fn();
      send = vi.fn();
    }
    vi.stubGlobal('WebSocket', FakeWebSocket);

    const { progressWS } = await import('./websocket');
    const statusCallback = vi.fn();
    const unsubscribeStatus = progressWS.onStatus(statusCallback);

    progressWS.connect();
    instances[0].onopen?.();
    instances[0].onclose?.();

    expect(statusCallback).toHaveBeenCalledWith('disconnected');
    expect(statusCallback).toHaveBeenCalledWith('reconnecting');

    vi.advanceTimersByTime(3000);
    expect(instances).toHaveLength(2);

    unsubscribeStatus();
    progressWS.disconnect();
  });
});
