// test/render-limiter.test.ts — sémaphore de rendus headless (revue T2 du
// plan S7). Pur : un `RenderProvider` fake (pas de Chrome, pas de DB), on
// vérifie juste que jamais plus de `max` appels ne sont en vol simultanément,
// même quand 8 appels sont lancés d'un coup.
import { expect, test } from "bun:test";
import {
  limitRenderConcurrency,
  MAX_CONCURRENT_RENDERS,
} from "../src/render-limiter";

test("MAX_CONCURRENT_RENDERS = 2 (cap documenté)", () => {
  expect(MAX_CONCURRENT_RENDERS).toBe(2);
});

test("limitRenderConcurrency: jamais plus de `max` appels en vol, sur 8 appels simulés", async () => {
  let inFlight = 0;
  let peak = 0;

  const fakeProvider = async (url: string) => {
    inFlight++;
    peak = Math.max(peak, inFlight);
    // Laisse une chance au scheduler de démarrer d'autres appels en attente
    // avant de résoudre — sans ce délai, un provider synchrone masquerait un
    // bug de comptage (aucun appel ne se chevaucherait jamais).
    await new Promise((r) => setTimeout(r, 5));
    inFlight--;
    return { html: `<html>${url}</html>`, finalUrl: url };
  };

  const limited = limitRenderConcurrency(fakeProvider, 2);

  const urls = Array.from({ length: 8 }, (_, i) => `https://example.com/${i}`);
  const results = await Promise.all(urls.map((u) => limited(u)));

  expect(results).toHaveLength(8);
  for (const [i, r] of results.entries()) {
    expect(r?.finalUrl).toBe(urls[i]);
  }
  expect(peak).toBeLessThanOrEqual(2);
  expect(peak).toBeGreaterThan(0);
});

test("limitRenderConcurrency: un rendu qui échoue (throw) libère quand même son slot", async () => {
  let inFlight = 0;
  let peak = 0;
  let calls = 0;

  const flakyProvider = async (url: string) => {
    calls++;
    inFlight++;
    peak = Math.max(peak, inFlight);
    await new Promise((r) => setTimeout(r, 2));
    inFlight--;
    if (url.endsWith("/1") || url.endsWith("/3")) {
      throw new Error("render failed");
    }
    return { html: "<html></html>", finalUrl: url };
  };

  const limited = limitRenderConcurrency(flakyProvider, 2);
  const urls = Array.from({ length: 8 }, (_, i) => `https://example.com/${i}`);

  const settled = await Promise.allSettled(urls.map((u) => limited(u)));

  expect(calls).toBe(8);
  expect(peak).toBeLessThanOrEqual(2);
  const rejected = settled.filter((s) => s.status === "rejected");
  expect(rejected).toHaveLength(2);
});

test("limitRenderConcurrency: par défaut applique MAX_CONCURRENT_RENDERS", async () => {
  let inFlight = 0;
  let peak = 0;
  const provider = async (url: string) => {
    inFlight++;
    peak = Math.max(peak, inFlight);
    await new Promise((r) => setTimeout(r, 5));
    inFlight--;
    return { html: "<html></html>", finalUrl: url };
  };

  const limited = limitRenderConcurrency(provider);
  await Promise.all(
    Array.from({ length: 6 }, (_, i) => limited(`https://example.com/${i}`)),
  );

  expect(peak).toBeLessThanOrEqual(MAX_CONCURRENT_RENDERS);
});
