#!/usr/bin/env bash
# facio - host-side maintenance helper for the Facio + Placet quickstart stack.
#
# Manages the local Docker Compose deployment created by setup.sh:
#   • Facio agents (status / logs / restart / scale / update / down)
#   • In bundled mode, Placet is updated together with Facio via
#     `./facio update` (`docker compose pull` + `up -d` for the whole stack).
#
# Run from the install directory (where docker-compose.yml lives).
#
# Compose files included by this helper:
#   docker-compose.yml             (always - Placet + Facio, profile-gated)
#   docker-compose.override.yml    (optional; user-owned, copied from
#                                   docker-compose.override.yml.template)
#   docker-compose.scale.yml       (auto-generated by `./facio scale N` for N>1)
#
# Docker Compose auto-loads docker-compose.override.yml when no -f flag is
# given. We always pass -f, so we re-include it here when present.

set -euo pipefail

BASE_FILE="${FACIO_COMPOSE_FILE:-docker-compose.yml}"
SCALE_FILE="${FACIO_SCALE_COMPOSE_FILE:-docker-compose.scale.yml}"
ENV_FILE="${FACIO_ENV_FILE:-.env}"
# User-owned. Docker Compose auto-loads this file when no -f flag is given,
# but `./facio` always passes -f, so we re-include it here when it exists.
OVERRIDE_FILE="${FACIO_OVERRIDE_COMPOSE_FILE:-docker-compose.override.yml}"

info() { printf '  -> %s\n' "$*"; }
fail() { printf '  x %s\n' "$*" >&2; exit 1; }

usage() {
  cat <<'EOF'
Usage: ./facio <command> [args]

Commands:
  status               Show Docker Compose service status.
  logs [service...]    Follow logs (e.g. ./facio logs facio-1).
  restart [service...] Restart services.
  update               Pull all images (Facio + Placet) and recreate services.
  scale N              Run N Facio agents with stable names/hostnames.
  render [N]           Regenerate docker-compose.scale.yml only.
  config               Render and print the merged Compose config.
  up                   Start the stack (idempotent).
  down                 Stop the stack.

Scaling keeps the first install clean as "Facio Agent". For N > 1, agents are
named "Facio Agent (#1)", "(#2)", ... and use stable internal hostnames
facio-1, facio-2, ... so Placet management callbacks stay reachable.
EOF
}

require_files() {
  [ -f "$ENV_FILE" ] || fail "$ENV_FILE not found. Run setup.sh first."
  [ -f "$BASE_FILE" ] || fail "$BASE_FILE not found. Run this from the install directory."
}

env_value() {
  local key="$1"
  grep -E "^${key}=" "$ENV_FILE" 2>/dev/null | tail -n1 | cut -d= -f2- || true
}

set_env_value() {
  local key="$1" value="$2" tmp
  tmp="${ENV_FILE}.tmp.$$"
  if grep -qE "^${key}=" "$ENV_FILE" 2>/dev/null; then
    awk -v key="$key" -v value="$value" '
      BEGIN { replaced = 0 }
      $0 ~ "^" key "=" { print key "=" value; replaced = 1; next }
      { print }
      END { if (!replaced) print key "=" value }
    ' "$ENV_FILE" > "$tmp"
    mv "$tmp" "$ENV_FILE"
  else
    printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
  fi
}

compose() {
  local args=(-f "$BASE_FILE")
  # Docker auto-loads docker-compose.override.yml only when no -f is passed.
  # We always pass -f, so re-include the user-owned override here if present.
  [ -s "$OVERRIDE_FILE" ] && args+=(-f "$OVERRIDE_FILE")
  [ -s "$SCALE_FILE" ] && args+=(-f "$SCALE_FILE")
  docker compose "${args[@]}" "$@"
}

agent_count() {
  local requested="${1:-}" count
  count="${requested:-$(env_value FACIO_REPLICAS)}"
  count="${count:-1}"
  [[ "$count" =~ ^[0-9]+$ ]] || fail "Scale value must be a positive integer."
  [ "$count" -ge 1 ] || fail "Scale value must be at least 1."
  printf '%s\n' "$count"
}

write_scale_file() {
  local count="$1" tmp index
  tmp="${SCALE_FILE}.tmp.$$"
  {
    printf '%s\n' \
      '# GENERATED-BY-FACIO — do not edit. Re-run `./facio scale N` to regenerate.' \
      '# Adds Facio agents 2..N as `extends:` of facio-1. The only per-agent diff is' \
      '# the hostname, the visible Placet name, and the management URL. Host-port' \
      '# binding is intentionally absent — scaled agents are reached only via the' \
      '# docker network name (http://facio-N:8900). Front them with a reverse proxy' \
      '# in external mode.' \
      '' \
      'services:' \
      '  facio-1:' \
      '    environment:' \
      '      FACIO_PLACET_AGENT_NAME: "${FACIO_AGENT_NAME:-Facio Agent} (#1)"'
    index=2
    while [ "$index" -le "$count" ]; do
      printf '\n'
      printf '  facio-%s:\n' "$index"
      printf '    extends:\n'
      printf '      file: docker-compose.yml\n'
      printf '      service: facio-1\n'
      printf '    hostname: facio-%s\n' "$index"
      printf '    environment:\n'
      printf '      FACIO_AGENT_HOSTNAME: facio-%s\n' "$index"
      printf '      FACIO_PLACET_AGENT_NAME: "${FACIO_AGENT_NAME:-Facio Agent} (#%s)"\n' "$index"
      printf '      FACIO_MANAGEMENT_URL: ${FACIO_AGENT_%s_MANAGEMENT_URL:-http://facio-%s:8900}\n' "$index" "$index"
      index=$((index + 1))
    done
  } > "$tmp"
  mv "$tmp" "$SCALE_FILE"
}

render_agents() {
  local count
  require_files
  count="$(agent_count "${1:-}")"

  if [ "$count" -eq 1 ]; then
    if [ -f "$SCALE_FILE" ]; then
      rm -f "$SCALE_FILE"
      info "Removed $SCALE_FILE; running one Facio agent."
    fi
    return
  fi

  write_scale_file "$count"
  info "Wrote $SCALE_FILE for $count Facio agents."
}

scale_agents() {
  local target current
  require_files
  target="$(agent_count "${1:-}")"
  current="$(env_value FACIO_REPLICAS)"
  current="${current:-1}"
  [[ "$current" =~ ^[0-9]+$ ]] || current=1

  if [ "$target" -eq "$current" ]; then
    info "Already running $current Facio agent(s) — nothing to change."
    # Still re-render and reconcile in case scale.yml or containers drifted.
    render_agents "$target"
    compose up -d --remove-orphans
    return
  fi

  if [ "$target" -gt "$current" ]; then
    info "Scaling up: $current → $target Facio agents."
  else
    info "Scaling down: $current → $target Facio agents (extras will be stopped)."
  fi

  set_env_value FACIO_REPLICAS "$target"
  render_agents "$target"
  # --remove-orphans removes any facio-N containers no longer in the merged config.
  compose up -d --remove-orphans
  info "Now running $target Facio agent(s)."
}

cmd="${1:-}"
case "$cmd" in
  status)
    require_files
    compose ps
    ;;
  logs)
    shift
    require_files
    compose logs -f --tail=200 "$@"
    ;;
  restart)
    shift
    require_files
    compose restart "$@"
    ;;
  update)
    # Pull latest images for everything in the current Compose config (Facio +
    # bundled Placet if active) and recreate containers. Keeps the configured
    # replica count from .env — does NOT change scaling. Safe to run anytime.
    require_files
    render_agents "$(agent_count)"
    info "Pulling latest images…"
    compose pull
    info "Recreating containers with new images…"
    compose up -d --remove-orphans
    info "Update complete."
    ;;
  scale)
    shift
    [ "$#" -eq 1 ] || fail "Usage: ./facio scale N"
    scale_agents "$1"
    ;;
  render)
    shift
    render_agents "${1:-}"
    ;;
  config)
    require_files
    render_agents "$(agent_count)"
    compose config
    ;;
  up)
    require_files
    render_agents "$(agent_count)"
    compose up -d --remove-orphans
    ;;
  down)
    require_files
    compose down
    ;;
  --help|-h|help|'')
    usage
    ;;
  *)
    usage >&2
    fail "Unknown command: $cmd"
    ;;
esac
