import { describe, expect, it } from "vitest";
import { createRateLimiter } from "../src/rate-limit.js";

describe("rate-limit", () => {
  it("accepts up to burst then rejects", () => {
    const lim = createRateLimiter({ perMinute: 60, burst: 3 });
    expect(lim.consume("u1", 0)).toEqual({ ok: true, remaining: 2 });
    expect(lim.consume("u1", 0)).toEqual({ ok: true, remaining: 1 });
    expect(lim.consume("u1", 0)).toEqual({ ok: true, remaining: 0 });
    const denied = lim.consume("u1", 0);
    expect(denied.ok).toBe(false);
    if (!denied.ok) {
      expect(denied.retryAfterMs).toBeGreaterThan(0);
      expect(denied.retryAfterMs).toBeLessThanOrEqual(1000);
    }
  });

  it("refills tokens over time", () => {
    const lim = createRateLimiter({ perMinute: 60, burst: 2 });
    expect(lim.consume("u1", 0).ok).toBe(true);
    expect(lim.consume("u1", 0).ok).toBe(true);
    expect(lim.consume("u1", 0).ok).toBe(false);
    // 1 second later, 1 token refilled
    expect(lim.consume("u1", 1000).ok).toBe(true);
    expect(lim.consume("u1", 1000).ok).toBe(false);
  });

  it("isolates buckets per id", () => {
    const lim = createRateLimiter({ perMinute: 60, burst: 1 });
    expect(lim.consume("u1", 0).ok).toBe(true);
    expect(lim.consume("u1", 0).ok).toBe(false);
    // u2 has its own bucket
    expect(lim.consume("u2", 0).ok).toBe(true);
    expect(lim.consume("u2", 0).ok).toBe(false);
  });

  it("caps refill at burst", () => {
    const lim = createRateLimiter({ perMinute: 60, burst: 2 });
    // Wait a long time, tokens should still be capped at 2
    const result = lim.consume("u1", 60_000);
    expect(result).toEqual({ ok: true, remaining: 1 });
    expect(lim.consume("u1", 60_000).ok).toBe(true);
    expect(lim.consume("u1", 60_000).ok).toBe(false);
  });

  it("reset clears a single bucket or all", () => {
    const lim = createRateLimiter({ perMinute: 60, burst: 1 });
    lim.consume("u1", 0);
    lim.consume("u2", 0);
    expect(lim.size()).toBe(2);
    lim.reset("u1");
    expect(lim.size()).toBe(1);
    expect(lim.consume("u1", 0).ok).toBe(true);
    lim.reset();
    expect(lim.size()).toBe(0);
  });
});
