#!/usr/bin/env bash
#
# Shared helpers for Docker E2E scripts that keep a named container running
# while polling readiness from the host.

docker_e2e_timeout_bin() {
  if command -v timeout >/dev/null 2>&1; then
    printf '%s\n' timeout
  elif command -v gtimeout >/dev/null 2>&1; then
    printf '%s\n' gtimeout
  else
    return 1
  fi
}

docker_e2e_timeout_cmd() {
  local timeout_value="$1"
  shift
  local timeout_bin
  if ! timeout_bin="$(docker_e2e_timeout_bin)"; then
    if command -v node >/dev/null 2>&1; then
      echo "timeout command not found; using Node watchdog for Docker command timeout ${timeout_value}" >&2
      node --input-type=module -e '
const [, timeoutValue, command, ...args] = process.argv;

const parseTimeoutMs = (value) => {
  const match = /^([0-9]+(?:\.[0-9]+)?)(ms|s|m|h)?$/u.exec(String(value ?? "").trim());
  if (!match) {
    throw new Error(`unsupported timeout value: ${value}`);
  }
  const amount = Number(match[1]);
  const unit = match[2] ?? "s";
  const multiplier = unit === "ms" ? 1 : unit === "s" ? 1_000 : unit === "m" ? 60_000 : 3_600_000;
  return Math.max(1, Math.ceil(amount * multiplier));
};

if (!command) {
  console.error("missing command for Node watchdog");
  process.exit(1);
}

const { spawn } = await import("node:child_process");
let timeoutMs;
try {
  timeoutMs = parseTimeoutMs(timeoutValue);
} catch (error) {
  console.error(error instanceof Error ? error.message : String(error));
  process.exit(1);
}

const child = spawn(command, args, {
  detached: process.platform !== "win32",
  stdio: "inherit",
});
let timedOut = false;
let parentSignal = null;
let parentSignalTimer = null;
const signalExitCodes = new Map([
  ["SIGHUP", 129],
  ["SIGINT", 130],
  ["SIGTERM", 143],
]);
const killGraceMs = Number.parseInt(
  process.env.OPENCLAW_DOCKER_TIMEOUT_KILL_GRACE_MS || "30000",
  10,
);
const killTarget = process.platform === "win32" ? child.pid : -child.pid;
const killChild = (signal) => {
  if (!child.pid) {
    return;
  }
  try {
    process.kill(killTarget, signal);
  } catch {
    try {
      child.kill(signal);
    } catch {}
  }
};
const timer = setTimeout(() => {
  timedOut = true;
  console.error(`Docker command timed out after ${timeoutValue}`);
  killChild("SIGTERM");
  setTimeout(() => killChild("SIGKILL"), killGraceMs).unref();
}, timeoutMs);
const forwardSignal = (signal) => {
  if (parentSignal) {
    killChild("SIGKILL");
    process.exit(signalExitCodes.get(signal) ?? 1);
  }
  parentSignal = signal;
  clearTimeout(timer);
  killChild(signal);
  parentSignalTimer = setTimeout(() => {
    killChild("SIGKILL");
    process.exit(signalExitCodes.get(signal) ?? 1);
  }, killGraceMs);
  parentSignalTimer.unref();
};
process.once("SIGINT", forwardSignal);
process.once("SIGTERM", forwardSignal);
child.on("exit", (code, signal) => {
  clearTimeout(timer);
  if (parentSignalTimer) {
    clearTimeout(parentSignalTimer);
  }
  if (timedOut) {
    process.exit(124);
  }
  if (parentSignal) {
    process.exit(signalExitCodes.get(parentSignal) ?? 1);
  }
  if (code !== null) {
    process.exit(code);
  }
  if (signal) {
    process.kill(process.pid, signal);
  }
  process.exit(1);
});
child.on("error", (error) => {
  clearTimeout(timer);
  console.error(error.message);
  process.exit(127);
});
' "$timeout_value" "$@"
      return
    fi
    echo "timeout command not found; cannot bound Docker command after ${timeout_value}" >&2
    return 127
  fi
  if "$timeout_bin" --kill-after=1s 1s true >/dev/null 2>&1; then
    "$timeout_bin" --kill-after=30s "$timeout_value" "$@"
  else
    "$timeout_bin" "$timeout_value" "$@"
  fi
}

docker_e2e_docker_cmd() {
  docker_e2e_timeout_cmd "${DOCKER_COMMAND_TIMEOUT:-600s}" docker "$@"
}

docker_e2e_docker_run_cmd() {
  docker_e2e_timeout_cmd "${DOCKER_COMMAND_TIMEOUT:-${OPENCLAW_DOCKER_E2E_RUN_TIMEOUT:-3600s}}" docker "$@"
}

docker_e2e_container_running() {
  local container_name="$1"
  [ "$(docker_e2e_docker_cmd inspect -f '{{.State.Running}}' "$container_name" 2>/dev/null || echo false)" = "true" ]
}

docker_e2e_container_exec_bash() {
  local container_name="$1"
  shift
  docker_e2e_docker_cmd exec "$container_name" bash -lc "$*"
}

docker_e2e_wait_container_bash() {
  local container_name="$1"
  shift
  docker_e2e_wait_container_bash_while_running "$container_name" "$container_name" "$@"
}

docker_e2e_wait_container_bash_while_running() {
  local running_container_name="$1"
  local exec_container_name="$2"
  local attempts="$3"
  local sleep_seconds="$4"
  shift 4
  local probe="$*"

  for _ in $(seq 1 "$attempts"); do
    if ! docker_e2e_container_running "$running_container_name"; then
      return 1
    fi
    if docker_e2e_container_exec_bash "$exec_container_name" "$probe" >/dev/null 2>&1; then
      return 0
    fi
    sleep "$sleep_seconds"
  done
  return 1
}

docker_e2e_tail_container_file_if_running() {
  local container_name="$1"
  local file_path="$2"
  local lines="${3:-120}"
  if docker_e2e_container_running "$container_name"; then
    docker_e2e_container_exec_bash "$container_name" "tail -n $lines $file_path" || true
  else
    docker_e2e_docker_cmd logs "$container_name" 2>&1 | tail -n "$lines" || true
  fi
}
