#!/usr/bin/env bash
# =============================================================================
# Wild Dragon MAM — Cluster Node Onboarding
# =============================================================================
#
# Provisions a Linux machine as a cluster worker node in one command.
#
# Quick-start (pipe to bash):
#   export MAM_API_URL=http://10.0.0.25:47432
#   export NODE_TOKEN=wd_xxxx          # create via Z-AMPP → Admin → Tokens
#   curl -sL https://forge.wilddragon.net/zgaetano/wild-dragon/raw/branch/main/deploy/onboard-node.sh | bash
#
# Or run from a cloned repo:
#   MAM_API_URL=http://10.0.0.25:47432 NODE_TOKEN=wd_xxxx ./deploy/onboard-node.sh
#
# Environment variables:
#   MAM_API_URL    REQUIRED  Primary MAM API base URL
#   NODE_TOKEN               API bearer token (required if AUTH_ENABLED=true)
#   NODE_ROLE                Role tag reported to the cluster   (default: auto-detect)
#   NODE_IP                  Override the LAN IP reported back  (default: auto-detect)
#   AGENT_PORT               Host port for the node agent       (default: 7436)
#   INSTALL_DIR              Where to clone/find the repo       (default: /opt/wild-dragon)
#   PROFILES                 Compose profiles, space-sep        (default: auto-detect from hardware)
#                            Override only to force, e.g. "worker capture"
#   BMD_MODEL                DeckLink card model name           (e.g. "DeckLink Duo 2")
#   REPO_URL                 Override the Forgejo clone URL
# =============================================================================

set -euo pipefail

# ── Config ───────────────────────────────────────────────────────────────────
REPO_URL="${REPO_URL:-https://forge.wilddragon.net/zgaetano/wild-dragon.git}"
INSTALL_DIR="${INSTALL_DIR:-/opt/wild-dragon}"
MAM_API_URL="${MAM_API_URL:-}"
NODE_TOKEN="${NODE_TOKEN:-}"
# Track whether the caller pinned NODE_ROLE explicitly (manual override) vs.
# us defaulting it — so auto-detection only fills in an *unset* role.
[[ -n "${NODE_ROLE:-}" ]] && NODE_ROLE_EXPLICIT=1 || NODE_ROLE_EXPLICIT=""
NODE_ROLE="${NODE_ROLE:-worker}"
NODE_IP="${NODE_IP:-}"
# NODE_NAME pins this node's cluster identity (the heartbeat key). Default to the
# OS hostname, but ALWAYS write it explicitly so cloned VMs that share an
# /etc/hostname (e.g. two boxes both named "zampp1") don't collide on the same
# cluster_nodes row — which silently hides the capture node's DeckLink devices.
NODE_NAME="${NODE_NAME:-$(hostname)}"
AGENT_PORT="${AGENT_PORT:-7436}"
PROFILES="${PROFILES:-}"
BMD_MODEL="${BMD_MODEL:-}"
PROJECT_NAME="wild-dragon-worker"

# ── Colours ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'; YEL='\033[1;33m'; GRN='\033[0;32m'; CYN='\033[0;36m'
BLD='\033[1m'; NC='\033[0m'
log()    { echo -e "${GRN}  ✓${NC}  $*"; }
info()   { echo -e "${CYN}  ▶${NC}  $*"; }
warn()   { echo -e "${YEL}  ⚠${NC}  $*"; }
header() { echo -e "\n${BLD}${CYN}── $* ──────────────────────────────────────${NC}"; }
die()    { echo -e "${RED}  ✗  ERROR:${NC}  $*" >&2; exit 1; }

# ── Auto-detect LAN IP ───────────────────────────────────────────────────────
# Node-agent runs in a container; os.networkInterfaces() inside the container
# returns the docker-bridge IP unless we pass NODE_IP through. We resolve the
# host's primary LAN IP here so the cluster page shows the right address.
detect_lan_ip() {
  local ip=""
  if command -v ip &>/dev/null; then
    ip=$(ip -4 route get 1.1.1.1 2>/dev/null \
         | awk '/src/ {for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}' \
         || true)
  fi
  if [[ -z "$ip" ]] && command -v hostname &>/dev/null; then
    ip=$(hostname -I 2>/dev/null | awk '{print $1}' || true)
  fi
  echo "$ip"
}

# ── Auto-detect hardware ─────────────────────────────────────────────────────
# Mirror detect_lan_ip's style: best-effort, guard every probe with `|| true`
# so a missing nvidia-smi/lspci never aborts under `set -euo pipefail`. The
# node self-describes its hardware here so the operator never has to pick a
# role — the right compose profiles are enabled automatically.

# GPU present? nvidia-smi is the strong signal; fall back to an lspci scan for
# NVIDIA or AMD VGA controllers (covers nodes where the driver isn't installed
# yet but the card is physically present).
detect_gpu() {
  if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then
    return 0
  fi
  if command -v lspci &>/dev/null; then
    if lspci 2>/dev/null | grep -iE 'nvidia|vga.*amd' &>/dev/null; then
      return 0
    fi
  fi
  return 1
}

# SDI capture card present? Blackmagic DeckLink or Deltacast, via lspci.
detect_sdi() {
  if command -v lspci &>/dev/null; then
    if lspci 2>/dev/null | grep -iE 'blackmagic|deltacast' &>/dev/null; then
      return 0
    fi
  fi
  return 1
}

# ── Preflight ────────────────────────────────────────────────────────────────
echo -e "\n${BLD}${CYN}Wild Dragon MAM — Cluster Node Onboarding${NC}\n"

[[ -z "$MAM_API_URL" ]] && die "MAM_API_URL is required.\n\n  Example:\n    export MAM_API_URL=http://10.0.0.25:47432\n    export NODE_TOKEN=wd_xxxx\n    ./deploy/onboard-node.sh"

if [[ -z "$NODE_IP" ]]; then
  NODE_IP="$(detect_lan_ip)"
  if [[ -n "$NODE_IP" ]]; then
    info "Auto-detected LAN IP: $NODE_IP"
  else
    warn "Could not auto-detect LAN IP — agent will fall back to interface heuristics."
  fi
fi

# ── Auto-assign compose profiles from detected hardware ──────────────────────
# Operator never picks a role: the worker profile always runs, and we add the
# gpu / capture profiles only when the matching hardware is present. Explicit
# PROFILES / NODE_ROLE env vars are honoured as a manual override escape hatch.
HAS_GPU=false; HAS_SDI=false
detect_gpu && HAS_GPU=true || true
detect_sdi && HAS_SDI=true || true

DETECTED_DESC="CPU"
[[ "$HAS_GPU" == true ]] && DETECTED_DESC="$DETECTED_DESC, GPU"
[[ "$HAS_SDI" == true ]] && DETECTED_DESC="$DETECTED_DESC, SDI capture card"

if [[ -z "$PROFILES" ]]; then
  AUTO_PROFILES="worker"
  [[ "$HAS_GPU" == true ]] && AUTO_PROFILES="$AUTO_PROFILES gpu"
  [[ "$HAS_SDI" == true ]] && AUTO_PROFILES="$AUTO_PROFILES capture"
  PROFILES="$AUTO_PROFILES"
  info "Detected: $DETECTED_DESC → profiles: $PROFILES"
else
  info "Detected: $DETECTED_DESC (profiles overridden by env: $PROFILES)"
fi

# Derive a human-friendly role tag from detected hardware when not pinned.
# Capture cards win over GPU (an SDI ingest node is the more specific role).
if [[ -z "$NODE_ROLE_EXPLICIT" ]]; then
  if   [[ "$HAS_SDI" == true ]]; then NODE_ROLE="capture"
  elif [[ "$HAS_GPU" == true ]]; then NODE_ROLE="gpu"
  else NODE_ROLE="worker"; fi
fi

info "Primary API  :  $MAM_API_URL"
info "Role         :  $NODE_ROLE"
info "Agent port   :  $AGENT_PORT"
info "Install dir  :  $INSTALL_DIR"
[[ -n "$NODE_IP"   ]] && info "Node IP      :  $NODE_IP"
[[ -n "$BMD_MODEL" ]] && info "DeckLink     :  $BMD_MODEL"
[[ -n "$PROFILES"  ]] && info "Profiles     :  $PROFILES"

if [[ -z "$NODE_TOKEN" ]]; then
  warn "NODE_TOKEN is not set."
  warn "If AUTH_ENABLED=true on the primary, heartbeats will be rejected."
  warn "Create a token: Z-AMPP web UI → Admin → Tokens → New Token"
fi

# ── Step 1: Docker ───────────────────────────────────────────────────────────
header "1/4  Docker"

if ! command -v docker &>/dev/null; then
  warn "Docker not found — installing via get.docker.com"
  curl -fsSL https://get.docker.com | bash
  systemctl enable --now docker 2>/dev/null || true
  usermod -aG docker "${SUDO_USER:-$USER}" 2>/dev/null || true
  log "Docker installed"
else
  log "Docker $(docker --version | grep -oP '\d+\.\d+\.\d+' | head -1) already installed"
fi

if ! docker info &>/dev/null; then
  die "Docker daemon not accessible.\n  Try: sudo systemctl start docker\n  Or add your user to the docker group and re-login."
fi

# ── Step 2: Repository ───────────────────────────────────────────────────────
header "2/4  Repository"

if [[ -d "$INSTALL_DIR/.git" ]]; then
  info "Updating $INSTALL_DIR"
  git -C "$INSTALL_DIR" pull --ff-only
  log "Repository up to date ($(git -C "$INSTALL_DIR" rev-parse --short HEAD))"
else
  info "Cloning $REPO_URL → $INSTALL_DIR"
  mkdir -p "$(dirname "$INSTALL_DIR")"
  git clone "$REPO_URL" "$INSTALL_DIR"
  log "Repository cloned"
fi

# ── Step 3: Environment ──────────────────────────────────────────────────────
header "3/4  Configuration"

ENV_FILE="$INSTALL_DIR/.env.worker"
info "Writing $ENV_FILE"

{
  echo "# Wild Dragon worker node — generated $(date -u +%Y-%m-%dT%H:%M:%SZ) by onboard-node.sh"
  echo "MAM_API_URL=$MAM_API_URL"
  echo "NODE_TOKEN=$NODE_TOKEN"
  echo "NODE_ROLE=$NODE_ROLE"
  echo "NODE_NAME=$NODE_NAME"
  echo "NODE_IP=$NODE_IP"
  echo "AGENT_PORT=$AGENT_PORT"
  echo "HEARTBEAT_MS=30000"
  [[ -n "$BMD_MODEL" ]] && echo "BMD_MODEL=$BMD_MODEL"
  for v in REDIS_URL DATABASE_URL S3_ENDPOINT S3_BUCKET S3_ACCESS_KEY S3_SECRET_KEY S3_REGION; do
    val="${!v:-}"
    [[ -n "$val" ]] && echo "$v=$val"
  done
} > "$ENV_FILE"

log "Env file written"

# ── Step 4: Start services ───────────────────────────────────────────────────
header "4/4  Starting services"

COMPOSE="docker compose -f $INSTALL_DIR/docker-compose.worker.yml --env-file $ENV_FILE --project-name $PROJECT_NAME"

PROFILE_FLAGS=""
for p in $PROFILES; do
  PROFILE_FLAGS="$PROFILE_FLAGS --profile $p"
done

info "Building images (this may take a minute on first run)…"
$COMPOSE build

info "Starting containers…"
# shellcheck disable=SC2086
$COMPOSE $PROFILE_FLAGS up -d

# ── Verify ───────────────────────────────────────────────────────────────────
echo ""
info "Waiting 6 seconds for agent to initialise…"
sleep 6

HEALTH_URL="http://localhost:$AGENT_PORT/health"
if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then
  log "Node agent healthy at $HEALTH_URL"
  REPORTED_IP=$(curl -sf "$HEALTH_URL" | sed -nE 's/.*"ip":"([^"]+)".*/\1/p')
  [[ -n "$REPORTED_IP" ]] && log "Reporting IP: $REPORTED_IP"
else
  warn "Could not reach $HEALTH_URL — check logs:"
  warn "  $COMPOSE logs node-agent"
fi

# ── Done ─────────────────────────────────────────────────────────────────────
echo ""
echo -e "${BLD}${GRN}Onboarding complete!${NC}"
echo ""
echo -e "  Node agent   ${BLD}:$AGENT_PORT${NC}  (heartbeating every 30s)"
echo -e "  Primary API  ${BLD}$MAM_API_URL${NC}"
echo -e "  Role         ${BLD}$NODE_ROLE${NC}"
[[ -n "$NODE_IP" ]] && echo -e "  Node IP      ${BLD}$NODE_IP${NC}"
echo ""
echo -e "  ${CYN}Useful commands:${NC}"
echo -e "    Status   :  $COMPOSE ps"
echo -e "    Logs     :  $COMPOSE logs -f"
echo -e "    Stop     :  $COMPOSE down"
echo -e "    Update   :  git -C $INSTALL_DIR pull && $COMPOSE build && $COMPOSE up -d"
echo ""
echo -e "  Open the Z-AMPP web UI → ${BLD}Admin → Cluster${NC} to see this node."
