import assert from 'node:assert/strict';
import test from 'node:test';
import {
  GENERIC_RATE_LIMIT_MAX_MS,
  LaunchThrottle,
  computeRateLimitUntil,
  genericRateLimitBackoffMs,
  randomLaunchGapMs,
} from '../src/launch-throttle.js';
import type { LaunchThrottleState } from '../src/storage.js';

function memoryStore(initial: LaunchThrottleState = { lastLaunchStartedAt: null, rateLimitUntil: null, rateLimitStrike: 0 }) {
  let state = { ...initial };
  return {
    load: async () => ({ ...state }),
    save: async (next: LaunchThrottleState) => { state = { ...next }; },
    read: () => ({ ...state }),
  };
}

test('launch gap is always between 9 and 12 seconds', () => {
  assert.equal(randomLaunchGapMs(() => 0), 9_000);
  assert.equal(randomLaunchGapMs(() => 0.999999999), 12_000);
});

test('second launch waits for jittered global spacing before reserving', async () => {
  let now = 100_000;
  const sleeps: number[] = [];
  const store = memoryStore({ lastLaunchStartedAt: now, rateLimitUntil: null, rateLimitStrike: 0 });
  const throttle = new LaunchThrottle(store, {
    random: () => 0,
    clock: { now: () => now, sleep: async (ms) => { sleeps.push(ms); now += ms; } },
  });
  await throttle.reserve(new Date(now + 30_000).toISOString());
  assert.deepEqual(sleeps, [9_000]);
  assert.equal(store.read().lastLaunchStartedAt, 109_000);
});

test('explicit ChatGPT reset time dominates generic backoff', () => {
  const now = Date.parse('2026-08-21T08:00:00.000Z');
  const reset = '2026-08-21T08:45:00.000Z';
  assert.equal(computeRateLimitUntil(now, 1, reset), Date.parse(reset));
});

test('generic rate-limit backoff grows and caps at five minutes', () => {
  assert.equal(genericRateLimitBackoffMs(1), 5 * 60_000);
  assert.equal(genericRateLimitBackoffMs(2), 5 * 60_000);
  assert.equal(genericRateLimitBackoffMs(10), GENERIC_RATE_LIMIT_MAX_MS);
});

test('durable rate-limit state blocks a short-deadline launch', async () => {
  let now = 100_000;
  const store = memoryStore();
  const throttle = new LaunchThrottle(store, {
    clock: { now: () => now, sleep: async (ms) => { now += ms; } },
  });
  await throttle.markRateLimited(null);
  assert.equal(store.read().rateLimitStrike, 1);
  await assert.rejects(throttle.reserve(new Date(now + 5_000).toISOString()), /launch is throttled/i);
});
