#!/usr/bin/env bash
# Ensures the harness control API is running. Prints the base URL to stdout.
set -euo pipefail

HARNESS_HOME="${HARNESS_HOME:-${HOME}/.harness}"
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
LOG_FILE="${HARNESS_HOME}/control-api.log"
PID_FILE="${HARNESS_HOME}/control-api.pid"
PORT_FILE="${HARNESS_HOME}/control-api.port"
TOKEN_FILE="${HARNESS_HOME}/token"
FINGERPRINT_FILE="${HARNESS_HOME}/control-api.fingerprint"
LOCK_FILE="${HARNESS_HOME}/control-api.ensure.lock"

control_api_fingerprint() {
  {
    find "${REPO_ROOT}/src" -type f -name '*.js' ! -name '*.test.js' ! -path '*/test/*' -print0 \
      | sort -z \
      | xargs -0 sha256sum
    sha256sum "${REPO_ROOT}/bin/ensure-control-api.sh"
  } | sha256sum | awk '{print $1}'
}

stop_saved_control_api() {
  [[ -f "${PID_FILE}" ]] || return 0
  local pid cwd command
  pid="$(cat "${PID_FILE}")"
  [[ "${pid}" =~ ^[0-9]+$ ]] || return 0
  [[ -d "/proc/${pid}" ]] || return 0
  cwd="$(readlink -f "/proc/${pid}/cwd" 2>/dev/null || true)"
  command="$(tr '\0' ' ' < "/proc/${pid}/cmdline" 2>/dev/null || true)"
  [[ "${cwd}" == "${REPO_ROOT}" && "${command}" == *'node src/control-api.js --port '* ]] || return 0
  kill "${pid}"
  for _ in $(seq 1 20); do
    kill -0 "${pid}" 2>/dev/null || return 0
    sleep 0.1
  done
  return 1
}

read_control_api_token() {
  if [[ -f "${TOKEN_FILE}" ]]; then
    tr -d '\r\n' < "${TOKEN_FILE}"
  fi
}

probe_control_api() {
  local port="$1"
  local token="${2:-}"
  node - "$port" "$token" <<'NODE'
const http = require('node:http');

const port = Number(process.argv[2] || 0);
const token = String(process.argv[3] || '');
const req = http.get({
  host: '127.0.0.1',
  port,
  path: '/health',
  timeout: 2000,
  headers: token ? { authorization: `Bearer ${token}` } : {},
}, res => {
  let raw = '';
  res.setEncoding('utf8');
  res.on('data', chunk => {
    raw += chunk;
  });
  res.on('end', () => {
    if (res.statusCode !== 200) {
      process.exit(1);
      return;
    }
    try {
      const parsed = JSON.parse(raw);
      process.exit(parsed && parsed.service === 'mega-plan-harness-control-api' ? 0 : 1);
    } catch (_error) {
      process.exit(1);
    }
  });
});

req.on('timeout', () => {
  req.destroy(new Error('timeout'));
});
req.on('error', () => {
  process.exit(1);
});
NODE
}

port_in_use() {
  local port="$1"
  node - "$port" <<'NODE'
const net = require('node:net');

const port = Number(process.argv[2] || 0);
const socket = net.createConnection({ host: '127.0.0.1', port });

socket.on('connect', () => {
  socket.destroy();
  process.exit(0);
});
socket.on('error', () => {
  process.exit(1);
});
NODE
}

find_free_port() {
  local port="${HARNESS_CONTROL_API_PORT:-4973}"
  while port_in_use "${port}"; do
    port=$((port + 1))
  done
  echo "${port}"
}

mkdir -p "${HARNESS_HOME}"
exec 9>"${LOCK_FILE}"
# Bounded wait: the critical section below tops out around 15s (30 probes x
# 0.5s), so a longer wait means the lock is wedged, not busy.
flock -w 60 -x 9 || {
  echo "ERROR: ensure lock wedged (held >60s): ${LOCK_FILE}" >&2
  exit 1
}

expected_fingerprint="$(control_api_fingerprint)"
saved_fingerprint="$(cat "${FINGERPRINT_FILE}" 2>/dev/null || true)"

if [[ -f "${PORT_FILE}" && "${saved_fingerprint}" == "${expected_fingerprint}" ]]; then
  saved_port="$(cat "${PORT_FILE}")"
  if probe_control_api "${saved_port}" "$(read_control_api_token)"; then
    echo "http://127.0.0.1:${saved_port}"
    exit 0
  fi
fi

if [[ "${saved_fingerprint}" != "${expected_fingerprint}" ]]; then
  stop_saved_control_api || {
    echo "ERROR: stale control API did not stop" >&2
    exit 1
  }
  rm -f "${PID_FILE}" "${PORT_FILE}" "${FINGERPRINT_FILE}"
fi

PORT="$(find_free_port)"
cd "${REPO_ROOT}"
# 9>&-: the daemon must not inherit the ensure-lock fd, or it holds the global
# lock for its whole lifetime and every later ensure wedges behind it.
# Keep the control API outside the runner's process group.  The runner may
# terminate its supervised process tree during reconcile; the API is a shared
# prerequisite and must survive that cleanup.
nohup setsid node src/control-api.js --port "${PORT}" >> "${LOG_FILE}" 2>&1 9>&- &
echo $! > "${PID_FILE}"
echo "${PORT}" > "${PORT_FILE}"
echo "${expected_fingerprint}" > "${FINGERPRINT_FILE}"

for _ in $(seq 1 30); do
  sleep 0.5
  if probe_control_api "${PORT}" "$(read_control_api_token)"; then
    echo "http://127.0.0.1:${PORT}"
    exit 0
  fi
done

echo "ERROR: control API did not start on port ${PORT} — see ${LOG_FILE}" >&2
exit 1
