#!/usr/bin/env bash
# Convenience wrapper around infra/ci-runners.compose.yml. Documented
# in infra/README.md → "CI runners" → "Convenience script".
#
# Hides the compose-file path on every command, automates the
# pre-pull of the job images that ADR-0015 §"Job image pinning and
# pre-pull" otherwise leaves to a manual step, and exposes the
# "one-runner-at-a-time" rotation pattern as a single verb so the CI
# pipeline is never starved during a config refresh.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COMPOSE_FILE="${SCRIPT_DIR}/ci-runners.compose.yml"

# Runner service names defined in the compose file. Update when a
# fourth runner is added (rare; documented in infra/README.md).
RUNNERS=(runner-1 runner-2 runner-3)

# Pre-pulled job images. The labels in ci-runners.compose.yml point at
# these tags; act_runner with `force_pull: false` (per
# infra/runner-config.yaml) refuses to start a job if the image is not
# already cached locally.
JOB_IMAGES=(
  "catthehacker/ubuntu:act-22.04"
  "catthehacker/ubuntu:full-22.04"
)

# Seconds to wait between rotation steps. The runners do not expose a
# Compose healthcheck; this is a conservative pause to let act_runner
# finish its registration handshake before we touch the next one.
ROTATE_WAIT_SECONDS=15

dc() {
  docker compose -f "$COMPOSE_FILE" "$@"
}

usage() {
  cat <<'USAGE'
Usage: ./infra/ci-runners.sh <command> [args]

Commands:
  up [--prepull]        Bring up the three runner containers.
                        --prepull also fetches the act_runner job images
                        (`catthehacker/ubuntu:act-22.04` and
                        `:full-22.04`) on the host first — required
                        because `runner-config.yaml` sets
                        `container.force_pull: false`.

  down                  Stop and remove the runner containers.
                        Persistent state under `data/runner-N/.runner`
                        survives — the runners re-authenticate from
                        their stored credentials on the next `up`.
                        For a clean wipe (forces re-registration with
                        a fresh Gitea token), invoke
                        `docker compose -f ci-runners.compose.yml down -v`
                        directly.

  restart <runner>      Restart one runner.

  rotate                Rolling restart of every runner (runner-1,
                        wait, runner-2, wait, …). The pause between
                        steps lets the previous runner finish its
                        registration handshake before the next one
                        goes down, so the CI pipeline always has at
                        least N-1 runners online.

  status                docker compose ps for the runner services.

  logs [runner]         Follow logs (one runner or all of them).

  pull-images           Pre-pull / refresh the act_runner job images
                        on the host. Idempotent (Docker reports
                        "Image is up to date" when nothing changed).

  help                  Show this help.

  <other> [args...]     Anything else is passed through to
                        `docker compose -f ci-runners.compose.yml ...`.

Examples:
  ./infra/ci-runners.sh up --prepull
  ./infra/ci-runners.sh rotate
  ./infra/ci-runners.sh logs runner-2
  ./infra/ci-runners.sh restart runner-1
USAGE
}

require_args() {
  local verb="$1"
  local n="$2"
  local hint="$3"
  shift 3
  if [[ $# -lt $n ]]; then
    echo "Error: '$verb' requires $hint." >&2
    echo "Try: ./infra/ci-runners.sh help" >&2
    exit 64
  fi
}

assert_known_runner() {
  local name="$1"
  for r in "${RUNNERS[@]}"; do
    if [[ "$r" == "$name" ]]; then
      return 0
    fi
  done
  echo "Error: unknown runner '$name'. Known: ${RUNNERS[*]}." >&2
  exit 64
}

pull_images() {
  echo "Pre-pulling job images on the runner host:"
  for img in "${JOB_IMAGES[@]}"; do
    echo "  • $img"
    docker pull "$img"
  done
}

cmd="${1:-help}"
[[ $# -gt 0 ]] && shift

case "$cmd" in
  up)
    if [[ "${1:-}" == "--prepull" ]]; then
      pull_images
      shift
    fi
    dc up -d "$@"
    ;;
  down)
    dc down "$@"
    ;;
  restart)
    require_args restart 1 "a runner name (e.g. runner-1)" "$@"
    assert_known_runner "$1"
    dc restart "$1"
    ;;
  rotate)
    echo "Rolling restart of ${#RUNNERS[@]} runners — ${ROTATE_WAIT_SECONDS}s pause between each."
    for r in "${RUNNERS[@]}"; do
      echo
      echo "→ Restarting $r…"
      dc restart "$r"
      if [[ "$r" != "${RUNNERS[-1]}" ]]; then
        echo "  Waiting ${ROTATE_WAIT_SECONDS}s before the next runner."
        sleep "$ROTATE_WAIT_SECONDS"
      fi
    done
    echo
    echo "✓ Rotation complete."
    ;;
  status | ps)
    dc ps "$@"
    ;;
  logs)
    dc logs -f "$@"
    ;;
  pull-images)
    pull_images
    ;;
  help | -h | --help | "")
    usage
    ;;
  *)
    # Pass-through.
    dc "$cmd" "$@"
    ;;
esac
