/**
 * Health Checker
 *
 * Polls service healthcheck endpoints through the gateway's subdomain-based routing
 * to verify that preview services are ready.
 */

import type { PreviewConfig, HealthResult } from './types.js';
import { createModuleLogger } from '../logger/index.js';

const log = createModuleLogger('preview:health');

/** Default timeout for health checks (120 seconds) */
const DEFAULT_TIMEOUT_MS = 120_000;

/** Default polling interval (3 seconds) */
const DEFAULT_INTERVAL_MS = 3_000;

/**
 * Wait for all non-internal services to become healthy by polling their
 * healthcheck endpoints through the gateway's subdomain routing.
 *
 * @param previewId - The preview deployment ID (used as subdomain)
 * @param gatewayPort - The gateway's host port
 * @param config - Preview config with service definitions
 * @param timeoutMs - Maximum time to wait (default: 120s)
 * @returns Per-service health results
 */
export async function waitForHealthy(
  previewId: string,
  gatewayPort: number,
  config: PreviewConfig,
  timeoutMs = DEFAULT_TIMEOUT_MS,
): Promise<HealthResult[]> {
  const services = Object.values(config.services).filter((svc) => {
    if (svc.internal) return false;
    if (!svc.healthcheck?.path) return false;
    return true;
  });

  if (services.length === 0) {
    log.info('no healthcheck endpoints configured, skipping health wait');
    return [];
  }

  const deadline = Date.now() + timeoutMs;
  const results = new Map<string, HealthResult>();

  // Initialize all as unhealthy
  for (const svc of services) {
    results.set(svc.name, { name: svc.name, healthy: false });
  }

  while (Date.now() < deadline) {
    const pending = services.filter((svc) => !results.get(svc.name)!.healthy);
    if (pending.length === 0) break;

    await Promise.all(
      pending.map(async (svc) => {
        const route = svc.route ?? '/';
        const healthPath = svc.healthcheck!.path;
        const basePath = route === '/' ? '' : route;
        const url = `http://${previewId}.localhost:${gatewayPort}${basePath}${healthPath}`;

        try {
          const response = await fetch(url, {
            signal: AbortSignal.timeout(5000),
          });
          if (response.ok) {
            log.info({ service: svc.name, url }, 'service healthy');
            results.set(svc.name, { name: svc.name, healthy: true });
          }
        } catch {
          // Not ready yet
        }
      }),
    );

    const stillPending = services.filter((svc) => !results.get(svc.name)!.healthy);
    if (stillPending.length === 0) break;

    log.debug(
      { pending: stillPending.map((s) => s.name) },
      'waiting for services to become healthy',
    );
    await sleep(DEFAULT_INTERVAL_MS);
  }

  // Mark timed-out services
  for (const svc of services) {
    const result = results.get(svc.name)!;
    if (!result.healthy) {
      result.error = 'health check timed out';
      log.warn({ service: svc.name }, 'service health check timed out');
    }
  }

  return Array.from(results.values());
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
