#!/bin/bash
# setup-wsl.sh — install OR upgrade the AgentControl bridge on WSL2.
#
# Phase 56.9 — one command handles BOTH a clean first install and an
# in-place UPGRADE (e.g. after a backend migration that moved the API to a
# new host / rotated the anon key). The public install endpoint serves this
# script + a prebuilt bridge tarball + a small bridge-env.json, so end-users
# never need GitHub access.
#
# Usage from inside Ubuntu (WSL2):
#   curl -fsSL https://install.agent-control.io/wsl.sh | bash
#
# Flags (pass via:  curl ... | bash -s -- <flag>):
#   --force-fresh    wipe ~/agentcontrol-bridge and reinstall from scratch
#
# Env overrides:
#   AGENTCONTROL_HOST=install.acme.example.com   # different install host
#   BRIDGE_DIR=/path/to/bridge                    # different install dir
#
set -euo pipefail

# ---------------------------------------------------------------------------
# Derive every host / URL from the single install host. Installing from a
# different deployment (e.g. acme.example.com) just works: override
# AGENTCONTROL_HOST and the api/operator/app subdomains follow.
# ---------------------------------------------------------------------------
INSTALL_HOST="${AGENTCONTROL_HOST:-install.agent-control.io}"
BASE_DOMAIN="${INSTALL_HOST#install.}"              # install.agent-control.io -> agent-control.io
API_URL="https://api.${BASE_DOMAIN}"
OPERATOR_URL="https://operator.${BASE_DOMAIN}"
APP_URL="https://app.${BASE_DOMAIN}"
TARBALL_URL="https://${INSTALL_HOST}/bridge.tar.gz"
BRIDGE_ENV_URL="https://${INSTALL_HOST}/bridge-env.json"
BRIDGE_DIR="${BRIDGE_DIR:-$HOME/agentcontrol-bridge}"
SERVICE=agentcontrol-bridge

# The systemd --user service resolves `npm` from this PATH (no nvm/fnm), so it
# runs /usr/bin/node. Native deps (better-sqlite3) MUST be built against that
# same node or the service crashes with NODE_MODULE_VERSION/ERR_DLOPEN_FAILED.
# Run every dependency install under this PATH so build-ABI == runtime-ABI.
SERVICE_PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
svc_npm() { env PATH="$SERVICE_PATH" npm "$@"; }

FORCE_FRESH=false
for arg in "$@"; do
  case "$arg" in
    --force-fresh) FORCE_FRESH=true ;;
    *) echo "warn: ignoring unknown argument '$arg'" >&2 ;;
  esac
done

# ---------------------------------------------------------------------------
# 0. WSL2 sanity
# ---------------------------------------------------------------------------
if ! grep -qi microsoft /proc/version 2>/dev/null; then
  echo "error: this script is for WSL2 only." >&2
  echo "       Use scripts/setup-termux.sh on Android or deploy/ on native Linux." >&2
  exit 1
fi

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
bridge_port() {
  local p=""
  [ -f "$BRIDGE_DIR/.env" ] && \
    p=$(grep -E '^PORT=' "$BRIDGE_DIR/.env" | head -1 | cut -d= -f2 | tr -d '[:space:]' || true)
  echo "${p:-3000}"
}

read_env_val() {  # $1=key — echoes the (trimmed) value from the installed .env, or empty
  [ -f "$BRIDGE_DIR/.env" ] || { echo ""; return; }
  grep -E "^$1=" "$BRIDGE_DIR/.env" | head -1 | cut -d= -f2- | tr -d '[:space:]' || true
}

set_env_val() {  # $1=key $2=value — upsert an uncommented KEY=value line in the installed .env
  local key="$1" val="$2" f="$BRIDGE_DIR/.env"
  if grep -qE "^$key=" "$f"; then
    # Non-/ delimiter: values carry https:// and JWT dots.
    sed -i "s|^$key=.*|$key=$val|" "$f"
  else
    printf '%s=%s\n' "$key" "$val" >>"$f"
  fi
}

# Fetch the published anon key (public, safe to serve). Sets PUBLISHED_ANON
# and PUBLISHED_URL; leaves them empty if the endpoint isn't reachable.
PUBLISHED_ANON=""
PUBLISHED_URL=""
fetch_bridge_env() {
  local json
  json=$(curl -fsSL --max-time 10 "$BRIDGE_ENV_URL" 2>/dev/null || true)
  [ -n "$json" ] || return 0
  PUBLISHED_ANON=$(printf '%s' "$json" \
    | grep -oE '"supabaseAnonKey"[[:space:]]*:[[:space:]]*"[^"]+"' \
    | head -1 | sed -E 's/.*"supabaseAnonKey"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/' || true)
  PUBLISHED_URL=$(printf '%s' "$json" \
    | grep -oE '"supabaseUrl"[[:space:]]*:[[:space:]]*"[^"]+"' \
    | head -1 | sed -E 's/.*"supabaseUrl"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/' || true)
}

download_tarball() {
  echo "       downloading bridge from $TARBALL_URL"
  local tmp; tmp=$(mktemp -d)
  curl -fsSL "$TARBALL_URL" -o "$tmp/bridge.tar.gz"
  mkdir -p "$BRIDGE_DIR"
  # dist/ is fully owned by the tarball — drop the old one so removed files
  # don't linger. data/ + .env are preserved (never shipped in the tarball).
  rm -rf "$BRIDGE_DIR/dist"
  tar -xzf "$tmp/bridge.tar.gz" -C "$BRIDGE_DIR"
  rm -rf "$tmp"
}

pkg_version() {  # $1=dir
  node -p "require('$1/package.json').version" 2>/dev/null || echo "unknown"
}

# Pairing artifacts issued by the OLD backend are invalid the moment the URL
# or anon key changes — clearing them forces a clean re-pair.
wipe_pairing() {
  rm -f "$BRIDGE_DIR/data/bridge-token.json" "$BRIDGE_DIR/data/pairing-state.json"
}

pairing_expired() {  # 0 = on-disk claim missing-expiry/expired, 1 = still valid
  local f="$BRIDGE_DIR/data/pairing-state.json"
  [ -f "$f" ] || return 1
  node -e '
    const fs = require("fs");
    try {
      const s = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
      const t = Date.parse(s.expiresAt);
      process.exit(Number.isNaN(t) || t <= Date.now() ? 0 : 1);
    } catch (_) { process.exit(0); }
  ' "$f"
}

install_user_service() {
  mkdir -p "$HOME/.config/systemd/user"
  local unit="$HOME/.config/systemd/user/${SERVICE}.service"
  if [ -f "$unit" ]; then
    echo "       systemd unit already present — keeping it"
  else
    cat >"$unit" <<UNIT
[Unit]
Description=AgentControl Bridge
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=${BRIDGE_DIR}
ExecStart=/usr/bin/env npm start
Restart=always
RestartSec=30

[Install]
WantedBy=default.target
UNIT
    echo "       wrote $unit"
  fi
  systemctl --user daemon-reload
  # Linger keeps the user service alive without an open WSL login shell.
  loginctl enable-linger "$USER" >/dev/null 2>&1 || true
  systemctl --user enable "$SERVICE" >/dev/null 2>&1 || true
}

unit_installed() {  # 0 if the systemd --user unit exists (active, failed, or stopped)
  [ -f "$HOME/.config/systemd/user/${SERVICE}.service" ] || \
    systemctl --user cat "$SERVICE" >/dev/null 2>&1
}

smoke_health() {  # waits up to 20s for /health; echoes the body or exits non-zero
  local port; port=$(bridge_port)
  local out="" i
  for i in $(seq 1 20); do
    out=$(curl -fsS --max-time 5 "http://localhost:${port}/health" 2>/dev/null || true)
    [ -n "$out" ] && break
    sleep 1
  done
  if [ -z "$out" ]; then
    echo "✖ bridge did not become healthy on :$port within 20s" >&2
    echo "  inspect: journalctl --user -u $SERVICE -n 50 --no-pager" >&2
    exit 1
  fi
  echo "✔ bridge healthy on :$port → $out"
}

# ---------------------------------------------------------------------------
# UPGRADE — existing, running install
# ---------------------------------------------------------------------------
do_upgrade() {
  echo "==> Upgrade mode (existing bridge detected at $BRIDGE_DIR)"
  local old_ver; old_ver=$(pkg_version "$BRIDGE_DIR")

  echo "[1/6] Stopping bridge service..."
  systemctl --user stop "$SERVICE" 2>/dev/null || true

  echo "[2/6] Reconciling .env against $API_URL ..."
  fetch_bridge_env
  local new_url="${PUBLISHED_URL:-$API_URL}"
  local new_anon="$PUBLISHED_ANON"
  local cur_url cur_anon; cur_url=$(read_env_val SUPABASE_URL); cur_anon=$(read_env_val SUPABASE_ANON_KEY)
  local wipe=false changed=false

  [ "$cur_url" != "$new_url" ] && changed=true
  [ -n "$new_anon" ] && [ "$cur_anon" != "$new_anon" ] && changed=true

  if [ "$changed" = true ]; then
    local bak="$BRIDGE_DIR/.env.bak.$(date +%s)"
    cp "$BRIDGE_DIR/.env" "$bak"
    echo "       backend changed — backed up .env -> $(basename "$bak")"
    if [ "$cur_url" != "$new_url" ]; then
      set_env_val SUPABASE_URL "$new_url"; echo "       SUPABASE_URL: ${cur_url:-<unset>} -> $new_url"
    fi
    if [ -n "$new_anon" ] && [ "$cur_anon" != "$new_anon" ]; then
      set_env_val SUPABASE_ANON_KEY "$new_anon"; echo "       SUPABASE_ANON_KEY: rotated to published key"
    fi
    wipe=true
  else
    echo "       SUPABASE_URL + anon key already current"
  fi

  if [ "$wipe" = false ] && pairing_expired; then
    echo "       on-disk pairing claim is stale/expired — forcing re-pair"
    wipe=true
  fi

  if [ "$wipe" = true ]; then
    echo "[3/6] Wiping stale pairing tokens (forces re-pair against fresh DB)..."
    wipe_pairing
  else
    echo "[3/6] Pairing tokens current — keeping them"
  fi

  echo "[4/6] Pulling fresh bridge tarball..."
  download_tarball

  echo "[5/6] Installing runtime deps (npm ci --omit=dev)..."
  ( cd "$BRIDGE_DIR" && svc_npm ci --omit=dev --no-fund --no-audit )

  echo "[6/6] Restarting bridge..."
  systemctl --user start "$SERVICE"

  echo
  smoke_health
  local new_ver; new_ver=$(pkg_version "$BRIDGE_DIR")
  echo "  version: ${old_ver} -> ${new_ver}"
  if [ "$wipe" = true ]; then
    echo
    echo "  ⚠ pairing was reset. Re-pair this bridge:"
    echo "    1. claim code:  curl -s localhost:$(bridge_port)/pair   (or: journalctl --user -u $SERVICE)"
    echo "    2. open $OPERATOR_URL -> Bridges -> Pair new bridge -> enter the code"
  fi
}

# ---------------------------------------------------------------------------
# FRESH — clean first install
# ---------------------------------------------------------------------------
do_fresh() {
  echo "==> Fresh install mode"

  echo "[1/7] Installing system dependencies (sudo may prompt for password)..."
  sudo apt-get update -qq
  sudo apt-get install -y -qq \
    curl ca-certificates build-essential python3 python3-pip openssl

  echo "[2/7] Installing Node.js 22 (system node — the one the service uses)..."
  # Check the SERVICE node, not a shadowing nvm/fnm node, so /usr/bin/node exists
  # and matches the ABI svc_npm builds against.
  if ! env PATH="$SERVICE_PATH" node --version 2>/dev/null | grep -qE '^v(2[2-9]|[3-9][0-9])'; then
    curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - >/dev/null
    sudo apt-get install -y -qq nodejs
  fi
  echo "       node $(env PATH="$SERVICE_PATH" node --version), npm $(env PATH="$SERVICE_PATH" npm --version)"

  echo "[3/7] Installing Claude Code CLI..."
  command -v claude >/dev/null 2>&1 || sudo npm install -g @anthropic-ai/claude-code >/dev/null
  echo "       claude $(claude --version 2>/dev/null || echo 'installed (login required)')"

  echo "[4/7] Downloading bridge..."
  download_tarball

  echo "[5/7] Installing runtime deps (npm ci --omit=dev)..."
  ( cd "$BRIDGE_DIR" && svc_npm ci --omit=dev --no-fund --no-audit )

  echo "[6/7] Bootstrapping .env..."
  if [ -f "$BRIDGE_DIR/.env" ]; then
    echo "       .env exists — left untouched"
  else
    cp "$BRIDGE_DIR/.env.example" "$BRIDGE_DIR/.env"
    set_env_val API_KEY "$(openssl rand -hex 32)"
    set_env_val CLAUDE_HOME "$HOME/.claude"
    set_env_val SUPABASE_URL "$API_URL"
    fetch_bridge_env
    [ -n "$PUBLISHED_ANON" ] && set_env_val SUPABASE_ANON_KEY "$PUBLISHED_ANON"
    echo "       .env created (API_KEY generated; backend -> $API_URL)"
  fi

  echo "[7/7] Installing + starting the bridge service..."
  install_user_service
  systemctl --user restart "$SERVICE"

  echo
  smoke_health
  print_fresh_nextsteps
}

print_fresh_nextsteps() {
  local key port; key=$(read_env_val API_KEY); port=$(bridge_port)
  cat <<EOF

═════════════════════════════════════════════════════════════════
✔ Bridge installed at: $BRIDGE_DIR  (auto-starts via systemd --user)
═════════════════════════════════════════════════════════════════

Your bridge API_KEY (needed for app sign-in):
  $key

Next steps:

  1. Authenticate Claude Code (browser opens; log into your Claude.ai account):
       claude /login

  2. Install Tailscale on Windows so the app can reach this bridge:
       https://tailscale.com/download/windows
     Log in to the same tailnet your AgentControl app uses.

  3. Pair the bridge with your AgentControl org:
       a. Read the claim code the bridge minted on boot:
            curl -s localhost:$port/pair
          (or: journalctl --user -u $SERVICE -n 30 --no-pager)
       b. Open $OPERATOR_URL -> sign in -> Bridges -> Pair new bridge
       c. Enter the claim code.

  Service controls:
       systemctl --user status  $SERVICE
       systemctl --user restart $SERVICE
       journalctl  --user -u    $SERVICE -f

═════════════════════════════════════════════════════════════════
EOF
}

# ---------------------------------------------------------------------------
# Dispatch
# ---------------------------------------------------------------------------
if [ "$FORCE_FRESH" = true ]; then
  echo "==> --force-fresh: removing $BRIDGE_DIR and reinstalling"
  systemctl --user stop "$SERVICE" 2>/dev/null || true
  rm -rf "$BRIDGE_DIR"
  do_fresh
elif [ -f "$BRIDGE_DIR/package.json" ] && unit_installed; then
  # Upgrade-eligible whether the service is active, stopped, or crash-looping —
  # a broken bridge is exactly the case an upgrade needs to recover.
  do_upgrade
elif [ -d "$BRIDGE_DIR" ] && [ -n "$(ls -A "$BRIDGE_DIR" 2>/dev/null)" ]; then
  echo "error: $BRIDGE_DIR already exists but no bridge service is installed." >&2
  echo "       This is an unclean state (partial/aborted install)." >&2
  echo "       Reinstall from scratch:  ... | bash -s -- --force-fresh" >&2
  exit 1
else
  do_fresh
fi
