#!/usr/bin/env bash
# agentina installer — fetch the latest signed tarball, verify, install.
#
# Production usage (single-line install via curl):
#
#   curl -fsSL https://install.agentina.io | sudo AGENTINA_TOKEN=act_... bash
#
# Or, for an existing artifact:
#
#   sudo bash install.sh \
#     --archive /tmp/agentina-0.0.1-linux-x64.tar.gz \
#     --token act_AAAAAAAAAAAAAAAAAAAAAAAA \
#     --cp https://control.agentina.io
#
# What it does:
#   1. Detect OS/arch
#   2. Download the latest stable manifest from dl.agentina.io
#      (or use --archive when run locally)
#   3. Verify cosign signature against keys/agentina-internal-release.pub
#   4. Create unprivileged 'agentina' user
#   5. Extract to /opt/agentina/<version>/, symlink current → version
#   6. Install systemd unit (or launchd plist on macOS)
#   7. If AGENTINA_TOKEN is set, run `agentina activate` immediately
#   8. enable + start the service
#
# Refuses to overwrite an existing install (use --upgrade for that).
# All steps are idempotent and reversible: a failed install leaves
# the previous version untouched.

set -euo pipefail

# ── Defaults ────────────────────────────────────────────────────
INSTALL_ROOT="${AGENTINA_INSTALL_ROOT:-/opt/agentina}"
STATE_DIR="${AGENTINA_STATE_DIR:-/var/lib/agentina}"
LOG_DIR="${AGENTINA_LOG_DIR:-/var/log/agentina}"
DEFAULT_CHANNEL="${AGENTINA_CHANNEL:-stable}"
CDN_BASE="${AGENTINA_CDN_BASE:-https://dl.agentina.io}"
COSIGN_PUBKEY="${AGENTINA_COSIGN_PUBKEY:-/opt/agentina/keys/agentina-internal-release.pub}"
SYSTEM_USER="agentina"
SYSTEM_GROUP="agentina"

# ── Args ────────────────────────────────────────────────────────
ARCHIVE=""
TOKEN="${AGENTINA_TOKEN:-}"
CP_URL="${AGENTINA_CONTROL_PLANE_URL:-https://control.agentina.io}"
KEYS_PATH=""
UPGRADE=false
DRY_RUN=false
VERSION=""
ALLOW_DEV_BUILDS=false
ONBOARD=false

while [[ $# -gt 0 ]]; do
  case "$1" in
    --archive)         ARCHIVE="$2"; shift 2 ;;
    --token)           TOKEN="$2"; shift 2 ;;
    --cp)              CP_URL="$2"; shift 2 ;;
    --keys)            KEYS_PATH="$2"; shift 2 ;;
    --version)         VERSION="$2"; shift 2 ;;
    --channel)         DEFAULT_CHANNEL="$2"; shift 2 ;;
    --install-root)    INSTALL_ROOT="$2"; shift 2 ;;
    --state-dir)       STATE_DIR="$2"; shift 2 ;;
    --upgrade)         UPGRADE=true; shift ;;
    --dry-run)         DRY_RUN=true; shift ;;
    --allow-dev-builds) ALLOW_DEV_BUILDS=true; shift ;;
    --onboard)         ONBOARD=true; shift ;;
    --help|-h)         cat <<'USAGE'
agentina install — bootstrap or upgrade the Agentina agent.

Required (one of):
  --archive <path>          local tarball (skips download)
  (otherwise downloads <CDN>/<channel>/manifest.json + binary)

Optional:
  --token <act_...>         activate immediately (or AGENTINA_TOKEN env)
  --cp <url>                Control Plane URL (default https://control.agentina.io)
  --keys <path>             trusted-keys JSON for activate (or AGENTINA_TRUSTED_KEYS_PATH)
  --version <v>             pin a specific version instead of channel-latest
  --channel <ch>            stable | beta | enterprise-lts (default stable)
  --install-root <path>     default /opt/agentina
  --state-dir <path>        default /var/lib/agentina
  --upgrade                 allow overwriting existing install
  --dry-run                 print actions but don't execute
  --allow-dev-builds        accept signing.status = "dev" / "unsigned"
                            (development / staging only — refused otherwise)
  --onboard                 after install, launch the onboarding wizard
                            (browser-based; activate + connectivity + service)
  --help                    this message
USAGE
      exit 0 ;;
    *)
      echo "unknown arg: $1" >&2; exit 2 ;;
  esac
done

# ── Preconditions ──────────────────────────────────────────────
if [[ "$EUID" -ne 0 ]] && ! $DRY_RUN; then
  echo "ERROR: must run as root (or use sudo). Try --dry-run to inspect first." >&2
  exit 2
fi

OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$OS" in
  linux|darwin) ;;
  *) echo "ERROR: unsupported OS '$OS' — only linux and darwin supported"; exit 2 ;;
esac

ARCH_RAW="$(uname -m)"
case "$ARCH_RAW" in
  x86_64|amd64) ARCH="x64" ;;
  aarch64|arm64) ARCH="arm64" ;;
  *) echo "ERROR: unsupported arch '$ARCH_RAW'"; exit 2 ;;
esac

run() {
  if $DRY_RUN; then
    echo "  DRY: $*"
  else
    eval "$@"
  fi
}

echo "── agentina install ──"
echo "  os/arch:      $OS-$ARCH"
echo "  install root: $INSTALL_ROOT"
echo "  state dir:    $STATE_DIR"
echo "  channel:      $DEFAULT_CHANNEL"
echo "  upgrade?:     $UPGRADE"
echo "  dry-run?:     $DRY_RUN"
echo ""

# ── 0. prereq check ────────────────────────────────────────────
# Fail fast with actionable messages rather than confusing errors
# 5 steps in.
echo "── 0. prereqs ──"
MISSING=()
command -v node >/dev/null 2>&1 || MISSING+=("node (>=22)")
command -v tar  >/dev/null 2>&1 || MISSING+=("tar")
command -v curl >/dev/null 2>&1 || MISSING+=("curl")
if [[ "${#MISSING[@]}" -gt 0 ]]; then
  echo "ERROR: missing required tools: ${MISSING[*]}" >&2
  echo "  Install on Debian/Ubuntu: apt-get install -y nodejs tar curl" >&2
  echo "  Install on RHEL/Fedora:   dnf install -y nodejs tar curl" >&2
  echo "  Install on macOS:         brew install node curl  (tar is built in)" >&2
  exit 2
fi
NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)"
if [[ "$NODE_MAJOR" -lt 20 ]]; then
  echo "ERROR: node $NODE_MAJOR is too old; agent requires node 20+" >&2
  exit 2
fi
echo "  node $(node --version)  tar $(tar --version 2>&1 | head -1 | awk '{print $NF}')  curl ok"
if command -v cosign >/dev/null 2>&1; then
  echo "  cosign $(cosign version --json 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).gitVersion" 2>/dev/null || echo unknown) (signature verification ENABLED)"
else
  echo "  cosign not present — signature verification will be SKIPPED"
fi
echo ""

# ── Refuse re-install (unless --upgrade) ───────────────────────
if [[ -d "$INSTALL_ROOT/current" && "$UPGRADE" != "true" ]]; then
  echo "ERROR: $INSTALL_ROOT/current already exists. Pass --upgrade to overwrite." >&2
  exit 4
fi

# ── Acquire archive (download or local) ────────────────────────
if [[ -z "$ARCHIVE" ]]; then
  echo "── 1. download manifest ──"
  TMP="$(mktemp -d)"
  trap "rm -rf '$TMP'" EXIT
  MANIFEST_URL="$CDN_BASE/agent/$DEFAULT_CHANNEL/manifest.json"
  if ! curl -fsSL "$MANIFEST_URL" -o "$TMP/manifest.json"; then
    echo "ERROR: failed to fetch $MANIFEST_URL" >&2; exit 5
  fi
  if [[ -z "$VERSION" ]]; then
    VERSION="$(node -e "console.log(JSON.parse(require('fs').readFileSync('$TMP/manifest.json')).current.version)")"
  fi
  ARCHIVE_URL="$CDN_BASE/agent/$DEFAULT_CHANNEL/$VERSION/agentina-${VERSION}-${OS}-${ARCH}.tar.gz"
  ARCHIVE="$TMP/$(basename "$ARCHIVE_URL")"
  echo "  fetching $ARCHIVE_URL"
  if ! curl -fsSL "$ARCHIVE_URL" -o "$ARCHIVE"; then
    echo "ERROR: failed to fetch $ARCHIVE_URL" >&2; exit 5
  fi
  if ! curl -fsSL "$ARCHIVE_URL.sig" -o "$ARCHIVE.sig"; then
    echo "WARN: no signature available — proceeding (release pipeline may be in soft-fail mode)" >&2
  fi
fi

if [[ ! -f "$ARCHIVE" ]]; then
  echo "ERROR: archive not found: $ARCHIVE" >&2
  exit 5
fi

# ── 2. verify cosign signature (if pubkey + sig present) ───────
#
# We do NOT yet know signing.status here (manifest is still inside the
# archive). Strategy: peek at the manifest from the tarball without
# extracting everything. For dev-signed bundles we have to pass
# --insecure-ignore-tlog because dev signatures explicitly skip Rekor.
echo ""
echo "── 2. verify ──"
if command -v cosign >/dev/null 2>&1 && [[ -f "$COSIGN_PUBKEY" ]] && [[ -f "$ARCHIVE.sig" ]]; then
  PEEK_STATUS="$(tar -xzOf "$ARCHIVE" manifest.json 2>/dev/null \
    | node -e "
let buf=''; process.stdin.on('data', c=>buf+=c); process.stdin.on('end', ()=>{
  try { const m = JSON.parse(buf); console.log((m.signing && m.signing.status) || 'unsigned'); }
  catch { console.log('unsigned'); }
});" 2>/dev/null || echo unsigned)"
  COSIGN_VERIFY_FLAGS=("--key" "$COSIGN_PUBKEY" "--signature" "$ARCHIVE.sig")
  if [[ "$PEEK_STATUS" == "dev" || "$PEEK_STATUS" == "unsigned" ]]; then
    COSIGN_VERIFY_FLAGS+=("--insecure-ignore-tlog")
  fi
  if cosign verify-blob "${COSIGN_VERIFY_FLAGS[@]}" "$ARCHIVE" >/dev/null 2>&1; then
    echo "  ✓ cosign signature valid (signing.status=$PEEK_STATUS)"
  else
    echo "ERROR: cosign signature INVALID for $ARCHIVE (signing.status=$PEEK_STATUS)" >&2
    exit 6
  fi
else
  echo "  ⚠ skipped (cosign / pubkey / .sig not all present)"
fi

# Verify SHA-256 of every file matches manifest.json (extracts to tmp first)
echo ""
echo "── 3. extract + verify manifest ──"
EXTRACT_DIR="$(mktemp -d)"
tar -xzf "$ARCHIVE" -C "$EXTRACT_DIR"
if [[ ! -f "$EXTRACT_DIR/manifest.json" ]]; then
  echo "ERROR: archive missing manifest.json" >&2; exit 7
fi
EXTRACTED_VERSION="$(node -e "console.log(JSON.parse(require('fs').readFileSync('$EXTRACT_DIR/manifest.json')).version)")"
if [[ -n "$VERSION" && "$VERSION" != "$EXTRACTED_VERSION" ]]; then
  echo "ERROR: manifest version $EXTRACTED_VERSION does not match requested $VERSION" >&2
  exit 7
fi
VERSION="$EXTRACTED_VERSION"
echo "  → version $VERSION verified"

# Read the signing.status field that build-bundle.sh stamped onto the
# manifest. Refuse "unsigned" or "dev" builds in production unless the
# operator explicitly opted in with --allow-dev-builds.
SIGNING_STATUS="$(node -e "
const m = JSON.parse(require('fs').readFileSync('$EXTRACT_DIR/manifest.json'));
console.log(m.signing && m.signing.status || 'unsigned');
")"
echo "  → signing.status = $SIGNING_STATUS"
if [[ "$SIGNING_STATUS" != "kms" && "$ALLOW_DEV_BUILDS" != "true" ]]; then
  echo "ERROR: refusing to install a $SIGNING_STATUS build in production." >&2
  echo "  Production releases must be signed via KMS (signing.status = 'kms')." >&2
  echo "  Pass --allow-dev-builds to override (development / staging only)." >&2
  exit 8
fi

# ── 4. create system user (idempotent) ─────────────────────────
echo ""
echo "── 4. system user ──"
if [[ "$OS" == "linux" ]]; then
  if ! getent passwd "$SYSTEM_USER" >/dev/null; then
    run "useradd --system --no-create-home --shell /usr/sbin/nologin '$SYSTEM_USER'"
    echo "  → created $SYSTEM_USER"
  else
    echo "  ✓ $SYSTEM_USER already exists"
  fi
elif [[ "$OS" == "darwin" ]]; then
  if ! id "$SYSTEM_USER" >/dev/null 2>&1; then
    echo "  WARN: macOS user creation requires manual setup (use System Settings or sysadminctl)"
  fi
fi

# ── 5. install to $INSTALL_ROOT/<version>/, symlink current ────
echo ""
echo "── 5. install files ──"
TARGET="$INSTALL_ROOT/$VERSION"
run "mkdir -p '$INSTALL_ROOT' '$STATE_DIR' '$LOG_DIR'"
# State dir holds private keys + bearer + license — read by agent only.
run "chmod 0700 '$STATE_DIR'"
# Log dir: agent writes, group can read for ops debugging via `adm`,
# but world cannot. (ProtectSystem=strict in the unit means the
# service can't widen these later.)
run "chmod 0750 '$LOG_DIR'"
run "chown $SYSTEM_USER:$SYSTEM_GROUP '$STATE_DIR' '$LOG_DIR' 2>/dev/null || true"
if [[ -d "$TARGET" ]]; then
  if $UPGRADE; then
    run "rm -rf '$TARGET'"
  else
    echo "ERROR: $TARGET exists; pass --upgrade" >&2; exit 4
  fi
fi
run "mv '$EXTRACT_DIR/bundle' '$TARGET'"
run "cp '$EXTRACT_DIR/manifest.json' '$TARGET/manifest.json'"
run "ln -sfn '$TARGET' '$INSTALL_ROOT/current'"
echo "  → installed to $TARGET"
echo "  → $INSTALL_ROOT/current → $TARGET"

# ── 6. install service unit ────────────────────────────────────
#
# Soft-skipped when no service manager is available (e.g. container
# without systemd). The installer is still useful for "install the
# files, I'll handle the service" workflows.
echo ""
echo "── 6. service unit ──"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SERVICE_INSTALLED=false
if [[ "$OS" == "linux" ]]; then
  if ! command -v systemctl >/dev/null 2>&1; then
    echo "  ⚠ systemctl not present — skipping service unit install"
    echo "    (you can run /opt/agentina/current/agentina run manually)"
  else
    if [[ -f "$SCRIPT_DIR/../systemd/agentina.service" ]]; then
      UNIT_SRC="$SCRIPT_DIR/../systemd/agentina.service"
    elif [[ -f "$INSTALL_ROOT/current/packaging/systemd/agentina.service" ]]; then
      UNIT_SRC="$INSTALL_ROOT/current/packaging/systemd/agentina.service"
    else
      echo "  WARN: agentina.service template not found — skip systemd install"
      UNIT_SRC=""
    fi
    if [[ -n "$UNIT_SRC" ]]; then
      run "install -m 0644 '$UNIT_SRC' /etc/systemd/system/agentina.service"
      run "systemctl daemon-reload"
      echo "  → /etc/systemd/system/agentina.service installed"
      SERVICE_INSTALLED=true
    fi
  fi
elif [[ "$OS" == "darwin" ]]; then
  if ! command -v launchctl >/dev/null 2>&1; then
    echo "  ⚠ launchctl not present — skipping plist install"
  elif [[ -f "$SCRIPT_DIR/../launchd/io.agentina.agent.plist" ]]; then
    run "install -m 0644 '$SCRIPT_DIR/../launchd/io.agentina.agent.plist' /Library/LaunchDaemons/io.agentina.agent.plist"
    echo "  → /Library/LaunchDaemons/io.agentina.agent.plist installed"
    SERVICE_INSTALLED=true
  fi
fi

# ── 7. activate (if token provided) ────────────────────────────
ACTIVATED=false
if [[ -n "$TOKEN" ]]; then
  echo ""
  echo "── 7. activate ──"
  ACT_ARGS=("--token" "$TOKEN" "--cp" "$CP_URL" "--state-dir" "$STATE_DIR")
  if [[ -n "$KEYS_PATH" ]]; then
    ACT_ARGS+=("--keys" "$KEYS_PATH")
  elif [[ -f "$INSTALL_ROOT/keys/trusted-keys.json" ]]; then
    ACT_ARGS+=("--keys" "$INSTALL_ROOT/keys/trusted-keys.json")
  fi
  if run "sudo -u $SYSTEM_USER '$INSTALL_ROOT/current/agentina' activate ${ACT_ARGS[*]}"; then
    ACTIVATED=true
  else
    echo "  ⚠ activation failed — see error above; service will not start cleanly until activation succeeds"
  fi
fi

# ── 8. enable + start service ──────────────────────────────────
echo ""
echo "── 8. start service ──"
if ! $SERVICE_INSTALLED; then
  echo "  (no service manager available; start manually with:"
  echo "     sudo -u $SYSTEM_USER /opt/agentina/current/agentina run --state-dir $STATE_DIR )"
elif [[ "$OS" == "linux" ]] && [[ -f /etc/systemd/system/agentina.service ]]; then
  run "systemctl enable agentina.service"
  run "systemctl restart agentina.service"
  echo "  → systemctl status agentina.service to inspect"
elif [[ "$OS" == "darwin" ]] && [[ -f /Library/LaunchDaemons/io.agentina.agent.plist ]]; then
  run "launchctl load /Library/LaunchDaemons/io.agentina.agent.plist"
  echo "  → launchctl list | grep agentina to inspect"
fi

# ── 9. post-install verification ───────────────────────────────
echo ""
echo "── 9. verify ──"
INSTALLED_VERSION=""
if [[ -x "$INSTALL_ROOT/current/agentina" ]] && ! $DRY_RUN; then
  INSTALLED_VERSION="$("$INSTALL_ROOT/current/agentina" version 2>/dev/null \
    | node -e "
      let buf=''; process.stdin.on('data',c=>buf+=c).on('end',()=>{
        try { console.log(JSON.parse(buf).version); } catch { console.log(''); }
      });" 2>/dev/null || echo "")"
  if [[ -n "$INSTALLED_VERSION" ]]; then
    echo "  ✓ agentina $INSTALLED_VERSION installed and runnable"
  else
    echo "  ⚠ agentina binary present but `version` failed — investigate"
  fi
fi

# Check whether the service actually came up (when service was installed
# AND we tried to start it AND we're not in dry-run).
SERVICE_RUNNING=false
if $SERVICE_INSTALLED && ! $DRY_RUN; then
  if [[ "$OS" == "linux" ]] && command -v systemctl >/dev/null 2>&1; then
    if systemctl is-active --quiet agentina.service 2>/dev/null; then
      SERVICE_RUNNING=true
      echo "  ✓ agentina.service is active"
    elif $ACTIVATED; then
      echo "  ⚠ agentina.service is NOT active (activation succeeded — check journalctl -u agentina -n 50)"
    fi
  fi
fi

# ── done ──
echo ""
echo "── done ──"
echo ""
echo "agentina $INSTALLED_VERSION installed at $INSTALL_ROOT/current"
echo ""
if ! $ACTIVATED; then
  echo "Next: activate the agent."
  echo ""
  echo "  Interactive:"
  echo "    sudo -u $SYSTEM_USER $INSTALL_ROOT/current/agentina activate"
  echo ""
  echo "  Or with flags:"
  echo "    sudo -u $SYSTEM_USER $INSTALL_ROOT/current/agentina activate \\"
  echo "      --token act_XXXXXXXXXXXXXXXXXXXXXXXX \\"
  echo "      --cp    $CP_URL \\"
  echo "      --keys  /path/to/trusted-keys.json"
  echo ""
  if $SERVICE_INSTALLED; then
    echo "After activation, start the service:"
    if [[ "$OS" == "linux" ]]; then
      echo "    sudo systemctl restart agentina"
      echo "    sudo systemctl status  agentina"
      echo "    sudo journalctl -u agentina -f"
    elif [[ "$OS" == "darwin" ]]; then
      echo "    sudo launchctl kickstart -k system/io.agentina.agent"
    fi
  else
    echo "Run the agent in foreground:"
    echo "    sudo -u $SYSTEM_USER $INSTALL_ROOT/current/agentina run --state-dir $STATE_DIR"
  fi
elif $SERVICE_RUNNING; then
  echo "Agent is activated and running."
  if [[ "$OS" == "linux" ]]; then
    echo "  status: sudo systemctl status agentina"
    echo "  logs:   sudo journalctl -u agentina -f"
  fi
elif $SERVICE_INSTALLED; then
  echo "Agent is activated. Service is installed but not yet running cleanly."
  if [[ "$OS" == "linux" ]]; then
    echo "  start: sudo systemctl restart agentina"
    echo "  inspect: sudo systemctl status agentina"
    echo "  logs:    sudo journalctl -u agentina -n 100"
  fi
else
  echo "Agent is activated. Run it in foreground:"
  echo "  sudo -u $SYSTEM_USER $INSTALL_ROOT/current/agentina run --state-dir $STATE_DIR"
fi
echo ""
echo "Status (anytime): sudo -u $SYSTEM_USER $INSTALL_ROOT/current/agentina status --state-dir $STATE_DIR"
echo "Quickstart:       docs/agentina-alpha-quickstart.md"

# ── 10. optional: launch the onboarding wizard ─────────────────
if $ONBOARD && ! $DRY_RUN && ! $ACTIVATED; then
  echo ""
  echo "── 10. launching onboarding wizard ──"
  if [[ "$EUID" -ne 0 ]]; then
    echo "  ⚠ not running as root; wizard cannot install the systemd unit"
  fi
  ONBOARD_ARGS=(--state-dir "$STATE_DIR")
  [[ -n "$CP_URL"    ]] && ONBOARD_ARGS+=(--cp "$CP_URL")
  [[ -n "$KEYS_PATH" ]] && ONBOARD_ARGS+=(--keys "$KEYS_PATH")
  exec "$INSTALL_ROOT/current/agentina" onboard "${ONBOARD_ARGS[@]}"
fi
