import axios from 'axios';
import { ZoomApiService } from './zoom-api.service';
import { ZOOM_ENV_KEYS } from 'src/common/constants/zoom.constants';

jest.mock('axios');

describe('ZoomApiService transient-failure handling', () => {
  const mockedAxios = axios as jest.Mocked<typeof axios>;
  const logger = { log: jest.fn(), warn: jest.fn(), error: jest.fn() } as any;
  const oauth = {
    getAccessToken: jest.fn().mockResolvedValue('token-abc'),
    invalidate: jest.fn(),
  } as any;
  const config = {
    get: (key: string) => (key === ZOOM_ENV_KEYS.ENABLE ? 'true' : undefined),
  } as any;

  const makeService = () => new ZoomApiService(config, oauth, logger);

  const httpError = (status: number, headers: Record<string, unknown> = {}) => ({
    response: { status, headers, data: { message: `err-${status}` } },
  });

  beforeEach(() => {
    jest.clearAllMocks();
    // Run backoff timers instantly so the suite does not actually sleep.
    jest.spyOn(global, 'setTimeout').mockImplementation(((fn: () => void) => {
      fn();
      return 0 as unknown as NodeJS.Timeout;
    }) as any);
  });

  afterEach(() => jest.restoreAllMocks());

  it('retries a 429 and succeeds on a later attempt', async () => {
    mockedAxios.request
      .mockRejectedValueOnce(httpError(429))
      .mockResolvedValueOnce({ data: { ok: true } } as any);
    const result = await makeService().fetchPanelists('w1');
    // fetchPanelists walks pages; the single page returns no collection → [].
    expect(result).toEqual([]);
    expect(mockedAxios.request).toHaveBeenCalledTimes(2);
  });

  it('retries a 5xx on an idempotent GET', async () => {
    mockedAxios.request
      .mockRejectedValueOnce(httpError(503))
      .mockResolvedValueOnce({ data: { participants: [{ id: 'p1' }] } } as any);
    const result = await makeService().fetchParticipants('w1');
    expect(result).toEqual([{ id: 'p1' }]);
    expect(mockedAxios.request).toHaveBeenCalledTimes(2);
  });

  it('does NOT retry a 5xx on a non-idempotent POST (avoids duplicate create)', async () => {
    mockedAxios.request.mockRejectedValue(httpError(503));
    await expect(
      makeService().createWebinar({ title: 't', startAt: '', duration: 60 } as any, 'host@x.com'),
    ).rejects.toBeDefined();
    expect(mockedAxios.request).toHaveBeenCalledTimes(1);
  });

  it('refreshes the token once on a 401 then retries', async () => {
    mockedAxios.request
      .mockRejectedValueOnce(httpError(401))
      .mockResolvedValueOnce({ data: {} } as any);
    await makeService().fetchPanelists('w1');
    expect(oauth.invalidate).toHaveBeenCalledTimes(1);
    expect(mockedAxios.request).toHaveBeenCalledTimes(2);
  });

  it('gives up after the configured number of transient attempts', async () => {
    mockedAxios.request.mockRejectedValue(httpError(429));
    await expect(makeService().fetchParticipants('w1')).rejects.toBeDefined();
    // ZOOM_RETRY.MAX_ATTEMPTS = 3 → initial try + 2 retries.
    expect(mockedAxios.request).toHaveBeenCalledTimes(3);
  });
});
