/**
 * Test server utilities
 * Helper functions for creating test servers and waiting for readiness
 */

import { createConnection } from 'net';
import { createServer } from 'net';

/**
 * Ask the OS for an available ephemeral port.
 *
 * This avoids flaky collisions from pseudo-random fixed ranges.
 */
export async function getAvailablePort(host = '127.0.0.1'): Promise<number> {
  return new Promise((resolve, reject) => {
    const server = createServer();
    server.unref();

    server.on('error', reject);
    server.listen(0, host, () => {
      const address = server.address();
      if (!address || typeof address === 'string') {
        server.close(() => reject(new Error('Failed to resolve ephemeral port')));
        return;
      }

      const { port } = address;
      server.close((err) => {
        if (err) {
          reject(err);
          return;
        }
        resolve(port);
      });
    });
  });
}

/**
 * Wait for a specified duration
 */
export function wait(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Create a promise that rejects after a timeout
 */
export function timeoutPromise<T>(ms: number, message = 'Timeout'): Promise<T> {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error(message)), ms);
  });
}

/**
 * Race a promise against a timeout
 */
export function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T> {
  return Promise.race([promise, timeoutPromise<T>(ms, message)]);
}

/**
 * Wait for HTTP server to be ready to accept connections
 *
 * Uses lightweight TCP connection probes with exponential backoff to ensure
 * the server's kernel-level TCP listener is ready. This prevents ECONNREFUSED
 * errors on slower CI environments where there's a small delay between
 * listen() callback firing and actual connection readiness.
 *
 * Performance: ~1-2ms on local machines (typical), up to ~5s on slow CI
 *
 * @param port - Port number to check
 * @param maxAttempts - Maximum retry attempts (default: 15)
 * @param initialDelayMs - Initial delay between retries (default: 10ms)
 * @returns Promise that resolves when server is ready
 * @throws Error if server not ready after max attempts
 */
export async function waitForHttpServer(
  port: number,
  maxAttempts = 15,
  initialDelayMs = 10
): Promise<void> {
  let lastError: Error | undefined;

  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      await new Promise<void>((resolve, reject) => {
        const socket = createConnection({ port, host: '127.0.0.1' }, () => {
          socket.end();
          resolve();
        });

        socket.on('error', (err) => {
          lastError = err;
          socket.destroy();
          reject(err);
        });

        socket.setTimeout(1000);
        socket.on('timeout', () => {
          socket.destroy();
          reject(new Error('Connection timeout'));
        });
      });

      return; // Success!
    } catch {
      if (attempt < maxAttempts - 1) {
        const delay = initialDelayMs * Math.pow(2, attempt);
        await wait(delay);
      }
    }
  }

  throw new Error(
    `Server not ready after ${maxAttempts} attempts. Last error: ${lastError?.message || 'unknown'}`
  );
}
