#!/usr/bin/env bash
set -Eeuo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="${ROOT_DIR}/.env"
ENV_EXAMPLE="${ROOT_DIR}/.env.example"
COMPOSE_FILE="${ROOT_DIR}/compose.yaml"
VERSIONS_FILE="${ROOT_DIR}/state/hermes/.last-known-good.env"

if [[ -f "$ENV_FILE" ]]; then
  set -a
  # shellcheck disable=SC1090
  source "$ENV_FILE"
  set +a
fi

PROJECT_NAME="${COMPOSE_PROJECT_NAME:-alan}"
HERMES_DATA_DIR="${HERMES_DATA_DIR:-./state/hermes}"
HERMES_BACKUP_DIR="${HERMES_BACKUP_DIR:-./backups/hermes}"
HERMES_API_PORT="${HERMES_API_PORT:-18642}"
HERMES_DASHBOARD_PORT="${HERMES_DASHBOARD_PORT:-19119}"
AGENTS_SHARED_NETWORK="${AGENTS_SHARED_NETWORK:-alan_agents_shared}"
BR0_DOCKER_NETWORK="${BR0_DOCKER_NETWORK:-br0_lan}"
BR0_PARENT="${BR0_PARENT:-br0}"
BR0_SUBNET="${BR0_SUBNET:-}"
BR0_GATEWAY="${BR0_GATEWAY:-}"
BR0_IP_RANGE="${BR0_IP_RANGE:-}"
BR0_HOST_SHIM_CIDR="${BR0_HOST_SHIM_CIDR:-}"
BR0_HOST_SHIM_IFACE="${BR0_HOST_SHIM_IFACE:-${BR0_DOCKER_NETWORK}-shim}"
HERMES_IMAGE="${HERMES_IMAGE:-nousresearch/hermes-agent}"
HERMES_TAG="${HERMES_TAG:-latest}"
HEALTHCHECK_URL="http://127.0.0.1:${HERMES_API_PORT}/health"

compose() {
  docker compose --project-name "$PROJECT_NAME" --env-file "$ENV_FILE" -f "$COMPOSE_FILE" "$@"
}

require_file() {
  local path="$1"
  [[ -f "$path" ]] || { echo "missing file: $path" >&2; exit 1; }
}

ensure_env() {
  if [[ ! -f "$ENV_FILE" ]]; then
    cp "$ENV_EXAMPLE" "$ENV_FILE"
    echo "[OK] .env created from .env.example"
  fi
}

ensure_dirs() {
  mkdir -p "$ROOT_DIR/state/hermes" "$ROOT_DIR/backups/hermes"
}

ensure_networks() {
  if ! docker network inspect "$AGENTS_SHARED_NETWORK" >/dev/null 2>&1; then
    docker network create "$AGENTS_SHARED_NETWORK" >/dev/null
    echo "[OK] created docker network ${AGENTS_SHARED_NETWORK}"
  fi
  if ! docker network inspect "$BR0_DOCKER_NETWORK" >/dev/null 2>&1; then
    echo "[WARN] missing external br0 network ${BR0_DOCKER_NETWORK}"
    echo "[HINT] run: ./scripts/alanctl net-create-br0"
  fi
}

require_docker() {
  command -v docker >/dev/null 2>&1 || { echo "docker not found" >&2; exit 1; }
  docker info >/dev/null 2>&1 || { echo "docker daemon not reachable" >&2; exit 1; }
  docker compose version >/dev/null 2>&1 || { echo "docker compose plugin not available" >&2; exit 1; }
}

port_in_use() {
  local port="$1"
  ss -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "(^|:)${port}$"
}

check_ports() {
  local fail=0
  for p in "$HERMES_API_PORT" "$HERMES_DASHBOARD_PORT"; do
    if port_in_use "$p"; then
      echo "[WARN] host port ${p} is already in use"
      fail=1
    fi
  done
  return "$fail"
}

show_openclaw_hint() {
  if docker ps --format '{{.Names}}' | grep -Eqi 'openclaw'; then
    echo "[INFO] OpenClaw container(s) detected. Shared network stays isolated by default; only explicit interconnects should use ${AGENTS_SHARED_NETWORK}."
  fi
}

show_br0_hint() {
  if ! ip link show "$BR0_PARENT" >/dev/null 2>&1; then
    echo "[WARN] host bridge ${BR0_PARENT} not found"
  fi
  if docker network inspect "$BR0_DOCKER_NETWORK" >/dev/null 2>&1; then
    echo "[OK] external br0 Docker network available: ${BR0_DOCKER_NETWORK}"
  else
    echo "[WARN] external br0 Docker network missing: ${BR0_DOCKER_NETWORK}"
  fi
}

write_last_known_good() {
  mkdir -p "$(dirname "$VERSIONS_FILE")"
  cat > "$VERSIONS_FILE" <<EOV
HERMES_IMAGE=${HERMES_IMAGE}
HERMES_TAG=${HERMES_TAG}
EOV
  echo "[OK] recorded last known good image ${HERMES_IMAGE}:${HERMES_TAG}"
}

read_last_known_good() {
  require_file "$VERSIONS_FILE"
  # shellcheck disable=SC1090
  source "$VERSIONS_FILE"
  echo "${HERMES_IMAGE}:${HERMES_TAG}"
}

health_wait() {
  local timeout="${1:-120}"
  local started
  started="$(date +%s)"
  while true; do
    if curl -fsS "$HEALTHCHECK_URL" >/dev/null 2>&1; then
      echo "[OK] Hermes healthy on ${HEALTHCHECK_URL}"
      return 0
    fi
    if (( $(date +%s) - started >= timeout )); then
      echo "[ERROR] Hermes health check timed out after ${timeout}s" >&2
      return 1
    fi
    sleep 3
  done
}

set_env_value() {
  local key="$1"
  local value="$2"
  ensure_env
  if grep -qE "^${key}=" "$ENV_FILE"; then
    sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
  else
    printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE"
  fi
}

current_image_ref() {
  echo "${HERMES_IMAGE}:${HERMES_TAG}"
}

cmd_bootstrap() {
  require_docker
  ensure_env
  ensure_dirs
  ensure_networks
  check_ports || true
  show_openclaw_hint
  show_br0_hint
  echo "[OK] bootstrap complete"
}

cmd_init_hermes() {
  require_docker
  ensure_env
  ensure_dirs
  echo "[INFO] starting Hermes setup wizard"
  docker run -it --rm \
    --network "$BR0_DOCKER_NETWORK" \
    -v "${ROOT_DIR}/state/hermes:/opt/data" \
    "${HERMES_IMAGE}:${HERMES_TAG}" setup
}

cmd_validate() {
  require_docker
  ensure_env
  ensure_dirs
  ensure_networks
  echo "[INFO] validating compose"
  compose config >/dev/null
  check_ports || true
  show_br0_hint
  echo "[OK] compose config valid"
}

cmd_up() {
  require_docker
  ensure_env
  ensure_dirs
  ensure_networks
  if ! docker network inspect "$BR0_DOCKER_NETWORK" >/dev/null 2>&1; then
    echo "missing external br0 Docker network: ${BR0_DOCKER_NETWORK}" >&2
    echo "create it with: ./scripts/alanctl net-create-br0" >&2
    exit 1
  fi
  check_ports || true
  local args=(up -d hermes)
  if [[ "${1:-}" == "--dashboard" ]]; then
    args=(--profile dashboard up -d hermes dashboard)
  fi
  compose "${args[@]}"
  health_wait 120
  write_last_known_good
}

cmd_down() {
  require_docker
  ensure_env
  compose down
}

cmd_restart() {
  require_docker
  ensure_env
  compose restart hermes
  health_wait 120
}

cmd_status() {
  require_docker
  ensure_env
  compose ps
  echo
  docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | awk 'NR==1 || /alan|openclaw/'
  if [[ -f "$VERSIONS_FILE" ]]; then
    echo
    echo "[INFO] last known good: $(read_last_known_good)"
  fi
}

cmd_logs() {
  require_docker
  ensure_env
  compose logs -f --tail=200 "${1:-hermes}"
}

cmd_shell() {
  require_docker
  ensure_env
  docker exec -it "${PROJECT_NAME}-hermes" bash
}

cmd_doctor() {
  require_docker
  ensure_env
  echo "[INFO] docker"
  docker version --format '{{.Server.Version}}'
  echo "[INFO] compose"
  docker compose version
  echo "[INFO] current image"
  echo "$(current_image_ref)"
  echo "[INFO] config check"
  compose config >/dev/null && echo "[OK] compose config"
  echo "[INFO] network"
  docker network inspect "$AGENTS_SHARED_NETWORK" --format '{{.Name}}' 2>/dev/null || echo "missing: ${AGENTS_SHARED_NETWORK}"
  docker network inspect "$BR0_DOCKER_NETWORK" --format '{{.Name}}' 2>/dev/null || echo "missing: ${BR0_DOCKER_NETWORK}"
  echo "[INFO] ports"
  check_ports || true
  echo "[INFO] host bridge"
  ip -brief link show "$BR0_PARENT" 2>/dev/null || echo "missing: ${BR0_PARENT}"
  echo "[INFO] health"
  if curl -fsS "$HEALTHCHECK_URL" >/dev/null 2>&1; then
    echo "[OK] Hermes API health endpoint reachable on ${HERMES_API_PORT}"
  else
    echo "[WARN] Hermes API health endpoint not reachable on ${HERMES_API_PORT}"
  fi
}

cmd_backup() {
  ensure_env
  ensure_dirs
  local src="${ROOT_DIR}/state/hermes"
  local dst_dir="${ROOT_DIR}/backups/hermes"
  local ts
  ts="$(date +%Y%m%d-%H%M%S)"
  local archive="${dst_dir}/hermes-${ts}.tar.gz"
  mkdir -p "$dst_dir"
  tar -C "$ROOT_DIR/state" -czf "$archive" hermes
  echo "$archive"
}

cmd_restore() {
  ensure_env
  ensure_dirs
  local archive="${1:-}"
  if [[ -z "$archive" || ! -f "$archive" ]]; then
    echo "usage: $0 restore /path/to/hermes-backup.tar.gz" >&2
    exit 1
  fi
  local restore_root="${ROOT_DIR}/state"
  local current_backup
  current_backup="$(cmd_backup)"
  echo "[INFO] current state backed up to ${current_backup}"
  rm -rf "${restore_root}/hermes"
  tar -C "$restore_root" -xzf "$archive"
  echo "[OK] restored ${archive}"
}

cmd_update_hermes() {
  require_docker
  ensure_env
  ensure_dirs
  local target_tag="${1:-$HERMES_TAG}"
  local previous_tag="$HERMES_TAG"
  local backup_file
  backup_file="$(cmd_backup)"
  echo "[INFO] backup created: ${backup_file}"
  echo "[INFO] updating Hermes from ${HERMES_IMAGE}:${previous_tag} to ${HERMES_IMAGE}:${target_tag}"
  set_env_value HERMES_TAG "$target_tag"
  # reload updated .env
  set -a
  # shellcheck disable=SC1090
  source "$ENV_FILE"
  set +a
  docker pull "${HERMES_IMAGE}:${target_tag}"
  compose up -d --force-recreate hermes
  if health_wait 180; then
    write_last_known_good
    echo "[OK] update successful"
    return 0
  fi
  echo "[WARN] update failed health check, rolling back to ${previous_tag}"
  set_env_value HERMES_TAG "$previous_tag"
  set -a
  # shellcheck disable=SC1090
  source "$ENV_FILE"
  set +a
  docker pull "${HERMES_IMAGE}:${previous_tag}" || true
  compose up -d --force-recreate hermes
  health_wait 180 || true
  echo "[WARN] restored previous tag. If state migration caused issues, restore backup: ${backup_file}"
  return 1
}

cmd_rollback_hermes() {
  require_docker
  ensure_env
  local target_ref
  target_ref="$(read_last_known_good)"
  local target_tag="${target_ref##*:}"
  echo "[INFO] rolling back to ${target_ref}"
  set_env_value HERMES_TAG "$target_tag"
  set -a
  # shellcheck disable=SC1090
  source "$ENV_FILE"
  set +a
  docker pull "$target_ref"
  compose up -d --force-recreate hermes
  health_wait 180
}

cmd_pin_hermes() {
  ensure_env
  local tag="${1:-}"
  if [[ -z "$tag" ]]; then
    echo "usage: $0 pin-hermes <tag>" >&2
    exit 1
  fi
  set_env_value HERMES_TAG "$tag"
  echo "[OK] pinned HERMES_TAG=${tag} in .env"
}

cmd_net_create_br0() {
  require_docker
  ensure_env
  if [[ -z "$BR0_SUBNET" || -z "$BR0_GATEWAY" || -z "$BR0_IP_RANGE" ]]; then
    echo "BR0_SUBNET, BR0_GATEWAY and BR0_IP_RANGE must be set in .env" >&2
    exit 1
  fi
  if ! ip link show "$BR0_PARENT" >/dev/null 2>&1; then
    echo "host bridge not found: ${BR0_PARENT}" >&2
    exit 1
  fi
  if docker network inspect "$BR0_DOCKER_NETWORK" >/dev/null 2>&1; then
    echo "[OK] ${BR0_DOCKER_NETWORK} already exists"
    return 0
  fi
  docker network create -d macvlan \
    --subnet "$BR0_SUBNET" \
    --gateway "$BR0_GATEWAY" \
    --ip-range "$BR0_IP_RANGE" \
    -o parent="$BR0_PARENT" \
    "$BR0_DOCKER_NETWORK" >/dev/null
  echo "[OK] created macvlan network ${BR0_DOCKER_NETWORK} on parent ${BR0_PARENT}"
}

cmd_net_create_shim() {
  if [[ -z "$BR0_HOST_SHIM_CIDR" ]]; then
    echo "BR0_HOST_SHIM_CIDR must be set in .env" >&2
    exit 1
  fi
  if ! docker network inspect "$BR0_DOCKER_NETWORK" >/dev/null 2>&1; then
    echo "missing Docker network: ${BR0_DOCKER_NETWORK}" >&2
    exit 1
  fi
  sudo ip link add "$BR0_HOST_SHIM_IFACE" link "$BR0_PARENT" type macvlan mode bridge 2>/dev/null || true
  sudo ip addr replace "$BR0_HOST_SHIM_CIDR" dev "$BR0_HOST_SHIM_IFACE"
  sudo ip link set "$BR0_HOST_SHIM_IFACE" up
  if [[ -n "$BR0_IP_RANGE" ]]; then
    sudo ip route replace "$BR0_IP_RANGE" dev "$BR0_HOST_SHIM_IFACE"
  fi
  echo "[OK] host shim ready on ${BR0_HOST_SHIM_IFACE} (${BR0_HOST_SHIM_CIDR})"
}

usage() {
  cat <<USAGE
Usage: $(basename "$0") <command>

Commands:
  bootstrap                Prepare directories, .env and Docker networks
  init-hermes              Run Hermes setup wizard interactively
  validate                 Validate compose config and port availability
  up [--dashboard]         Start Hermes, optionally with dashboard
  down                     Stop the stack
  restart                  Restart Hermes
  status                   Show compose and related container status
  logs [service]           Tail logs (default: hermes)
  shell                    Open a shell inside the Hermes container
  doctor                   Health and environment checks
  backup                   Create a timestamped backup archive
  restore <file>           Restore Hermes state from backup archive
  update-hermes [tag]      Backup, pull target tag, recreate service, verify health, auto-rollback on failure
  rollback-hermes          Reapply the last known good image tag
  pin-hermes <tag>         Persist a specific Hermes image tag in .env
  net-create-br0           Create the external macvlan Docker network on top of br0
  net-create-shim          Create optional host-side macvlan shim for host<->container access
USAGE
}

main() {
  local cmd="${1:-}"
  shift || true
  case "$cmd" in
    bootstrap) cmd_bootstrap "$@" ;;
    init-hermes) cmd_init_hermes "$@" ;;
    validate) cmd_validate "$@" ;;
    up) cmd_up "$@" ;;
    down) cmd_down "$@" ;;
    restart) cmd_restart "$@" ;;
    status) cmd_status "$@" ;;
    logs) cmd_logs "$@" ;;
    shell) cmd_shell "$@" ;;
    doctor) cmd_doctor "$@" ;;
    backup) cmd_backup "$@" ;;
    restore) cmd_restore "$@" ;;
    update-hermes) cmd_update_hermes "$@" ;;
    rollback-hermes) cmd_rollback_hermes "$@" ;;
    pin-hermes) cmd_pin_hermes "$@" ;;
    net-create-br0) cmd_net_create_br0 "$@" ;;
    net-create-shim) cmd_net_create_shim "$@" ;;
    *) usage; [[ -n "$cmd" ]] && exit 1 ;;
  esac
}

main "$@"
