import { spawn } from "node:child_process";
import { closeSync, openSync } from "node:fs";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";

type ServiceName = "api" | "worker" | "web";
const SERVICE_NAMES: ServiceName[] = ["api", "worker", "web"];

export async function workbenchNativeServicesStatus(input: { cwd: string; env: Record<string, string | undefined> }) {
  const results = await Promise.all(SERVICE_NAMES.map((service) => workbenchNativeServiceCommand({
    service,
    action: "status",
    cwd: input.cwd,
    env: input.env
  })));
  const services = Object.fromEntries(results.map((result) => [result.service, result]));
  const runningCount = results.filter((result) => result.ok).length;
  return {
    ok: runningCount === SERVICE_NAMES.length,
    operation: "workbench.service.status",
    status: runningCount === SERVICE_NAMES.length ? "running" : runningCount === 0 ? "stopped" : "degraded",
    summary: {
      total: SERVICE_NAMES.length,
      running: runningCount,
      unavailable: SERVICE_NAMES.length - runningCount
    },
    services,
    valuesPrinted: false
  };
}

export async function workbenchNativeServiceCommand(input: { service: string; action: string; cwd: string; env: Record<string, string | undefined> }) {
  const service = validService(input.service);
  const stateDir = path.resolve(input.cwd, input.env.WORKBENCH_NATIVE_SERVICE_STATE_DIR ?? ".state/workbench-native/services");
  const stateFile = path.join(stateDir, `${service}.json`);
  const logFile = path.join(stateDir, `${service}.log`);
  if (input.action === "status") return status(service, stateFile, logFile);
  if (input.action === "logs") return logs(service, stateFile, logFile);
  if (input.action === "stop") return stop(service, stateFile, logFile);
  if (input.action === "restart") {
    await stop(service, stateFile, logFile);
    return start(service, stateDir, stateFile, logFile, input.cwd, input.env);
  }
  if (input.action === "start") return start(service, stateDir, stateFile, logFile, input.cwd, input.env);
  throw codedError("unsupported_service_action", `unsupported Workbench service action: ${input.action}`);
}

async function start(service: ServiceName, stateDir: string, stateFile: string, logFile: string, cwd: string, env: Record<string, string | undefined>) {
  const current = await readState(stateFile);
  if (current?.pid && alive(current.pid)) throw codedError("service_already_running", `${service} is already running`, { pid: current.pid, correctionCommand: `hwlab-cli workbench service ${service} stop` });
  await mkdir(stateDir, { recursive: true });
  const endpoints = serviceEndpoints(service, env);
  const command = serviceCommand(service, env);
  const logFd = openSync(logFile, "w");
  const child = spawn(command[0], command.slice(1), {
    cwd,
    env: serviceEnv(service, env),
    detached: true,
    stdio: ["ignore", logFd, logFd]
  });
  closeSync(logFd);
  child.unref();
  const state = { service, pid: child.pid, status: "starting", command, cwd, logFile, endpoints, healthUrl: endpoints.probe?.url ?? null, startedAt: new Date().toISOString() };
  await writeFile(stateFile, `${JSON.stringify(state, null, 2)}\n`, "utf8");
  return { ok: true, operation: `workbench.service.${service}.start`, ...state, nextCommand: `hwlab-cli workbench service ${service} status` };
}

async function stop(service: ServiceName, stateFile: string, logFile: string) {
  const state = await readState(stateFile);
  if (!state?.pid || !alive(state.pid)) { await rm(stateFile, { force: true }); return { ok: true, operation: `workbench.service.${service}.stop`, service, status: "stopped", alreadyStopped: true, logFile }; }
  try { process.kill(-state.pid, "SIGTERM"); } catch (error: any) { if (error?.code !== "ESRCH") throw error; }
  await rm(stateFile, { force: true });
  return { ok: true, operation: `workbench.service.${service}.stop`, service, status: "stopped", pid: state.pid, logFile };
}

async function status(service: ServiceName, stateFile: string, logFile: string) {
  const state = await readState(stateFile);
  const processRunning = Boolean(state?.pid && alive(state.pid));
  const health = processRunning && state?.healthUrl ? await probeHealth(state.healthUrl) : { ok: false, status: null, error: processRunning ? "health-url-not-declared" : "process-not-running" };
  const running = processRunning && health.ok;
  return { ok: running, operation: `workbench.service.${service}.status`, service, status: running ? "running" : processRunning ? "degraded" : "stopped", pid: processRunning ? state.pid : null, endpoints: state?.endpoints ?? null, health, stateFile, logFile, nextCommand: `hwlab-cli workbench service ${service} logs` };
}

async function logs(service: ServiceName, stateFile: string, logFile: string) {
  const state = await status(service, stateFile, logFile);
  const lines = (await readFile(logFile, "utf8").catch(() => "")).split("\n").filter(Boolean).slice(-80);
  return { ...state, operation: `workbench.service.${service}.logs`, lines, truncated: lines.length === 80 };
}

function serviceCommand(service: ServiceName, env: Record<string, string | undefined>) {
  const watch = env.WORKBENCH_BACKEND_WATCH === "1" ? ["--watch"] : [];
  if (service === "api") return ["bun", ...watch, "cmd/hwlab-workbench-api/main.ts"];
  if (service === "worker") return ["bun", ...watch, "cmd/hwlab-workbench-worker/main.ts"];
  return ["bun", "run", "workbench:web:dev"];
}
function serviceEnv(service: ServiceName, env: Record<string, string | undefined>) {
  if (service === "api") return { ...process.env, ...env, WORKBENCH_MODE: env.WORKBENCH_MODE ?? "native-test", WORKBENCH_API_HOST: requiredEnv(env, "WORKBENCH_API_BIND_HOST"), WORKBENCH_API_PORT: requiredEnv(env, "WORKBENCH_API_PORT") };
  if (service === "web") return { ...process.env, ...env, WORKBENCH_WEB_HOST: requiredEnv(env, "WORKBENCH_WEB_BIND_HOST"), WORKBENCH_WEB_PORT: requiredEnv(env, "WORKBENCH_WEB_PORT"), WORKBENCH_NATIVE_API_URL: requiredEnv(env, "WORKBENCH_NATIVE_API_URL"), WORKBENCH_WEB_RUNTIME_CONFIG: requiredEnv(env, "WORKBENCH_WEB_RUNTIME_CONFIG") };
  return { ...process.env, ...env, WORKBENCH_MODE: env.WORKBENCH_MODE ?? "temporal", WORKBENCH_WORKER_HEALTH_HOST: requiredEnv(env, "WORKBENCH_WORKER_BIND_HOST"), WORKBENCH_WORKER_HEALTH_PORT: requiredEnv(env, "WORKBENCH_WORKER_HEALTH_PORT") };
}
function validService(value: string): ServiceName { if (value === "api" || value === "worker" || value === "web") return value; throw codedError("invalid_service", "service must be api, worker, or web"); }
async function readState(file: string) { return JSON.parse(await readFile(file, "utf8").catch(() => "null")); }
function alive(pid: number) { try { process.kill(pid, 0); return true; } catch { return false; } }
function serviceEndpoints(service: ServiceName, env: Record<string, string | undefined>) {
  if (service === "worker") {
    const bindHost = requiredEnv(env, "WORKBENCH_WORKER_BIND_HOST");
    const probeHost = requiredEnv(env, "WORKBENCH_WORKER_PROBE_HOST");
    const port = requiredEnv(env, "WORKBENCH_WORKER_HEALTH_PORT");
    return { bind: { host: bindHost, port: validPort(port) }, probe: endpoint(probeHost, port, "/health/ready"), public: null };
  }
  const prefix = service === "api" ? "WORKBENCH_API" : "WORKBENCH_WEB";
  const bindHost = requiredEnv(env, `${prefix}_BIND_HOST`);
  const probeHost = requiredEnv(env, `${prefix}_PROBE_HOST`);
  const publicBaseUrl = fixedHttpsOrigin(requiredEnv(env, "WORKBENCH_PUBLIC_BASE_URL"), "WORKBENCH_PUBLIC_BASE_URL");
  const port = requiredEnv(env, `${prefix}_PORT`);
  const pathname = service === "api" ? "/health/ready" : "/workbench";
  return {
    bind: { host: bindHost, port: validPort(port) },
    probe: endpoint(probeHost, port, pathname),
    public: { url: service === "api" ? publicBaseUrl : `${publicBaseUrl}${pathname}` }
  };
}
function endpoint(host: string, port: string, pathname: string) { const numericPort = validPort(port); return { host, port: numericPort, url: `http://${host}:${numericPort}${pathname}` }; }
function validPort(value: string) { const port = Number(value); if (!Number.isInteger(port) || port < 1 || port > 65535) throw codedError("invalid_service_port", `invalid Workbench service port: ${value}`); return port; }
function requiredEnv(env: Record<string, string | undefined>, key: string) { const value = String(env[key] ?? "").trim(); if (!value) throw codedError("native_exposure_config_required", `${key} is required from the owning YAML native development exposure`); return value; }
function fixedHttpsOrigin(value: string, key: string) {
  try {
    const parsed = new URL(value);
    if (parsed.protocol === "https:" && parsed.pathname === "/" && !parsed.search && !parsed.hash && !parsed.port) return parsed.origin;
  } catch {
    // Report one stable configuration error below.
  }
  throw codedError("native_exposure_config_invalid", `${key} must be a pathless HTTPS origin`);
}
async function probeHealth(url: string) {
  try { const response = await fetch(url, { signal: AbortSignal.timeout(1000) }); return { ok: response.ok, status: response.status, url }; }
  catch (error) { return { ok: false, status: null, url, error: error instanceof Error ? error.message : String(error) }; }
}
function codedError(code: string, message: string, details?: unknown) { return Object.assign(new Error(message), { code, details }); }
