#!/usr/bin/env bash
# hum installer — v0.3 (Rust daemon, ensemble mesh, openai-server door).
#
# What this does, in order:
#   1. Ensure prerequisites: claude CLI (≥ MIN_CLAUDE), cargo, pnpm (for nestlings).
#   2. Build the Rust humd from source — `cargo install --path humd --root $HUM_BIN_ROOT`.
#   3. Mint Ed25519 identity at $HUM_STATE/humd.key if missing.
#   4. Seed an empty peers.json at $HUM_CONFIG/peers.json if missing.
#   5. Seed a minimal hum.json at $HUM_CONFIG/hum.json if missing.
#   6. Install systemd --user unit pointing at the Rust binary.
#   7. Build and install the openai-server nestling (the 0.3 front door).
#   8. Start the daemon.
#
# 0.2 → 0.3 is a config-shape change; there is no migration script. Read
# MIGRATING.md for the old-key → new-home table. Re-run this installer
# after editing $HUM_CONFIG/hum.json into the new shape.
#
# Idempotent — re-running upgrades in place.
#
# Usage:
#   ./install              # from a cloned repo
#   ./install status       # daemon health
#   ./install logs         # journalctl tail
#   ./install uninstall    # remove binary + unit (keeps state)
#   ./install purge        # remove everything INCLUDING state
#
set -euo pipefail

# ─── XDG dirs ────────────────────────────────────────────────────────────────
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
HUM_CONFIG="$XDG_CONFIG_HOME/hum"
HUM_DATA="$XDG_DATA_HOME/hum"
HUM_STATE="$XDG_STATE_HOME/hum"
HUM_BIN_ROOT="$HOME/.local"
HUM_BIN="$HUM_BIN_ROOT/bin/humd"

if [ -n "${XDG_RUNTIME_DIR:-}" ]; then
  HUM_RUNTIME="$XDG_RUNTIME_DIR/hum"
else
  HUM_RUNTIME="/tmp/hum-$(id -u)"
fi
# Canonical per WIRE.md. All clients (thrum-core / thrum-clients/{ts,python,go})
# resolve here. Override at runtime via HUM_THRUM_SOCK env if you must.
THRUM_SOCK="$HUM_RUNTIME/thrum.sock"

OPENCODE_CONFIG="$XDG_CONFIG_HOME/opencode/opencode.json"
HUM_REPO_URL="${HUM_REPO_URL:-https://github.com/adiled/hum.git}"
HUM_SRC="$HUM_DATA/src"

MIN_CLAUDE="2.1.86"

CMD="${1:-install}"
OS="$(uname -s)"

# ─── helpers ─────────────────────────────────────────────────────────────────

log()  { printf '\033[1m[hum]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[hum]\033[0m %s\n' "$*" >&2; }
fail() { printf '\033[1;31m[hum]\033[0m %s\n' "$*" >&2; exit 1; }

version_gte() { printf '%s\n%s\n' "$2" "$1" | sort -V -C; }

read_version() {
  "$1" --version 2>/dev/null | sed -n 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' | head -1
}

ensure_min_version() {
  local NAME="$1" BIN="$2" MIN="$3"
  if ! command -v "$BIN" >/dev/null 2>&1; then
    fail "$NAME not found on PATH. Install it and re-run."
  fi
  local CUR; CUR="$(read_version "$BIN" || true)"
  if [ -z "$CUR" ]; then fail "$NAME found but couldn't read version."; fi
  if version_gte "$CUR" "$MIN"; then
    log "$NAME ${CUR} ≥ ${MIN} ✓"
  else
    fail "$NAME ${CUR} < ${MIN} — upgrade and re-run."
  fi
}

# ─── prerequisite checks ─────────────────────────────────────────────────────

ensure_prereqs() {
  ensure_min_version "claude CLI" "claude" "$MIN_CLAUDE"
  if ! command -v cargo >/dev/null 2>&1; then
    # Dev paradigm: if a pre-built humd is already at $HUM_BIN (e.g.
    # placed by ./dev/deploy from a root user), allow proceeding so the
    # installer can still write the systemd unit + hum.json. Otherwise
    # building from source is required.
    if [ -x "$HUM_BIN" ]; then
      warn "cargo not found; using pre-built humd at $HUM_BIN"
      SKIP_BUILD=1
    else
      fail "cargo (rustup) not found. Install rustup: https://rustup.rs"
    fi
  fi
}

# ─── 0.2 detection — guide, don't migrate ───────────────────────────────────

flag_0_2_install() {
  local LANDMARK_TS="$HUM_DATA/src/humd/humd.ts"
  local LANDMARK_UNIT
  LANDMARK_UNIT="$(systemctl --user cat hum 2>/dev/null | grep -E '^ExecStart=' | head -1 || true)"
  local IS_OLD=false
  [ -f "$LANDMARK_TS" ] && IS_OLD=true
  case "$LANDMARK_UNIT" in *humd.ts*|*tsx*) IS_OLD=true ;; esac
  $IS_OLD || return 0

  warn "v0.2 install detected (TS daemon landmarks present)."
  warn ""
  warn "  0.3 has no migration script — config shape changed too much."
  warn "  Read MIGRATING.md for the old-key → new-home table."
  warn ""
  warn "  Quick path: stop the old service, remove $HUM_DATA/src, edit"
  warn "  $HUM_CONFIG/hum.json into the namespaced 0.3 shape, re-run this script."
  warn ""
  warn "  Continuing the install will overwrite the systemd unit and"
  warn "  binary; your hum.json stays untouched."
}

# ─── identity ────────────────────────────────────────────────────────────────

ensure_identity() {
  mkdir -p "$HUM_STATE"
  local KEY="$HUM_STATE/humd.key"
  if [ -s "$KEY" ]; then
    log "humd identity: $KEY ✓"
    return 0
  fi
  log "minting humd identity at $KEY (Ed25519, 32 bytes)"
  if command -v openssl >/dev/null 2>&1; then
    openssl genpkey -algorithm Ed25519 -outform DER -out "$KEY.tmp" 2>/dev/null
    tail -c 32 "$KEY.tmp" > "$KEY"
    rm -f "$KEY.tmp"
  else
    dd if=/dev/urandom of="$KEY" bs=32 count=1 2>/dev/null
  fi
  chmod 600 "$KEY"
}

# ─── peers.json ──────────────────────────────────────────────────────────────

ensure_peers_config() {
  mkdir -p "$HUM_CONFIG"
  local PEERS="$HUM_CONFIG/peers.json"
  if [ -s "$PEERS" ]; then
    log "peers.json: $PEERS ✓"
    return 0
  fi
  echo "[]" > "$PEERS"
  log "wrote empty peers.json at $PEERS"
}

# ─── hum.json (0.3 namespaced) ──────────────────────────────────────────────

ensure_hum_config() {
  mkdir -p "$HUM_CONFIG"
  local CFG="$HUM_CONFIG/hum.json"
  if [ -s "$CFG" ]; then
    log "hum.json: $CFG ✓"
    return 0
  fi
  cat > "$CFG" <<JSON
{
  "\$schema": "https://adiled.github.io/hum/hum.schema.json",
  "humd": {
    "permissionDuskMs": 60000,
    "driftRetentionDays": 30
  },
  "fs": {
    "roots": [
      { "path": "~/code", "mode": "rw" },
      { "path": "/tmp", "mode": "rw" }
    ],
    "denied": ["~/.ssh", "~/.aws", "~/.gnupg", "~/.config/hum"]
  },
  "nest": {
    "maxProcs": 4,
    "idleThresholdMs": 300000,
    "default": "claude-cli"
  },
  "hives": {
    "claude-cli":  { "cliPath": "claude", "defaultModel": "claude-sonnet-4-5" },
    "claude-repl": { "cliPath": "claude", "defaultModel": "claude-sonnet-4-5" }
  }
}
JSON
  log "wrote default hum.json at $CFG"
  log "  edit fs.roots to match where you actually code."
}

# ─── humd binary ─────────────────────────────────────────────────────────────

HUM_CLI_BIN="$HUM_BIN_ROOT/bin/hum"

build_humd() {
  local SRC
  if [ -f "$(dirname "$0")/Cargo.toml" ] && grep -q '"humd"' "$(dirname "$0")/Cargo.toml"; then
    SRC="$(dirname "$0")"
  else
    if [ ! -d "$HUM_SRC/.git" ]; then
      log "cloning $HUM_REPO_URL into $HUM_SRC"
      mkdir -p "$HUM_DATA"
      git clone --depth 1 "$HUM_REPO_URL" "$HUM_SRC"
    else
      log "updating $HUM_SRC"
      (cd "$HUM_SRC" && git pull --ff-only --quiet)
    fi
    SRC="$HUM_SRC"
  fi
  log "building Rust binaries via cargo install (may take a few minutes)"
  cargo install --quiet --locked --path "$SRC/humd" --root "$HUM_BIN_ROOT" --force
  cargo install --quiet --locked --path "$SRC/hum"  --root "$HUM_BIN_ROOT" --force
  [ -x "$HUM_BIN" ]     || fail "humd binary not at $HUM_BIN after build"
  [ -x "$HUM_CLI_BIN" ] || fail "hum binary not at $HUM_CLI_BIN after build"
  log "humd installed at $HUM_BIN"
  log "hum  installed at $HUM_CLI_BIN"
}

# ─── service unit (Linux systemd / macOS launchd via scripts/svc.sh) ───────

# Resolve and source the cross-platform svc helper.
SVC_HELPER="$(dirname "$0")/scripts/svc.sh"
if [ -f "$SVC_HELPER" ]; then
  # shellcheck source=/dev/null
  . "$SVC_HELPER"
elif [ -f "$HUM_DATA/src/scripts/svc.sh" ]; then
  . "$HUM_DATA/src/scripts/svc.sh"
else
  warn "scripts/svc.sh not found; service management will be skipped"
fi

install_service_unit() {
  if ! command -v svc_install >/dev/null 2>&1; then
    warn "svc helper unavailable — run '$HUM_BIN' manually"
    return 0
  fi
  case "$SVC_OS" in
    Linux|Darwin) ;;
    *) warn "service install not supported on $SVC_OS — run '$HUM_BIN' manually"; return 0 ;;
  esac
  svc_install hum "$HUM_BIN" \
    --env "HUM_LOG_LEVEL=info" \
    --env "XDG_CONFIG_HOME=$XDG_CONFIG_HOME" \
    --env "XDG_DATA_HOME=$XDG_DATA_HOME" \
    --env "XDG_STATE_HOME=$XDG_STATE_HOME" \
    --env "XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
    --env "HUM_THRUM_SOCK=$THRUM_SOCK" \
    --env "PATH=$HUM_BIN_ROOT/bin:/usr/local/bin:/usr/bin:/bin"
  log "service unit installed for hum ($SVC_OS)"
  # humd does its own daily auto-update check in-process — no separate
  # timer / cron / launchd interval to maintain.
  # Hives are NOT installed here — each hive ships its own installer
  # under hives/<kind>/install. humd just routes; whichever bee you
  # bring up registers itself via thrum bee:["worker"] (or "forager").
}

start_daemon() {
  if ! command -v svc_restart >/dev/null 2>&1; then
    warn "svc helper unavailable — start '$HUM_BIN' manually"
    return 0
  fi
  svc_restart hum 2>/dev/null || svc_start hum 2>/dev/null || true
  sleep 1
  if svc_is_active hum; then
    log "hum service running ✓"
  else
    warn "hum service did not start — \`./install logs\` to inspect."
  fi
}

# ─── next steps ──────────────────────────────────────────────────────────────

# humd has no opinion on which nestlings you run. Each nestling is a
# separate process with its own installer under hives/<kind>/install.
# Pick at least one to give humd an outside-world surface.
print_next_steps() {
  log ""
  log "humd is up. Pick a recipe to give it an outside-world surface:"
  log ""
  log "  hum + opencode (chat from the opencode TUI/CLI):"
  log "    ./recipes/opencode/install"
  log ""
  log "  Other nestlings — each is a standalone install:"
  log "    hives/openai-server/install     (OpenAI-shape HTTP)"
  log "    hives/anthropic-server/install  (Anthropic-shape HTTP)"
  log "    hives/ollama-server/install     (Ollama-shape HTTP)"
  log ""
  log "Health checks:"
  log "    ./install status                    (is humd alive)"
  log "    ./install logs                      (recent humd journal)"
}

# ─── status / logs / uninstall ───────────────────────────────────────────────

show_status() {
  echo "humd binary:  $HUM_BIN"
  echo "  version:    $($HUM_BIN --version 2>&1 || echo 'not installed')"
  echo "identity:     $HUM_STATE/humd.key $([ -s "$HUM_STATE/humd.key" ] && echo ✓ || echo MISSING)"
  echo "peers.json:   $HUM_CONFIG/peers.json $([ -s "$HUM_CONFIG/peers.json" ] && echo ✓ || echo MISSING)"
  echo "hum.json:     $HUM_CONFIG/hum.json $([ -s "$HUM_CONFIG/hum.json" ] && echo ✓ || echo MISSING)"
  echo "thrum socket: $THRUM_SOCK $([ -S "$THRUM_SOCK" ] && echo ✓ || echo absent)"
  if command -v svc_status >/dev/null 2>&1; then
    echo "service ($SVC_OS):"
    svc_status hum 2>&1 | head -6 || true
  fi
}

show_logs() {
  case "$SVC_OS" in
    Linux)  journalctl --user -u hum --no-pager -n 200 ;;
    Darwin) tail -n 200 "$HOME/Library/Logs/sh.hum.hum.out.log" "$HOME/Library/Logs/sh.hum.hum.err.log" 2>/dev/null ;;
    *) warn "logs command unavailable on $SVC_OS" ;;
  esac
}

uninstall() {
  if command -v svc_uninstall >/dev/null 2>&1; then
    svc_uninstall hum-update || true
    svc_uninstall hum || true
  fi
  rm -f "$HUM_BIN" "$HUM_CLI_BIN"
  log "uninstalled. State preserved in $HUM_STATE + $HUM_CONFIG."
  log "  run \`./install purge\` to remove state too."
}

purge() {
  uninstall
  rm -rf "$HUM_STATE" "$HUM_CONFIG" "$HUM_DATA"
  log "purged all hum state."
}

# ─── dispatch ────────────────────────────────────────────────────────────────

do_install() {
  log "hum installer — v0.3"
  flag_0_2_install
  ensure_prereqs
  if [ "${SKIP_BUILD:-0}" != "1" ]; then build_humd; fi
  ensure_identity
  ensure_peers_config
  ensure_hum_config
  install_service_unit
  start_daemon
  log ""
  log "humd installed. Try: ./install status   /   ./install logs"
  log ""
  print_next_steps
}

case "$CMD" in
  install|"") do_install ;;
  status)     show_status ;;
  logs)       show_logs ;;
  uninstall)  uninstall ;;
  purge)      purge ;;
  *) fail "unknown command: $CMD (try: install, status, logs, uninstall, purge)" ;;
esac
