/**
 * Bootstrap unit tests — focuses on the small pure helpers that don't
 * need a live ConfigManager / Registry / TCP listener.
 *
 * The full `start()` path is exercised indirectly via the gateway
 * integration / e2e tests; here we only need to lock in the timeout
 * behaviour of `closeServerWithTimeout`.
 */
import { describe, it, expect, vi } from 'vitest';
import { closeServerWithTimeout } from './bootstrap.js';

describe('closeServerWithTimeout', () => {
  it('resolves immediately when server.close() returns fast', async () => {
    const close = vi.fn((cb: () => void) => { setImmediate(cb); });
    const closeAll = vi.fn();
    const fakeServer = { close, closeAllConnections: closeAll };
    const t0 = Date.now();
    await closeServerWithTimeout(fakeServer, 5_000);
    const elapsed = Date.now() - t0;
    expect(close).toHaveBeenCalledTimes(1);
    expect(closeAll).not.toHaveBeenCalled();
    // Fast resolve — way under the 5s budget.
    expect(elapsed).toBeLessThan(200);
  });

  it('force-closes connections when server.close() exceeds the budget', async () => {
    // Server that never calls back — simulates a hung SSE.
    const close = vi.fn((_cb: () => void) => {
      // Intentionally never call cb — graceful close hangs forever.
    });
    const closeAll = vi.fn();
    const fakeServer = { close, closeAllConnections: closeAll };
    const t0 = Date.now();
    await closeServerWithTimeout(fakeServer, 50);
    const elapsed = Date.now() - t0;
    expect(close).toHaveBeenCalledTimes(1);
    expect(closeAll).toHaveBeenCalledTimes(1);
    // Should resolve at the budget, not block forever.
    expect(elapsed).toBeGreaterThanOrEqual(40);
    expect(elapsed).toBeLessThan(1_000);
  });

  it('does not double-resolve when close() callback fires after timeout', async () => {
    // Server callback fires after the budget — we should already have
    // resolved, and the late callback must not throw or cause a
    // double-resolve hang.
    let storedCb: (() => void) | undefined;
    const close = vi.fn((cb: () => void) => { storedCb = cb; });
    const closeAll = vi.fn();
    const fakeServer = { close, closeAllConnections: closeAll };

    await closeServerWithTimeout(fakeServer, 30);
    expect(closeAll).toHaveBeenCalledTimes(1);

    // Now simulate the late callback. Must not throw.
    expect(() => storedCb?.()).not.toThrow();
  });

  it('handles servers without closeAllConnections (older Node) gracefully', async () => {
    const close = vi.fn((_cb: () => void) => { /* hang */ });
    // No closeAllConnections method.
    const fakeServer = { close };
    // Should not throw even though force-close is unavailable.
    await closeServerWithTimeout(fakeServer, 30);
    expect(close).toHaveBeenCalledTimes(1);
  });

  it('catches a throwing closeAllConnections', async () => {
    const close = vi.fn((_cb: () => void) => { /* hang */ });
    const closeAll = vi.fn(() => { throw new Error('socket teardown blew up'); });
    const fakeServer = { close, closeAllConnections: closeAll };
    // Must not surface the throw — shutdown driver expects resolution.
    await expect(closeServerWithTimeout(fakeServer, 30)).resolves.toBeUndefined();
  });
});
