import { describe, expect, it, vi, afterEach } from 'vitest';
import { createLimiter, extractionLimiter } from '../llmRetry';

afterEach(() => { vi.useRealTimers(); });

describe('createLimiter', () => {
  it('rejects an invalid rps', () => {
    expect(() => createLimiter({ rps: 0 })).toThrow();
    expect(() => createLimiter({ rps: -1 })).toThrow();
  });

  it('rejects an invalid burst', () => {
    expect(() => createLimiter({ rps: 1, burst: 0 })).toThrow();
  });

  it('lets the first call through immediately (initial burst token)', async () => {
    const limiter = createLimiter({ rps: 1, burst: 1 });
    const start = Date.now();
    await limiter.acquire();
    expect(Date.now() - start).toBeLessThan(50);
  });

  it('queues subsequent calls to respect the spacing', async () => {
    vi.useFakeTimers();
    const limiter = createLimiter({ rps: 10, burst: 1 }); // 100ms spacing
    await limiter.acquire(); // consume initial token

    let resolved = false;
    const p = limiter.acquire().then(() => { resolved = true; });

    await vi.advanceTimersByTimeAsync(50);
    expect(resolved).toBe(false);

    await vi.advanceTimersByTimeAsync(100);
    await p;
    expect(resolved).toBe(true);
  });

  it('honours pauseUntil — no acquire returns before the pause expires', async () => {
    vi.useFakeTimers();
    const limiter = createLimiter({ rps: 100, burst: 5 });
    limiter.pauseUntil(Date.now() + 1000);

    let resolved = false;
    const p = limiter.acquire().then(() => { resolved = true; });

    await vi.advanceTimersByTimeAsync(500);
    expect(resolved).toBe(false);

    await vi.advanceTimersByTimeAsync(600);
    await p;
    expect(resolved).toBe(true);
  });

  it('aborts a queued acquire when the signal fires', async () => {
    const limiter = createLimiter({ rps: 1, burst: 1 });
    await limiter.acquire(); // consume

    const ctl = new AbortController();
    const p = limiter.acquire({ signal: ctl.signal });
    ctl.abort();

    await expect(p).rejects.toBeInstanceOf(DOMException);
  });
});

describe('extractionLimiter', () => {
  it('is exported and exposes the limiter shape', () => {
    expect(typeof extractionLimiter.acquire).toBe('function');
    expect(typeof extractionLimiter.pauseUntil).toBe('function');
  });
});
