import net from "node:net";

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

export async function probeHttp(url, options = {}) {
  const timeoutMs = options.timeoutMs ?? 5000;
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const response = await fetch(url, { signal: controller.signal, redirect: "manual" });
    return { ok: response.status >= 200 && response.status < 400, status: response.status };
  } catch (error) {
    return { ok: false, status: null, error: error?.message ?? String(error) };
  } finally {
    clearTimeout(timer);
  }
}

export async function probeTcp(host, port, options = {}) {
  const timeoutMs = options.timeoutMs ?? 5000;
  return new Promise((resolve) => {
    const socket = net.connect({ host, port });
    const timer = setTimeout(() => {
      socket.destroy();
      resolve({ ok: false, error: "timeout" });
    }, timeoutMs);
    socket.once("connect", () => {
      clearTimeout(timer);
      socket.end();
      resolve({ ok: true });
    });
    socket.once("error", (error) => {
      clearTimeout(timer);
      resolve({ ok: false, error: error.message });
    });
  });
}

async function resolveProcessAlive(config) {
  if (typeof config.checkProcessAlive === "function") {
    return Boolean(await config.checkProcessAlive());
  }
  return Boolean(config.processAlive);
}

export async function collectDeploymentHealth(config, options = {}) {
  const probeTimeoutMs = options.probeTimeoutMs ?? 5000;
  const processAlive = await resolveProcessAlive(config);
  const processStatus = processAlive ? "running" : "stopped";

  const health = await probeHttp(`${config.apiBaseUrl}/healthz`, { timeoutMs: probeTimeoutMs });
  const ready = await probeHttp(`${config.apiBaseUrl}/ready`, { timeoutMs: probeTimeoutMs });
  const web = await probeHttp(config.webUrl, { timeoutMs: probeTimeoutMs });

  const apiHealth = health.ok && health.status === 200 ? "healthy" : "unhealthy";
  const apiReady = ready.ok && ready.status === 200 ? "ready" : "unhealthy";
  const webStatus = web.ok ? "reachable" : "unreachable";
  const ok =
    processStatus === "running" &&
    apiHealth === "healthy" &&
    apiReady === "ready" &&
    webStatus === "reachable";

  return {
    process: processStatus,
    apiHealth,
    apiReady,
    web: webStatus,
    ok
  };
}

export async function waitForDeployment(config, options = {}) {
  const intervalMs = options.intervalMs ?? 1000;
  const timeoutMs = options.timeoutMs ?? 60_000;
  const deadline = Date.now() + timeoutMs;
  let summary;

  while (Date.now() <= deadline) {
    summary = await collectDeploymentHealth(config, options);
    if (summary.ok) return summary;
    await sleep(intervalMs);
  }

  const error = new Error(
    `Deployment health check timed out: process=${summary.process} apiHealth=${summary.apiHealth} apiReady=${summary.apiReady} web=${summary.web}`
  );
  error.summary = summary;
  throw error;
}