#!/bin/bash
# orbvis-setup – manage OrbVis installation
#
# Commands (run as the OMD site user):
#   orbvis-setup           – install / upgrade OrbVis
#   orbvis-setup uninstall – remove OrbVis (boards and database are kept)
#
set -euo pipefail

BOLD='\033[1m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; RED='\033[0;31m'; RESET='\033[0m'
step()  { echo -e "  ${BOLD}▸ $*${RESET}"; }
ok()    { echo -e "  ${GREEN}✓ $*${RESET}"; }
warn()  { echo -e "  ${YELLOW}⚠ $*${RESET}"; }
die()   { echo -e "\n${RED}Error: $*${RESET}\n" >&2; exit 1; }

SITE="${OMD_SITE:-}"
ROOT="${OMD_ROOT:-}"
[[ -z "$SITE" || -z "$ROOT" ]] && die "Run as the OMD site user (OMD_SITE / OMD_ROOT must be set).\nExample: su - <SITE> -c 'orbvis-setup'"

# Layout: user data + install artefacts under var/orbvis/, admin config in
# etc/orbvis/. Both paths sit outside cmk.gui.watolib.activate_changes
# replication_paths so WATO "Activate Changes" never overwrites them.
# The MKP itself still installs into local/lib/orbvis (CMK's MKP machinery
# decides that); orbvis-setup extracts the bundled tarballs from there into
# var/orbvis/ at install time.
MKP_LIB="$ROOT/local/lib/orbvis"
ORBVIS_DIR="$ROOT/var/orbvis"
ORBVIS_ETC_DIR="$ROOT/etc/orbvis"
LEGACY_DIR="$ROOT/local/share/orbvis"
HTDOCS_DIR="$ORBVIS_DIR/htdocs"
BOARDS_DIR="$ORBVIS_DIR/boards"
ENV_FILE="$ORBVIS_ETC_DIR/.env"
CONNECTIONS_FILE="$ORBVIS_DIR/connections.json"
DB_FILE="$ORBVIS_DIR/orbvis.db"
VENV_DIR="$ORBVIS_DIR/venv"
APACHE_CONF="$ROOT/etc/apache/conf.d/orbvis.conf"
INIT_SCRIPT="$ROOT/etc/init.d/orbvis"
LIVESTATUS_SOCKET="$ROOT/tmp/run/live"

# Pick a TCP port that no other OrbVis install on this host already claims.
# Reuse our own .env if present; otherwise scan all sibling sites' .env files
# (both the new etc/orbvis/.env location and the legacy local/share/ one, so
# a pre-migration install still surfaces its reserved port). Then pick the
# lowest free port from 8420 upward.
discover_port() {
  local existing port envf p
  for probe in "$ENV_FILE" "$LEGACY_DIR/.env"; do
    if [[ -f "$probe" ]]; then
      existing="$(grep -E '^ORBVIS_PORT=' "$probe" | head -1 | cut -d= -f2- || true)"
      if [[ -n "$existing" ]]; then echo "$existing"; return; fi
    fi
  done
  declare -A reserved=()
  for envf in /omd/sites/*/etc/orbvis/.env /omd/sites/*/local/share/orbvis/.env; do
    [[ -f "$envf" && "$envf" != "$ENV_FILE" ]] || continue
    p="$(grep -E '^ORBVIS_PORT=' "$envf" 2>/dev/null | head -1 | cut -d= -f2- || true)"
    [[ -n "$p" ]] && reserved[$p]=1
  done
  port=8420
  while (( port < 8500 )); do
    if [[ -z "${reserved[$port]:-}" ]] \
       && ! ss -tlnH "( sport = :$port )" 2>/dev/null | grep -q .; then
      echo "$port"; return
    fi
    (( port++ ))
  done
  die "No free port available in 8420-8499 for OrbVis backend."
}
BACKEND_PORT="$(discover_port)"

CMD="${1:-setup}"

case "$CMD" in

# ---------------------------------------------------------------------------
uninstall)
# ---------------------------------------------------------------------------
  echo ""
  echo -e "${BOLD}OrbVis Uninstall${RESET}"
  echo "  Site: $SITE  ($ROOT)"
  echo ""

  step "Stopping OrbVis"
  omd stop orbvis 2>/dev/null || true
  ok "OrbVis stopped"

  step "Removing OMD service"
  rm -f "$ROOT/etc/rc.d/85-orbvis"
  rm -f "$INIT_SCRIPT"
  ok "OMD service removed"

  step "Removing Apache configuration"
  rm -f "$APACHE_CONF"
  omd reload apache
  ok "Apache configuration removed"

  step "Removing frontend and backend"
  rm -rf "$HTDOCS_DIR"
  rm -rf "$VENV_DIR"
  rm -rf "$MKP_LIB/server"
  # Sweep up any leftovers from pre-migration installs under local/share/orbvis/.
  rm -rf "$LEGACY_DIR/htdocs" "$LEGACY_DIR/venv" "$LEGACY_DIR/cmk_plugins"
  ok "Frontend, venv and backend source removed"

  echo ""
  echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
  echo -e "${GREEN}${BOLD}  OrbVis uninstalled.${RESET}"
  echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
  echo ""
  echo "  Kept (user data):  $BOARDS_DIR"
  echo "                     $DB_FILE"
  echo "                     $ENV_FILE"
  echo "                     $CONNECTIONS_FILE"
  echo ""
  echo "  Remove manually if no longer needed:"
  echo "    rm -rf $ORBVIS_DIR"
  echo ""
  echo -e "  ${YELLOW}${BOLD}Next step: remove the MKP package from Checkmk:${RESET}"
  echo "    mkp disable orbvis"
  echo "    mkp remove orbvis"
  echo ""
  ;;

# ---------------------------------------------------------------------------
setup)
# ---------------------------------------------------------------------------
  [[ -d "$MKP_LIB" ]] || die "MKP library not found at $MKP_LIB\nDid you install the orbvis MKP first?"

  echo ""
  echo -e "${BOLD}OrbVis Post-Install Setup${RESET}"
  echo "  Site: $SITE  ($ROOT)"
  echo ""

  mkdir -p "$ORBVIS_DIR" "$ORBVIS_ETC_DIR"

  # 0. Migrate data from legacy local/share/orbvis/ to var/orbvis/ + etc/orbvis/
  #
  # Up to v0.x OrbVis installed everything under $OMD_ROOT/local/share/orbvis/,
  # which lives inside cmk.gui.watolib.activate_changes.replication_paths
  # (ident="local"). Every WATO Activate Changes pushed boards/db/venv/htdocs
  # to all remote sites. Move user data to var/orbvis/ and the .env to
  # etc/orbvis/ — both paths sit outside the snapshot.
  if [[ -d "$LEGACY_DIR" ]]; then
    step "Migrating data from legacy local/share/orbvis/"
    # ``backends.json`` is the pre-rename predecessor of ``connections.json``;
    # the backend recognises it on startup, so move it along. ``tiles`` is
    # the OSM cache — must move out of local/ to keep it out of the WATO
    # replication snapshot.
    for sub in boards images orbvis.db connections.json backends.json settings.json tiles; do
      if [[ -e "$LEGACY_DIR/$sub" && ! -e "$ORBVIS_DIR/$sub" ]]; then
        mv "$LEGACY_DIR/$sub" "$ORBVIS_DIR/$sub"
      fi
    done
    if [[ -f "$LEGACY_DIR/.env" && ! -f "$ENV_FILE" ]]; then
      mv "$LEGACY_DIR/.env" "$ENV_FILE"
    fi
    # Disposable artefacts; rebuilt below from the MKP-shipped tarballs.
    rm -rf "$LEGACY_DIR/htdocs" "$LEGACY_DIR/venv" \
           "$LEGACY_DIR/cmk_plugins" "$LEGACY_DIR/VERSION" \
           "$LEGACY_DIR/CHANGELOG.md"
    # User-data leftovers that didn't migrate because the destination
    # already existed. Destination is canonical; drop the legacy copy so it
    # can't leak via WATO replication.
    for sub in boards images orbvis.db connections.json backends.json settings.json tiles .env; do
      [[ -e "$LEGACY_DIR/$sub" && -e "$ORBVIS_DIR/$sub" ]] && rm -rf "$LEGACY_DIR/$sub"
    done
    # Drop empty legacy directory so the next replication snapshot has
    # nothing to delete on this site.
    rmdir "$LEGACY_DIR" 2>/dev/null || true
    ok "Migration complete"
  fi

  # 1. Frontend: extract pre-built tarball
  step "Deploying frontend"
  mkdir -p "$HTDOCS_DIR"
  rm -rf "${HTDOCS_DIR:?}"/*
  tar xzf "$MKP_LIB/htdocs.tar.gz" -C "$HTDOCS_DIR"
  ok "Frontend deployed to $HTDOCS_DIR"

  # 2. Backend source: extract tarball
  step "Extracting backend source"
  mkdir -p "$MKP_LIB/server"
  tar xzf "$MKP_LIB/server.tar.gz" -C "$MKP_LIB/server"
  ok "Backend source extracted"

  # 3. Boards directory — backend seeds bundled demos on first start
  # (gated by a .demo-seeded marker, so user deletions are honored)
  mkdir -p "$BOARDS_DIR/backgrounds"

  # 4. Python virtualenv + backend
  step "Setting up Python environment"

  # Resolve a Python 3.12+ interpreter. Order:
  #   1. PYTHON3 env var (lets users force a specific binary)
  #   2. $OMD_ROOT/bin/python3 (the site Python)
  #   3. python3.13, python3.12 on PATH (deadsnakes / OS package)
  #   4. python3 on PATH (last resort, may be too old)
  py_version_ok() {
    "$1" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 12) else 1)' 2>/dev/null
  }

  PYTHON3="${PYTHON3:-}"
  if [[ -n "$PYTHON3" ]]; then
    [[ -x "$PYTHON3" ]] || die "PYTHON3=$PYTHON3 is not executable."
  else
    for _candidate in \
        "$ROOT/bin/python3" \
        "$(command -v python3.13 2>/dev/null || true)" \
        "$(command -v python3.12 2>/dev/null || true)" \
        "$(command -v python3 2>/dev/null || true)"; do
      [[ -n "$_candidate" && -x "$_candidate" ]] || continue
      if py_version_ok "$_candidate"; then
        PYTHON3="$_candidate"
        break
      fi
    done
  fi

  if [[ -z "$PYTHON3" ]] || ! py_version_ok "$PYTHON3"; then
    FOUND_VERSION="$("${PYTHON3:-python3}" --version 2>&1 || echo 'not found')"
    die "OrbVis requires Python 3.12 or newer.

  Found: $FOUND_VERSION
  Searched: \$PYTHON3, $ROOT/bin/python3, python3.13, python3.12, python3

  Install Python 3.12 and re-run orbvis-setup. On Debian/Ubuntu:
    sudo add-apt-repository ppa:deadsnakes/ppa
    sudo apt install python3.12 python3.12-venv

  Then point orbvis-setup at it explicitly:
    PYTHON3=/usr/bin/python3.12 orbvis-setup"
  fi
  ok "Using $($PYTHON3 --version) ($PYTHON3)"

  # --system-site-packages: 2.6+ ships a sitecustomize.py that imports
  # cmk.licensing on every Python start, which lives in OMD's site-packages.
  # Without the flag the venv shadows that layout and sitecustomize fails.
  # Recreate any pre-existing venv that was built without it.
  if [[ ! -d "$VENV_DIR" ]] || ! grep -q '^include-system-site-packages = true' "$VENV_DIR/pyvenv.cfg" 2>/dev/null; then
    rm -rf "$VENV_DIR"
    "$PYTHON3" -m venv --symlinks --system-site-packages "$VENV_DIR"
  fi
  if [[ ! -L "$VENV_DIR/bin/python3" ]]; then
    ln -sf "$PYTHON3" "$VENV_DIR/bin/python3"
  fi
  step "Installing backend dependencies"
  # --no-warn-conflicts silences the false-positive resolver complaints from
  # the cmk meta-package whose deps live in the OMD site-packages, not the venv.
  export PIP_DISABLE_PIP_VERSION_CHECK=1
  "$VENV_DIR/bin/pip" install --quiet --no-warn-conflicts --upgrade pip
  "$VENV_DIR/bin/pip" install --quiet --no-warn-conflicts "$MKP_LIB/server"
  ok "Backend installed"

  # 5. Configuration
  step "Writing configuration"
  EXISTING_SECRET=""
  [[ -f "$ENV_FILE" ]] && EXISTING_SECRET=$(grep -E '^SECRET_KEY=' "$ENV_FILE" | head -1 | cut -d= -f2- || true)
  SECRET_KEY="${EXISTING_SECRET:-$("$PYTHON3" -c 'import secrets; print(secrets.token_hex(32))')}"

  cat > "$ENV_FILE" << EOF
BOARDS_DIR=$BOARDS_DIR
CONNECTIONS_FILE=$CONNECTIONS_FILE
DATABASE_URL=sqlite+aiosqlite:///$DB_FILE
SECRET_KEY=$SECRET_KEY
STATE_REFRESH_INTERVAL=15
CHECKMK_HTPASSWD=$ROOT/etc/htpasswd
CHECKMK_OMD_ROOT=$ROOT
CHECKMK_SITE=$SITE
ORBVIS_PORT=$BACKEND_PORT
EOF

  if [[ ! -f "$CONNECTIONS_FILE" ]]; then
    cat > "$CONNECTIONS_FILE" << EOF
[
  {
    "id": "live_1",
    "type": "livestatus",
    "label": "cmk $SITE",
    "socket_path": "$LIVESTATUS_SOCKET",
    "checkmk_url": "/$SITE/check_mk"
  }
]
EOF
    ok "Configuration written"
  else
    ok "Configuration written (existing connections.json kept)"
  fi

  # 6. Apache configuration
  step "Writing Apache configuration"

  PROXY_SO=""
  PROXY_HTTP_SO=""
  PROXY_WSTUNNEL_SO=""
  for _dir in \
      "$ROOT/lib/apache/modules" \
      "/usr/lib/apache2/modules" \
      "/usr/lib64/httpd/modules" \
      "/usr/lib/httpd/modules"; do
    [[ -z "$PROXY_SO" && -f "$_dir/mod_proxy.so" ]] && PROXY_SO="$_dir/mod_proxy.so"
    [[ -z "$PROXY_HTTP_SO" && -f "$_dir/mod_proxy_http.so" ]] && PROXY_HTTP_SO="$_dir/mod_proxy_http.so"
    [[ -z "$PROXY_WSTUNNEL_SO" && -f "$_dir/mod_proxy_wstunnel.so" ]] && PROXY_WSTUNNEL_SO="$_dir/mod_proxy_wstunnel.so"
  done

  if [[ -z "$PROXY_SO" ]]; then
    warn "mod_proxy.so not found — API proxy will be disabled. Backend API will be unreachable."
  fi
  if [[ -z "$PROXY_WSTUNNEL_SO" ]]; then
    warn "mod_proxy_wstunnel.so not found — WebSocket state stream will be unreachable."
  fi

  WSTUNNEL_LOAD=""
  WS_PROXY=""
  if [[ -n "$PROXY_WSTUNNEL_SO" ]]; then
    WSTUNNEL_LOAD="<IfModule !mod_proxy_wstunnel.c>
    LoadModule proxy_wstunnel_module $PROXY_WSTUNNEL_SO
</IfModule>"
    WS_PROXY="ProxyPass        /$SITE/orbvis/api/v1/ws  ws://127.0.0.1:$BACKEND_PORT/api/v1/ws
ProxyPassReverse /$SITE/orbvis/api/v1/ws  ws://127.0.0.1:$BACKEND_PORT/api/v1/ws"
  fi

  cat > "$APACHE_CONF" << EOF
# OrbVis – static frontend + backend proxy
# Auto-generated by orbvis-setup

# mod_proxy is not bundled in all OMD versions — load from detected path
<IfModule !mod_proxy.c>
    LoadModule proxy_module $PROXY_SO
</IfModule>
<IfModule !mod_proxy_http.c>
    LoadModule proxy_http_module $PROXY_HTTP_SO
</IfModule>
$WSTUNNEL_LOAD

Alias /$SITE/orbvis $HTDOCS_DIR

<Location /$SITE/orbvis>
    AuthType None
    Require all granted
</Location>

<Directory $HTDOCS_DIR>
    Options -Indexes +FollowSymLinks
    AllowOverride None
    Require all granted
    FallbackResource /$SITE/orbvis/index.html
</Directory>

# WS must come before the catch-all /api ProxyPass (Apache first-match).
$WS_PROXY
ProxyPass        /$SITE/orbvis/api  http://127.0.0.1:$BACKEND_PORT/api
ProxyPassReverse /$SITE/orbvis/api  http://127.0.0.1:$BACKEND_PORT/api

<Location /$SITE/orbvis/images>
    ProxyPass        http://127.0.0.1:$BACKEND_PORT/images
    ProxyPassReverse http://127.0.0.1:$BACKEND_PORT/images
</Location>

<Location /$SITE/orbvis/boards/backgrounds>
    ProxyPass        http://127.0.0.1:$BACKEND_PORT/boards/backgrounds
    ProxyPassReverse http://127.0.0.1:$BACKEND_PORT/boards/backgrounds
</Location>
EOF
  ok "Apache configuration written"

  # 7. OMD init script
  step "Registering OrbVis as OMD service"
  cat > "$INIT_SCRIPT" << EOF
#!/bin/bash
# OMD init script for OrbVis backend

PIDFILE="\$OMD_ROOT/tmp/run/orbvis.pid"
LOGFILE="\$OMD_ROOT/var/log/orbvis.log"
VENV="$VENV_DIR"
ENV_FILE="$ENV_FILE"
MKP_VERSION_FILE="$MKP_LIB/VERSION"
INSTALLED_VERSION_FILE="$ORBVIS_DIR/.installed-version"
SETUP_BIN="\$OMD_ROOT/local/bin/orbvis-setup"

# Trigger orbvis-setup automatically when a newer MKP has been installed but
# this site hasn't been refreshed yet (mkp update only replaces ~/local/, not
# the per-site venv / Apache conf / data layout). ORBVIS_NO_RESTART avoids the
# omd-recursion: orbvis-setup is told to skip its closing \`omd restart orbvis\`
# because that's exactly what we're inside of.
auto_refresh() {
  [[ -f "\$MKP_VERSION_FILE" && -x "\$SETUP_BIN" ]] || return 0
  local mkp_ver installed_ver
  mkp_ver="\$(cat "\$MKP_VERSION_FILE" 2>/dev/null || true)"
  installed_ver="\$(cat "\$INSTALLED_VERSION_FILE" 2>/dev/null || true)"
  [[ -n "\$mkp_ver" && "\$mkp_ver" != "\$installed_ver" ]] || return 0
  local banner="OrbVis: MKP version \$mkp_ver differs from installed \${installed_ver:-<none>}; running orbvis-setup..."
  echo ""
  echo "\$banner"
  echo "\$banner" >> "\$LOGFILE"
  if ! ORBVIS_NO_RESTART=1 "\$SETUP_BIN" >> "\$LOGFILE" 2>&1; then
    echo "WARN: orbvis-setup failed; see \$LOGFILE. Continuing with previous installation." >&2
    return 1
  fi
  echo "OrbVis refreshed to \$mkp_ver"
}

case "\$1" in
  start)
    if [[ -f "\$PIDFILE" ]] && kill -0 "\$(cat "\$PIDFILE")" 2>/dev/null; then
      echo "orbvis already running (pid \$(cat "\$PIDFILE"))"
      exit 0
    fi
    auto_refresh || true
    echo -n "Starting orbvis..."
    set -a; source "\$ENV_FILE"; set +a
    PORT="\${ORBVIS_PORT:-8420}"
    cd "$MKP_LIB/server"  # extracted by orbvis-setup step 2
    "\$VENV/bin/python3" -m alembic upgrade head >> "\$LOGFILE" 2>&1
    # setsid + nohup + </dev/null detaches from the calling session so SIGHUP
    # on \`omd su -i bash -c orbvis-setup\` exit doesn't take uvicorn down.
    setsid nohup "\$VENV/bin/python3" -m uvicorn app.main:app \\
      --host 127.0.0.1 --port "\$PORT" \\
      --log-level warning \\
      </dev/null >> "\$LOGFILE" 2>&1 &
    PID=\$!
    echo \$PID > "\$PIDFILE"
    sleep 1
    if kill -0 "\$PID" 2>/dev/null; then
      echo " OK (pid \$PID)"
    else
      rm -f "\$PIDFILE"
      echo " FAILED"
      echo "uvicorn died within 1s; see \$LOGFILE" >&2
      tail -20 "\$LOGFILE" >&2
      exit 1
    fi
    ;;
  stop)
    if [[ -f "\$PIDFILE" ]]; then
      PID="\$(cat "\$PIDFILE")"
      if kill -0 "\$PID" 2>/dev/null; then
        echo -n "Stopping orbvis (pid \$PID)..."
        kill "\$PID"
        for _ in \$(seq 1 20); do kill -0 "\$PID" 2>/dev/null || break; sleep 0.5; done
        echo " OK"
      fi
      rm -f "\$PIDFILE"
    else
      echo "orbvis not running"
    fi
    ;;
  restart) \$0 stop; \$0 start ;;
  status)
    if [[ -f "\$PIDFILE" ]] && kill -0 "\$(cat "\$PIDFILE")" 2>/dev/null; then
      echo "orbvis running (pid \$(cat "\$PIDFILE"))"; exit 0
    else
      echo "orbvis not running"; exit 1
    fi
    ;;
  *) echo "Usage: \$0 {start|stop|restart|status}"; exit 1 ;;
esac
EOF
  chmod +x "$INIT_SCRIPT"
  ln -sf "$INIT_SCRIPT" "$ROOT/etc/rc.d/85-orbvis" 2>/dev/null || true
  ok "OrbVis registered as OMD service"

  # 8. Stamp installed version so the init script can detect when a later
  # `mkp update` ships a newer VERSION and trigger an automatic refresh.
  if [[ -f "$MKP_LIB/VERSION" ]]; then
    cp "$MKP_LIB/VERSION" "$ORBVIS_DIR/.installed-version"
  fi

  # 9. Start services
  step "Reloading Apache"
  omd reload apache
  ok "Apache reloaded"

  # ORBVIS_NO_RESTART lets the init script call us during its own start
  # sequence without recursing into omd. The caller starts the backend itself.
  if [[ "${ORBVIS_NO_RESTART:-0}" == "1" ]]; then
    ok "OrbVis restart skipped (caller will start backend)"
  else
    step "Starting OrbVis"
    omd restart orbvis
    ok "OrbVis started"
  fi

  HOST="$(hostname -f 2>/dev/null || hostname)"
  echo ""
  echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
  echo -e "${GREEN}${BOLD}  OrbVis setup complete!${RESET}"
  echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
  echo ""
  echo "  Open in browser:  http://$HOST/$SITE/orbvis/"
  echo ""
  ;;

*)
  die "Unknown command: $CMD\nUsage: orbvis-setup [setup|uninstall]"
  ;;

esac
