#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────
# Alvin Bot — One-Line Installer
# Usage: curl -fsSL https://unpkg.com/alvin-bot/install.sh | bash
#    or: curl -fsSL https://cdn.jsdelivr.net/npm/alvin-bot/install.sh | bash
#
# Designed for a *fresh* machine. It needs NOTHING preinstalled — no
# Homebrew, no Xcode Command Line Tools, no sudo, no system Node. If a
# usable Node (>= 18) is already on PATH it is reused; otherwise a
# self-contained Node runtime is downloaded into ~/.alvin-bot/runtime and
# alvin-bot is installed into a user-owned npm prefix (~/.npm-global).
#
# It does NOT clone the git repo and never touches system directories.
# `~/.alvin-bot/` is the per-user data dir (env, memory, logs, runtime).
# ─────────────────────────────────────────────────────────────
set -euo pipefail

# Colors (disabled when stdout is not a TTY, e.g. piped to a file)
if [ -t 1 ]; then
  RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
  BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m'
else
  RED=''; GREEN=''; YELLOW=''; BLUE=''; BOLD=''; NC=''
fi

MIN_NODE_VERSION=18
NODE_MAJOR=22                       # portable runtime line (current LTS)
RUNTIME_DIR="$HOME/.alvin-bot/runtime"
NPM_PREFIX="$HOME/.npm-global"

# ─── Helpers ────────────────────────────────────────────────

info()  { echo -e "${BLUE}ℹ${NC}  $*"; }
ok()    { echo -e "${GREEN}✔${NC}  $*"; }
warn()  { echo -e "${YELLOW}⚠${NC}  $*"; }
fail()  { echo -e "${RED}✘${NC}  $*"; exit 1; }

is_interactive() { [ -t 0 ] && [ -t 1 ]; }

# Append a line to the user's shell rc files, guarded so re-runs don't
# duplicate it. Writes to every rc that already exists; if NONE exist
# (a fresh macOS account often has no ~/.zshrc yet), it creates the one
# for the login shell — otherwise the PATH wouldn't persist and a new
# terminal still wouldn't find `alvin-bot`.
persist_path_line() {
  local line="$1" marker="$2" rc wrote=0
  for rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do
    [ -e "$rc" ] || continue
    wrote=1
    grep -qF "$marker" "$rc" 2>/dev/null && continue
    printf '\n# %s\n%s\n' "$marker" "$line" >> "$rc"
  done
  if [ "$wrote" = 0 ]; then
    local primary
    case "${SHELL##*/}" in
      zsh)  primary="$HOME/.zshrc" ;;
      bash) [ "${OS:-}" = "macOS" ] && primary="$HOME/.bash_profile" || primary="$HOME/.bashrc" ;;
      *)    primary="$HOME/.zshrc" ;;   # modern macOS default is zsh
    esac
    printf '\n# %s\n%s\n' "$marker" "$line" >> "$primary"
  fi
}

# ─── OS / Arch detection ───────────────────────────────────

detect_platform() {
  case "$(uname -s)" in
    Darwin*) OS="macOS"; NPLAT="darwin" ;;
    Linux*)
      if grep -qi microsoft /proc/version 2>/dev/null; then OS="WSL"; else OS="Linux"; fi
      NPLAT="linux"
      ;;
    *) fail "Unsupported OS: $(uname -s). Use macOS, Linux, or WSL." ;;
  esac
  case "$(uname -m)" in
    arm64|aarch64) NARCH="arm64" ;;
    x86_64|amd64)  NARCH="x64" ;;
    *) fail "Unsupported CPU architecture: $(uname -m)." ;;
  esac
  ok "Detected ${BOLD}$OS${NC} ($NARCH)"
}

# ─── Node bootstrap (zero prereqs) ─────────────────────────

# True if a Node >= MIN_NODE_VERSION is already callable.
have_good_node() {
  command -v node >/dev/null 2>&1 || return 1
  local major; major=$(node -v 2>/dev/null | sed 's/^v//' | cut -d. -f1)
  [ -n "$major" ] && [ "$major" -ge "$MIN_NODE_VERSION" ]
}

# Download a self-contained Node runtime into RUNTIME_DIR. No sudo, no
# package manager, no compiler — just an official prebuilt tarball.
install_portable_node() {
  # Reuse an existing good runtime from a previous run.
  if [ -x "$RUNTIME_DIR/bin/node" ] && \
     [ "$("$RUNTIME_DIR/bin/node" -v | sed 's/^v//' | cut -d. -f1)" -ge "$MIN_NODE_VERSION" ]; then
    ok "Reusing Node runtime at ~/.alvin-bot/runtime ($("$RUNTIME_DIR/bin/node" -v))"
    export PATH="$RUNTIME_DIR/bin:$PATH"
    return 0
  fi

  info "No suitable Node found — downloading a self-contained Node $NODE_MAJOR runtime..."
  local base="https://nodejs.org/dist/latest-v${NODE_MAJOR}.x"
  # The directory-listing row prints the filename twice (href + link text),
  # so grep -o yields two hits — sed picks the first. sed reads the whole
  # (tiny) stream, so nothing closes early and pipefail stays happy.
  local tarname
  tarname=$(curl -fsSL "$base/" \
    | grep -oE "node-v${NODE_MAJOR}\.[0-9]+\.[0-9]+-${NPLAT}-${NARCH}\.tar\.gz" \
    | sed -n '1p') \
    || fail "Could not determine the Node download URL from nodejs.org."
  [ -n "$tarname" ] || fail "No Node $NODE_MAJOR build for ${NPLAT}-${NARCH}."

  local tmp; tmp=$(mktemp -d)
  info "Fetching $tarname ..."
  curl -fL# -o "$tmp/node.tar.gz" "$base/$tarname" \
    || fail "Download failed: $base/$tarname"

  rm -rf "$RUNTIME_DIR"
  mkdir -p "$RUNTIME_DIR"
  tar -xzf "$tmp/node.tar.gz" -C "$RUNTIME_DIR" --strip-components=1 \
    || fail "Failed to extract the Node runtime."
  rm -rf "$tmp"

  export PATH="$RUNTIME_DIR/bin:$PATH"
  command -v node >/dev/null 2>&1 || fail "Node runtime installed but not callable."
  ok "Node runtime ready: $(node -v)"
  persist_path_line "export PATH=\"\$HOME/.alvin-bot/runtime/bin:\$PATH\"" \
                    "alvin-bot node runtime"
}

ensure_node() {
  if have_good_node; then
    ok "Node.js found: $(node -v)"
    USED_PORTABLE_NODE=0
  else
    install_portable_node
    USED_PORTABLE_NODE=1
  fi
  command -v npm >/dev/null 2>&1 || fail "npm missing — Node install looks broken."
  ok "npm: $(npm -v)"
}

# ─── User-owned npm prefix (no-sudo fallback) ──────────────

# Only used when a *system* Node's global prefix isn't writable. The
# portable runtime's own prefix is already user-owned, so it skips this.
# We persist NPM_CONFIG_PREFIX too so later self-updates land in the same
# place instead of hitting EACCES again.
setup_user_prefix() {
  mkdir -p "$NPM_PREFIX/lib" "$NPM_PREFIX/bin"
  export NPM_CONFIG_PREFIX="$NPM_PREFIX"
  export PATH="$NPM_PREFIX/bin:$PATH"
  persist_path_line "export PATH=\"\$HOME/.npm-global/bin:\$PATH\"" \
                    "alvin-bot npm global bin"
  persist_path_line "export NPM_CONFIG_PREFIX=\"\$HOME/.npm-global\"" \
                    "alvin-bot npm prefix"
}

# ─── Install ───────────────────────────────────────────────

install_bot() {
  info "Installing alvin-bot from the npm registry..."
  # Skip Chromium downloads — WhatsApp/browser deps are optional and the
  # bot manages Playwright separately when actually needed.
  local env_skip="PUPPETEER_SKIP_DOWNLOAD=1 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true"

  if [ "${USED_PORTABLE_NODE:-0}" = "1" ]; then
    # Portable runtime: pin the global prefix to the runtime dir so a stray
    # user ~/.npmrc (`prefix=…`) can't redirect the `alvin-bot` bin somewhere
    # we never added to PATH. This keeps install location == persisted PATH ==
    # self-update target, all under ~/.alvin-bot/runtime. No sudo needed.
    env $env_skip NPM_CONFIG_PREFIX="$RUNTIME_DIR" npm install -g alvin-bot \
      || fail "npm install -g alvin-bot failed. See the output above."
  else
    # System Node: try the user's existing global prefix first. Only if
    # that fails on permissions do we fall back to a user-owned prefix —
    # never sudo, so we don't scribble into system dirs.
    if env $env_skip npm install -g alvin-bot; then
      :
    else
      warn "Global install failed (likely a read-only npm prefix) — retrying in ~/.npm-global (no sudo)..."
      setup_user_prefix
      env $env_skip npm install -g alvin-bot \
        || fail "npm install -g alvin-bot failed even in a user-owned prefix. See output above."
    fi
  fi

  command -v alvin-bot >/dev/null 2>&1 \
    || fail "alvin-bot installed but not on PATH — open a new terminal and retry."
  ALVIN_BIN="$(command -v alvin-bot)"   # absolute path — works even before PATH reloads
  ok "alvin-bot installed: $(alvin-bot version 2>/dev/null | head -1)"
}

# ─── Main ──────────────────────────────────────────────────

main() {
  # Run from $HOME so a stray ./.npmrc in the caller's working directory
  # (e.g. one pinning `prefix=`) isn't read as npm "project config" — npm
  # rejects a project-level prefix and prints a confusing error otherwise.
  cd "$HOME" 2>/dev/null || true

  echo ""
  echo -e "${BOLD}🦊 Alvin Bot Installer${NC}"
  echo -e "─────────────────────────────────────"
  echo ""

  detect_platform
  ensure_node
  echo ""
  install_bot

  echo ""
  echo -e "─────────────────────────────────────"
  echo -e "${GREEN}${BOLD}🎉 Alvin Bot installed!${NC}"
  echo ""
  echo -e "  Next steps:"
  echo -e "    ${BOLD}alvin-bot setup${NC}    — Configure your bot (pick a provider, paste a token)"
  echo -e "    ${BOLD}alvin-bot start${NC}    — Start the bot"
  echo -e "    ${BOLD}alvin-bot --help${NC}   — Show all commands"
  echo ""

  if is_interactive; then
    info "Starting the setup wizard..."
    echo ""
    alvin-bot setup || warn "Setup skipped. Run 'alvin-bot setup' whenever you're ready."
  else
    # `curl ... | bash` has no TTY for stdin → can't run the interactive wizard.
    # The PATH was just added to your shell rc, but THIS shell doesn't know it
    # yet — so point at the absolute path, which works right now, and mention
    # the new-terminal route for convenience afterwards.
    warn "Piped install — the 'alvin-bot' command isn't on PATH in THIS terminal yet."
    echo ""
    echo -e "  Run setup now (works immediately):"
    echo -e "    ${BOLD}${ALVIN_BIN:-alvin-bot} setup${NC}"
    echo ""
    echo -e "  …or open a ${BOLD}new terminal${NC} and just run:  ${BOLD}alvin-bot setup${NC}"
  fi
}

# Allow sourcing for tests without running main().
if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
  main "$@"
fi
