#!/bin/sh
set -eu

BASE_URL="${BUD_INSTALL_BASE_URL:-https://get.bud.dev}"
SERVER_URL="${BUD_SERVER_URL:-wss://app.bud.dev/ws}"
INSTALL_ROOT="${BUD_INSTALL_ROOT:-$HOME/.bud}"
BIN_DIR="$INSTALL_ROOT/bin"
BUD_BIN="$BIN_DIR/bud"
IDENTITY_FILE="$INSTALL_ROOT/identity.json"
ENV_FILE="$INSTALL_ROOT/bud.env"
MANIFEST_URL="$BASE_URL/releases/stable/manifest.json"

log() {
  printf '%s\n' "$*" >&2
}

fail() {
  log "error: $*"
  exit 1
}

usage_matrix() {
  cat >&2 <<'EOF'
Supported Bud installer targets:
  macOS 13+ arm64      -> aarch64-apple-darwin
  macOS 13+ x86_64     -> x86_64-apple-darwin
  Linux glibc 2.35+ x86_64 -> x86_64-unknown-linux-gnu
  Linux glibc 2.35+ aarch64 -> aarch64-unknown-linux-gnu
EOF
}

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1"
}

download_file() {
  url="$1"
  dest="$2"
  if command -v curl >/dev/null 2>&1; then
    curl -fsSL "$url" -o "$dest"
    return
  fi
  if command -v wget >/dev/null 2>&1; then
    wget -q -O "$dest" "$url"
    return
  fi
  fail "missing required command: curl or wget"
}

sha256_file() {
  file="$1"
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$file" | awk '{print $1}'
    return
  fi
  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$file" | awk '{print $1}'
    return
  fi
  fail "missing required command: sha256sum or shasum"
}

shell_quote() {
  printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"
}

write_env_file() {
  mkdir -p "$INSTALL_ROOT"
  {
    printf 'BUD_SERVER_URL=%s\n' "$(shell_quote "$SERVER_URL")"
    printf 'BUD_TERMINAL_ENABLED=true\n'
    printf 'BUD_BASE_DIR=%s\n' "$(shell_quote "$INSTALL_ROOT")"
    printf 'BUD_DEVICE_NAME=%s\n' "$(shell_quote "$DEVICE_NAME")"
  } > "$ENV_FILE"
}

default_device_name() {
  h="$(hostname -s 2>/dev/null || uname -n 2>/dev/null || true)"
  h="${h%%.*}"
  [ "$h" ] || h="bud"
  printf '%s' "$h"
}

# Pick the Bud's display name before the claim (the name rides the claim
# metadata). BUD_INSTALL_NAME=<name> skips the prompt; otherwise a tty
# prompt defaults to the machine's short hostname; non-interactive installs
# take the hostname silently. If the owning account already has a Bud with
# the chosen name, the service suffixes it (host-2, host-3, ...).
setup_device_name() {
  if [ "${BUD_INSTALL_NAME:-}" ]; then
    DEVICE_NAME="$BUD_INSTALL_NAME"
    log "Naming this Bud '$DEVICE_NAME' (from BUD_INSTALL_NAME)."
    return
  fi
  DEVICE_NAME="$(default_device_name)"
  if [ "${BUD_INSTALL_NO_NAME_PROMPT:-}" != "1" ] && (: < /dev/tty) 2>/dev/null; then
    printf 'Name this Bud [%s]: ' "$DEVICE_NAME" > /dev/tty
    IFS= read -r answer < /dev/tty || answer=""
    [ "$answer" ] && DEVICE_NAME="$answer"
  fi
  log "This Bud will be named '$DEVICE_NAME' (a -2/-3 suffix is added automatically if the name is taken on your account)."
}

version_major_minor_at_least() {
  version="$1"
  min_major="$2"
  min_minor="$3"
  major="$(printf '%s' "$version" | awk -F. '{print $1}')"
  minor="$(printf '%s' "$version" | awk -F. '{print $2}')"
  case "$major:$minor" in
    *[!0-9:]* | :* | *:) return 1 ;;
  esac
  [ "$major" -gt "$min_major" ] || { [ "$major" -eq "$min_major" ] && [ "$minor" -ge "$min_minor" ]; }
}

detect_glibc_version() {
  if [ "${BUD_INSTALL_GLIBC_VERSION:-}" ]; then
    printf '%s\n' "$BUD_INSTALL_GLIBC_VERSION"
    return
  fi
  if command -v getconf >/dev/null 2>&1; then
    getconf GNU_LIBC_VERSION 2>/dev/null | awk '{print $2}'
    return
  fi
  if command -v ldd >/dev/null 2>&1; then
    ldd --version 2>&1 | awk 'NR == 1 { for (i = 1; i <= NF; i++) if ($i ~ /^[0-9]+\.[0-9]+/) { print $i; exit } }'
  fi
}

detect_target() {
  os="${BUD_INSTALL_OS:-$(uname -s)}"
  arch="${BUD_INSTALL_ARCH:-$(uname -m)}"

  case "$os:$arch" in
    Darwin:arm64 | Darwin:aarch64)
      macos_version="${BUD_INSTALL_MACOS_VERSION:-$(sw_vers -productVersion 2>/dev/null || printf '')}"
      if ! version_major_minor_at_least "$macos_version" 13 0; then
        usage_matrix
        fail "macOS 13+ is required; detected ${macos_version:-unknown}"
      fi
      printf '%s\n' "aarch64-apple-darwin"
      ;;
    Darwin:x86_64 | Darwin:amd64)
      macos_version="${BUD_INSTALL_MACOS_VERSION:-$(sw_vers -productVersion 2>/dev/null || printf '')}"
      if ! version_major_minor_at_least "$macos_version" 13 0; then
        usage_matrix
        fail "macOS 13+ is required; detected ${macos_version:-unknown}"
      fi
      printf '%s\n' "x86_64-apple-darwin"
      ;;
    Linux:x86_64 | Linux:amd64)
      glibc_version="$(detect_glibc_version || true)"
      if ! version_major_minor_at_least "${glibc_version:-}" 2 35; then
        usage_matrix
        fail "glibc 2.35+ is required; detected ${glibc_version:-unknown}"
      fi
      printf '%s\n' "x86_64-unknown-linux-gnu"
      ;;
    Linux:aarch64 | Linux:arm64)
      glibc_version="$(detect_glibc_version || true)"
      if ! version_major_minor_at_least "${glibc_version:-}" 2 35; then
        usage_matrix
        fail "glibc 2.35+ is required; detected ${glibc_version:-unknown}"
      fi
      printf '%s\n' "aarch64-unknown-linux-gnu"
      ;;
    *)
      usage_matrix
      fail "unsupported OS/architecture: $os/$arch"
      ;;
  esac
}

manifest_field_for_target() {
  manifest="$1"
  target="$2"
  field="$3"
  awk -v target="$target" -v field="$field" '
    $0 ~ "\"target\"" {
      in_artifact = index($0, "\"" target "\"") > 0
    }
    in_artifact && $0 ~ "\"" field "\"" {
      line = $0
      sub(/^[^:]*:[[:space:]]*/, "", line)
      gsub(/[",]/, "", line)
      gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
      print line
      exit
    }
  ' "$manifest"
}

verify_checksum() {
  archive="$1"
  expected="$2"
  actual="$(sha256_file "$archive")"
  if [ "$actual" != "$expected" ]; then
    rm -f "$archive"
    fail "checksum mismatch for archive: expected $expected, got $actual"
  fi
}

install_archive() {
  archive="$1"
  tmp_extract="$2"
  mkdir -p "$tmp_extract"
  tar -xzf "$archive" -C "$tmp_extract"
  [ -x "$tmp_extract/bud" ] || [ -x "$tmp_extract/./bud" ] || fail "release archive did not contain executable bud"
  mkdir -p "$BIN_DIR"
  # Stage next to the destination and rename: overwriting a RUNNING binary
  # in place fails on Linux with ETXTBSY ("Text file busy"), while rename
  # atomically swaps the directory entry and leaves the executing inode
  # untouched. A live daemon keeps running the old binary until restarted.
  staged="$BIN_DIR/.bud.install.$$"
  cp "$tmp_extract/bud" "$staged"
  chmod 0755 "$staged"
  mv -f "$staged" "$BUD_BIN"
}

# --- PATH setup -------------------------------------------------------------
# Offers to add ~/.bud/bin to the user's shell profile so `bud status` etc.
# work after install. Confirmation is read from /dev/tty because stdin is the
# piped script under `curl | sh`. Knobs: BUD_INSTALL_MODIFY_PATH=1 (yes
# without prompting), BUD_INSTALL_NO_MODIFY_PATH=1 (never touch profiles).

profile_file_for_shell() {
  case "$(basename "${SHELL:-/bin/sh}")" in
    zsh) printf '%s\n' "${ZDOTDIR:-$HOME}/.zshrc" ;;
    bash) printf '%s\n' "$HOME/.bashrc" ;;
    fish) printf '%s\n' "$HOME/.config/fish/conf.d/bud.fish" ;;
    *) printf '%s\n' "$HOME/.profile" ;;
  esac
}

path_already_configured() {
  case ":$PATH:" in
    *":$BIN_DIR:"*) return 0 ;;
  esac
  [ -f "$1" ] && grep -F "$BIN_DIR" "$1" >/dev/null 2>&1
}

append_path_line() {
  profile="$1"
  profile_dir="$(dirname "$profile")"
  mkdir -p "$profile_dir"
  if [ "$(basename "${SHELL:-/bin/sh}")" = "fish" ]; then
    printf '# Added by the Bud installer\nfish_add_path -g %s\n' "$BIN_DIR" >> "$profile"
  else
    printf '\n# Added by the Bud installer\nexport PATH=%s:"$PATH"\n' "$(shell_quote "$BIN_DIR")" >> "$profile"
  fi
}

print_path_hint() {
  log "To use the \`bud\` command directly, add it to your PATH:"
  log "  export PATH=\"$BIN_DIR:\$PATH\""
}

setup_path() {
  if [ "${BUD_INSTALL_NO_MODIFY_PATH:-}" = "1" ]; then
    return
  fi
  profile="$(profile_file_for_shell)"
  if path_already_configured "$profile"; then
    return
  fi

  answer=""
  if [ "${BUD_INSTALL_MODIFY_PATH:-}" = "1" ]; then
    answer="y"
  elif (: < /dev/tty) 2>/dev/null; then
    # -r/-w on /dev/tty pass even without a controlling terminal; only an
    # actual open proves one exists (stdin is the piped script under
    # `curl | sh`, so the prompt must go through the tty).
    printf 'Add %s to your PATH in %s? [Y/n] ' "$BIN_DIR" "$profile" > /dev/tty
    IFS= read -r answer < /dev/tty || answer="n"
    [ "$answer" ] || answer="y"
  else
    # Non-interactive (no tty): never edit profiles silently.
    print_path_hint
    log "(rerun with BUD_INSTALL_MODIFY_PATH=1 to do this automatically)"
    return
  fi

  case "$answer" in
    y | Y | yes | YES)
      append_path_line "$profile"
      log "Added $BIN_DIR to PATH in $profile."
      # Remembered so the very LAST line of the install repeats the
      # activation hint — this one scrolls away behind the claim QR and
      # service output.
      PATH_PROFILE_UPDATED="$profile"
      ;;
    *)
      log "Skipped PATH setup."
      print_path_hint
      ;;
  esac
}

print_path_activation_hint() {
  if [ "${PATH_PROFILE_UPDATED:-}" ]; then
    log ""
    log "To use the \`bud\` command in THIS shell, run:"
    log "  source $PATH_PROFILE_UPDATED"
    log "(new terminals pick it up automatically)"
  fi
}

run_doctor() {
  log "Running Bud preflight..."
  if ! (unset BUD_CLAIM_ID; env BUD_SERVER_URL="$SERVER_URL" BUD_TERMINAL_ENABLED=true BUD_BASE_DIR="$INSTALL_ROOT" BUD_DEVICE_NAME="$DEVICE_NAME" "$BUD_BIN" doctor); then
    log "Bud preflight reported issues. Review the messages above, then rerun the installer after resolving them."
  fi
}

# --- Local LLM setup ---------------------------------------------------------
# Detection lives in the bud binary (`bud llm probe/enable`) so the installer
# and daemon share one rule. Knobs: BUD_INSTALL_DS4_URL=<url> (enable without
# probing candidates), BUD_INSTALL_NO_LLM_PROBE=1 (skip entirely).

setup_local_llm() {
  if [ "${BUD_INSTALL_NO_LLM_PROBE:-}" = "1" ]; then
    return
  fi

  if [ "${BUD_INSTALL_DS4_URL:-}" ]; then
    if env BUD_BASE_DIR="$INSTALL_ROOT" "$BUD_BIN" llm enable "$BUD_INSTALL_DS4_URL"; then
      log "Local LLM endpoint configured from BUD_INSTALL_DS4_URL."
    else
      log "warning: could not enable local LLM at $BUD_INSTALL_DS4_URL (continuing)."
    fi
    return
  fi

  for candidate in http://127.0.0.1:8888/v1 http://127.0.0.1:8000/v1; do
    # Only VALIDATED model families (DeepSeek v4) are auto-offered; other
    # local servers get a suggestion instead — the installer must not tee
    # users into an unvalidated agent experience.
    if env BUD_BASE_DIR="$INSTALL_ROOT" "$BUD_BIN" llm probe --url "$candidate" --require-validated >/dev/null 2>&1; then
      answer="n"
      if (: < /dev/tty) 2>/dev/null; then
        printf 'Found a local DeepSeek v4 server at %s. Enable it for this Bud? [Y/n] ' "$candidate" > /dev/tty
        IFS= read -r answer < /dev/tty || answer="n"
        [ "$answer" ] || answer="y"
      else
        log "Found a local DeepSeek v4 server at $candidate."
        log "Enable it later with: $BUD_BIN llm enable $candidate"
        return
      fi
      case "$answer" in
        y | Y | yes | YES)
          env BUD_BASE_DIR="$INSTALL_ROOT" "$BUD_BIN" llm enable "$candidate" || log "warning: enabling local LLM failed (continuing)."
          ;;
        *)
          log "Skipped local LLM setup. Enable it later with: $BUD_BIN llm enable $candidate"
          ;;
      esac
      return
    elif env BUD_BASE_DIR="$INSTALL_ROOT" "$BUD_BIN" llm probe --url "$candidate" >/dev/null 2>&1; then
      log "Found a local LLM server at $candidate (experimental models)."
      log "Enable it with: $BUD_BIN llm enable $candidate"
      return
    fi
  done
}

bootstrap_bud() {
  if [ "${BUD_INSTALL_SKIP_BOOTSTRAP:-}" = "1" ]; then
    log "Skipping Bud bootstrap because BUD_INSTALL_SKIP_BOOTSTRAP=1."
    log "Start Bud manually with: $BUD_BIN --terminal-enabled"
    print_path_activation_hint
    return
  fi

  if [ "${BUD_INSTALL_FOREGROUND:-}" = "1" ]; then
    log "Starting Bud in the foreground. Press Ctrl+C to stop it."
    if [ "${BUD_CLAIM_ID:-}" ]; then
      exec env BUD_SERVER_URL="$SERVER_URL" BUD_TERMINAL_ENABLED=true BUD_BASE_DIR="$INSTALL_ROOT" BUD_DEVICE_NAME="$DEVICE_NAME" BUD_CLAIM_ID="$BUD_CLAIM_ID" "$BUD_BIN" --terminal-enabled
    fi
    exec env BUD_SERVER_URL="$SERVER_URL" BUD_TERMINAL_ENABLED=true BUD_BASE_DIR="$INSTALL_ROOT" BUD_DEVICE_NAME="$DEVICE_NAME" "$BUD_BIN" --terminal-enabled
  fi

  # Standard flow: complete the interactive claim (link/QR, or install-token
  # redemption) in the foreground, then hand the daemon to the platform
  # background service so it survives terminal close, logout, and reboot.
  if [ "${BUD_CLAIM_ID:-}" ]; then
    env BUD_SERVER_URL="$SERVER_URL" BUD_TERMINAL_ENABLED=true BUD_BASE_DIR="$INSTALL_ROOT" BUD_DEVICE_NAME="$DEVICE_NAME" BUD_CLAIM_ID="$BUD_CLAIM_ID" "$BUD_BIN" claim || fail "Bud device claim failed"
  else
    env BUD_SERVER_URL="$SERVER_URL" BUD_TERMINAL_ENABLED=true BUD_BASE_DIR="$INSTALL_ROOT" BUD_DEVICE_NAME="$DEVICE_NAME" "$BUD_BIN" claim || fail "Bud device claim failed"
  fi

  setup_local_llm

  if env BUD_SERVER_URL="$SERVER_URL" BUD_TERMINAL_ENABLED=true BUD_BASE_DIR="$INSTALL_ROOT" BUD_DEVICE_NAME="$DEVICE_NAME" "$BUD_BIN" service install; then
    log "Bud is installed as a background service. Check it with: $BUD_BIN status"
    print_path_activation_hint
  else
    log "No supported background service manager; starting Bud in the foreground. Press Ctrl+C to stop it."
    exec env BUD_SERVER_URL="$SERVER_URL" BUD_TERMINAL_ENABLED=true BUD_BASE_DIR="$INSTALL_ROOT" BUD_DEVICE_NAME="$DEVICE_NAME" "$BUD_BIN" --terminal-enabled
  fi
}

main() {
  need_cmd awk
  need_cmd sed
  need_cmd tar
  need_cmd mktemp

  if [ -f "$IDENTITY_FILE" ] && [ "${BUD_CLAIM_ID:-}" ]; then
    fail "existing Bud identity found at $IDENTITY_FILE; refusing to redeem a new install claim over an existing identity"
  fi

  target="$(detect_target)"
  tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/bud-install.XXXXXX")"
  trap 'rm -rf "$tmp_dir"' EXIT HUP INT TERM

  manifest="$tmp_dir/manifest.json"
  archive="$tmp_dir/bud.tar.gz"
  extract_dir="$tmp_dir/extract"

  log "Downloading Bud release manifest..."
  download_file "$MANIFEST_URL" "$manifest"

  artifact_url="$(manifest_field_for_target "$manifest" "$target" "url")"
  artifact_sha="$(manifest_field_for_target "$manifest" "$target" "sha256")"
  [ "$artifact_url" ] || fail "manifest did not contain artifact URL for $target"
  [ "$artifact_sha" ] || fail "manifest did not contain SHA-256 for $target"

  log "Downloading Bud for $target..."
  download_file "$artifact_url" "$archive"

  log "Verifying archive checksum..."
  verify_checksum "$archive" "$artifact_sha"

  log "Installing Bud to $BUD_BIN..."
  install_archive "$archive" "$extract_dir"
  setup_device_name
  write_env_file
  setup_path
  run_doctor

  log "Bud installed."
  bootstrap_bud
}

main "$@"
