/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert';

import {
  acquireFederationLoadSlot,
  releaseFederationLoadSlot,
  __resetFederationLoadGate,
  __getFederationLoadGateStats,
} from './federationLoadGate.js';

describe('federationLoadGate', () => {
  beforeEach(() => {
    __resetFederationLoadGate();
  });

  it('admits a single load immediately regardless of size', async () => {
    const id = await acquireFederationLoadSlot(8000);
    assert.strictEqual(__getFederationLoadGateStats().activeCount, 1);
    releaseFederationLoadSlot(id);
    assert.strictEqual(__getFederationLoadGateStats().activeCount, 0);
  });

  it('admits two small loads concurrently when budget allows', async () => {
    const a = await acquireFederationLoadSlot(50);
    const b = await acquireFederationLoadSlot(50);
    const stats = __getFederationLoadGateStats();
    assert.strictEqual(stats.activeCount, 2);
    assert.strictEqual(stats.queuedCount, 0);
    releaseFederationLoadSlot(a);
    releaseFederationLoadSlot(b);
  });

  it('queues a second large load when the budget is full', async () => {
    const first = await acquireFederationLoadSlot(2048);
    let secondAcquired = false;
    const secondPromise = acquireFederationLoadSlot(2048).then((id) => {
      secondAcquired = true;
      return id;
    });
    await new Promise((r) => setTimeout(r, 10));
    assert.strictEqual(secondAcquired, false);
    assert.strictEqual(__getFederationLoadGateStats().queuedCount, 1);

    releaseFederationLoadSlot(first);
    const second = await secondPromise;
    assert.strictEqual(secondAcquired, true);
    releaseFederationLoadSlot(second);
  });

  it('FIFO order: first queued resolves first', async () => {
    const blocker = await acquireFederationLoadSlot(2048);

    const order: string[] = [];
    const aPromise = acquireFederationLoadSlot(2048).then((id) => { order.push('a'); return id; });
    const bPromise = acquireFederationLoadSlot(50).then((id) => { order.push('b'); return id; });

    await new Promise((r) => setTimeout(r, 10));
    // Releasing the blocker frees the budget for the head of the FIFO queue.
    // A 2048 MB load costs more than the whole budget, so the first-queued load
    // is admitted alone (single-file exception) and the 50 MB load stays queued
    // until it releases. Awaiting them together would deadlock — and asserting
    // that B does NOT wake alongside A is exactly what proves the gate respects
    // the budget during the drain (the regression this guards against).
    releaseFederationLoadSlot(blocker);

    const a = await aPromise;
    assert.deepStrictEqual(order, ['a']);
    assert.strictEqual(__getFederationLoadGateStats().queuedCount, 1);

    releaseFederationLoadSlot(a);
    const b = await bPromise;
    assert.deepStrictEqual(order, ['a', 'b']);
    releaseFederationLoadSlot(b);
  });

  it('release wakes multiple queued loads that fit together', async () => {
    const blocker = await acquireFederationLoadSlot(2048);
    const p1 = acquireFederationLoadSlot(50);
    const p2 = acquireFederationLoadSlot(50);

    await new Promise((r) => setTimeout(r, 10));
    assert.strictEqual(__getFederationLoadGateStats().queuedCount, 2);

    releaseFederationLoadSlot(blocker);
    const [id1, id2] = await Promise.all([p1, p2]);
    assert.strictEqual(__getFederationLoadGateStats().activeCount, 2);
    releaseFederationLoadSlot(id1);
    releaseFederationLoadSlot(id2);
  });

  it('treats negative file size as zero', async () => {
    const id = await acquireFederationLoadSlot(-100);
    assert.strictEqual(__getFederationLoadGateStats().activeCount, 1);
    releaseFederationLoadSlot(id);
  });
});
