import { env } from "cloudflare:test";
import { beforeEach, describe, expect, it } from "vitest";
import app from "../src/app";
import {
  checkRateLimit,
  resetRateLimits,
} from "../src/middleware/rate-limit";
import { activateTestSite, authHeaders, seedLicenseAndBucket } from "./helpers";

async function activateWithIp(ip: string): Promise<Response> {
  return app.request(
    "/v1/sites/activate",
    {
      method: "POST",
      headers: { "content-type": "application/json", "cf-connecting-ip": ip },
      body: JSON.stringify({
        license_key: "WPB-TEST-1111-2222",
        site_url: "https://example.com",
      }),
    },
    env,
  );
}

describe("checkRateLimit", () => {
  beforeEach(resetRateLimits);

  it("allows 120 requests per window then blocks", () => {
    const t0 = 1_000_000;
    for (let i = 0; i < 120; i++) {
      expect(checkRateLimit("site-a", t0 + i)).toBe(true);
    }
    expect(checkRateLimit("site-a", t0 + 500)).toBe(false);
  });

  it("resets after the 60s window and isolates sites", () => {
    const t0 = 1_000_000;
    for (let i = 0; i < 121; i++) checkRateLimit("site-a", t0);
    expect(checkRateLimit("site-a", t0)).toBe(false);
    expect(checkRateLimit("site-b", t0)).toBe(true);
    expect(checkRateLimit("site-a", t0 + 60_001)).toBe(true);
  });

  it("honors a custom max limit", () => {
    const t0 = 1_000_000;
    for (let i = 0; i < 10; i++) {
      expect(checkRateLimit("k", t0 + i, 10)).toBe(true);
    }
    expect(checkRateLimit("k", t0 + 11, 10)).toBe(false);
  });
});

describe("rate limited routes", () => {
  beforeEach(async () => {
    resetRateLimits();
    await seedLicenseAndBucket();
  });

  it("returns 429 with the uniform error shape once over the limit", async () => {
    const { site_token } = await activateTestSite();
    let last: Response | null = null;
    for (let i = 0; i < 121; i++) {
      last = await app.request("/v1/usage", { headers: authHeaders(site_token) }, env);
    }
    expect(last?.status).toBe(429);
    const body = (await last?.json()) as { error: { code: string } };
    expect(body.error.code).toBe("rate_limited");
  });
});

describe("activate rate limiting", () => {
  beforeEach(async () => {
    resetRateLimits();
    await seedLicenseAndBucket();
  });

  it("allows 10 activations per IP then blocks the 11th", async () => {
    for (let i = 0; i < 10; i++) {
      expect((await activateWithIp("203.0.113.1")).status).toBe(201);
    }
    const blocked = await activateWithIp("203.0.113.1");
    expect(blocked.status).toBe(429);
    const body = (await blocked.json()) as { error: { code: string } };
    expect(body.error.code).toBe("rate_limited");
  });

  it("keeps separate counters per cf-connecting-ip", async () => {
    for (let i = 0; i < 10; i++) {
      expect((await activateWithIp("198.51.100.1")).status).toBe(201);
    }
    // First IP is now exhausted...
    expect((await activateWithIp("198.51.100.1")).status).toBe(429);
    // ...but a different IP still has its full budget.
    expect((await activateWithIp("198.51.100.2")).status).toBe(201);
  });
});
