import { describe, expect, it } from 'vitest';
import { Code, ConnectError } from '@connectrpc/connect';
import { maintenanceRetryMs, parseRetryAfterMs } from './maintenance';

// A raw ConnectError as connect-web builds it from a non-Connect HTTP 503: the response
// headers are preserved as `.metadata` (verified against @connectrpc/connect-web).
const err503 = (headers: Record<string, string>): ConnectError =>
  new ConnectError('HTTP 503', Code.Unavailable, new Headers(headers));

describe('maintenanceRetryMs', () => {
  it('detects the marker and parses Retry-After', () => {
    expect(maintenanceRetryMs(err503({ 'x-scrabble-maintenance': '1', 'retry-after': '120' }))).toBe(120_000);
  });

  it('matches the header case-insensitively, defaulting the delay when Retry-After is absent', () => {
    expect(maintenanceRetryMs(err503({ 'X-Scrabble-Maintenance': '1' }))).toBe(15_000);
  });

  it('returns null for a 503 without the marker (a plain transient failure)', () => {
    expect(maintenanceRetryMs(err503({ 'retry-after': '30' }))).toBeNull();
  });

  it('returns null for non-Connect errors', () => {
    expect(maintenanceRetryMs(new Error('boom'))).toBeNull();
    expect(maintenanceRetryMs(undefined)).toBeNull();
    expect(maintenanceRetryMs('nope')).toBeNull();
  });
});

describe('parseRetryAfterMs', () => {
  it('clamps into the 3s–120s band', () => {
    expect(parseRetryAfterMs('120')).toBe(120_000);
    expect(parseRetryAfterMs('1')).toBe(3_000); // floor
    expect(parseRetryAfterMs('9999')).toBe(120_000); // ceiling
  });

  it('falls back to the default on absent / garbage / non-positive', () => {
    expect(parseRetryAfterMs(null)).toBe(15_000);
    expect(parseRetryAfterMs(undefined)).toBe(15_000);
    expect(parseRetryAfterMs('soon')).toBe(15_000);
    expect(parseRetryAfterMs('0')).toBe(15_000);
    expect(parseRetryAfterMs('-5')).toBe(15_000);
  });
});
