#!/usr/bin/env bash
#
# Avici CLI installer.
#
# Usage:
#   curl -fsSL https://avici.ai/install.sh | bash
#
# Environment overrides:
#   AVICI_API_URL              API base URL the CLI talks to (default below).
#   AVICI_INSTALL_BASE_URL     Where to download the CLI from (default below).
#                              Point at http://localhost:3000 for local dev.
#   PREFIX                     Install prefix (default: existing Avici prefix,
#                              otherwise $HOME/.avici).
#                              Binary goes to $PREFIX/bin, lib to $PREFIX/lib/avici.
#   AVICI_SKIP_LOGIN           Set to 1 to install without starting QR login.

set -euo pipefail

BASE_URL="${AVICI_INSTALL_BASE_URL:-https://avici.ai}"
if [[ -n "${PREFIX:-}" ]]; then
  PREFIX="$PREFIX"
elif existing_avici=$(command -v avici 2>/dev/null); then
  PREFIX=$(dirname "$(dirname "$existing_avici")")
else
  PREFIX="${HOME}/.avici"
fi
BIN_DIR="${PREFIX}/bin"
LIB_DIR="${PREFIX}/lib/avici"
BIN_DEST="${BIN_DIR}/avici"

CODEX_SKILL_DIR="${HOME}/.codex/skills/avici"
CLAUDE_SKILL_DIR="${HOME}/.claude/skills/avici"

CONFIG_DIR="${HOME}/.avici"
ENV_FILE="${CONFIG_DIR}/env"
RUNTIME_BIN="${CONFIG_DIR}/runtime/bin"

# Colors only when stdout is a TTY (so piped/redirected output stays clean).
if [[ -t 1 ]]; then
  C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'
  C_CYAN=$'\033[36m'; C_GREEN=$'\033[32m'
  C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'
else
  C_RESET=""; C_BOLD=""; C_DIM=""
  C_CYAN=""; C_GREEN=""; C_YELLOW=""; C_RED=""
fi

step() { printf '%s==>%s %s%s%s\n' "$C_CYAN"  "$C_RESET" "$C_BOLD" "$*" "$C_RESET"; }
note() { printf '    %s%s%s\n'     "$C_DIM"   "$*"       "$C_RESET"; }
item() { printf '    %s▸%s %s\n'   "$C_CYAN"  "$C_RESET" "$*"; }
ok()   { printf '%s✓%s %s\n'       "$C_GREEN" "$C_RESET" "$*"; }
warn() { printf '%s!%s %s\n'       "$C_YELLOW" "$C_RESET" "$*" >&2; }
err()  { printf '%s✗%s %s\n'       "$C_RED"   "$C_RESET" "$*" >&2; exit 1; }

# Defaults for env values written to ~/.avici/env. PEM is Rain's public RSA key
# used for the 'cards reveal' handshake; bundled here so fresh installs work
# without manual setup (public keys are not secret).
RAIN_SESSION_PUBLIC_KEY_DEFAULT='-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeZ9uCoxi2XvOw1VmvVLo88TLk
GE+OO1j3fa8HhYlJZZ7CCIAsaCorrU+ZpD5PUTnmME3DJk+JyY1BB3p8XI+C5uno
QucrbxFbkM1lgR10ewz/LcuhleG0mrXL/bzUZbeJqI6v3c9bXvLPKlsordPanYBG
FZkmBPxc8QEdRgH4awIDAQAB
-----END PUBLIC KEY-----'

# Precedence: env var (caller override) > existing ~/.avici/env (preserve on upgrade) > default.
caller_api_url="${AVICI_API_URL-}"
caller_rain_key="${RAIN_SESSION_PUBLIC_KEY-}"
if [[ -f "$ENV_FILE" ]]; then
  # shellcheck disable=SC1090
  source "$ENV_FILE"
fi
AVICI_API_URL="${caller_api_url:-${AVICI_API_URL:-https://api-v1.avici.club/api/v1}}"
RAIN_SESSION_PUBLIC_KEY="${caller_rain_key:-${RAIN_SESSION_PUBLIC_KEY:-$RAIN_SESSION_PUBLIC_KEY_DEFAULT}}"
unset caller_api_url caller_rain_key

NODE_VERSION="${AVICI_NODE_VERSION:-v24.18.0}"
NODE_DIST_URL="${AVICI_NODE_DIST_URL:-https://nodejs.org/dist/${NODE_VERSION}}"
JQ_VERSION="${AVICI_JQ_VERSION:-jq-1.8.2}"
JQ_DIST_URL="${AVICI_JQ_DIST_URL:-https://github.com/jqlang/jq/releases/download/${JQ_VERSION}}"

# Prefer Avici's private runtime when a previous install already provided it.
PATH="${RUNTIME_BIN}:${PATH}"
export PATH

command -v curl >/dev/null 2>&1 || err "curl is required to install avici"

verify_sha256() {
  local file="$1" expected="$2" actual=""
  if command -v shasum >/dev/null 2>&1; then
    actual=$(shasum -a 256 "$file" | awk '{print $1}')
  elif command -v sha256sum >/dev/null 2>&1; then
    actual=$(sha256sum "$file" | awk '{print $1}')
  else
    err "SHA-256 verification is unavailable; Avici was not installed"
  fi
  [[ -n "$expected" && "$actual" == "$expected" ]] \
    || err "dependency checksum verification failed; Avici was not installed"
}

dependency_platform() {
  case "$(uname -s 2>/dev/null || true)" in
    Darwin) printf 'darwin' ;;
    Linux) printf 'linux' ;;
    *) return 1 ;;
  esac
}

dependency_arch() {
  case "$(uname -m 2>/dev/null || true)" in
    arm64|aarch64) printf 'arm64' ;;
    x86_64|amd64) printf 'x64' ;;
    *) return 1 ;;
  esac
}

install_private_jq() {
  local platform arch jq_platform jq_arch asset tmp_dir expected
  platform=$(dependency_platform) || err "unsupported operating system; Avici supports macOS and Linux"
  arch=$(dependency_arch) || err "unsupported CPU architecture; Avici supports arm64 and x86_64"
  case "$platform" in
    darwin) jq_platform="macos" ;;
    linux) jq_platform="linux" ;;
  esac
  case "$arch" in
    arm64) jq_arch="arm64" ;;
    x64) jq_arch="amd64" ;;
  esac
  asset="jq-${jq_platform}-${jq_arch}"
  tmp_dir=$(mktemp -d -t avici-jq.XXXXXX)
  curl -fsSL -o "${tmp_dir}/${asset}" "${JQ_DIST_URL}/${asset}" \
    || err "failed to download private jq runtime"
  curl -fsSL -o "${tmp_dir}/sha256sum.txt" "${JQ_DIST_URL}/sha256sum.txt" \
    || err "failed to download jq checksums"
  expected=$(awk -v name="$asset" '$2 == name || $2 == "*" name {print $1; exit}' \
    "${tmp_dir}/sha256sum.txt")
  verify_sha256 "${tmp_dir}/${asset}" "$expected"
  mkdir -p "$RUNTIME_BIN"
  chmod 755 "${tmp_dir}/${asset}"
  mv "${tmp_dir}/${asset}" "${RUNTIME_BIN}/jq"
  rm -rf -- "$tmp_dir"
  "${RUNTIME_BIN}/jq" --version >/dev/null 2>&1 \
    || err "private jq runtime could not start; Avici was not installed"
}

jq_is_usable() {
  command -v jq >/dev/null 2>&1 && jq --version >/dev/null 2>&1
}

ensure_jq() {
  jq_is_usable && return 0
  step "Installing private jq dependency"
  install_private_jq
  ok "jq installed for Avici only"
}

install_private_node() {
  local platform arch archive folder tmp_dir expected
  platform=$(dependency_platform) || err "unsupported operating system; Avici supports macOS and Linux"
  arch=$(dependency_arch) || err "unsupported CPU architecture; Avici supports arm64 and x86_64"
  archive="node-${NODE_VERSION}-${platform}-${arch}.tar.gz"
  folder="node-${NODE_VERSION}-${platform}-${arch}"
  tmp_dir=$(mktemp -d -t avici-node.XXXXXX)
  curl -fsSL -o "${tmp_dir}/${archive}" "${NODE_DIST_URL}/${archive}" \
    || err "failed to download private Node.js runtime"
  curl -fsSL -o "${tmp_dir}/SHASUMS256.txt" "${NODE_DIST_URL}/SHASUMS256.txt" \
    || err "failed to download Node.js checksums"
  expected=$(awk -v name="$archive" '$2 == name || $2 == "*" name {print $1; exit}' \
    "${tmp_dir}/SHASUMS256.txt")
  verify_sha256 "${tmp_dir}/${archive}" "$expected"
  tar -xzf "${tmp_dir}/${archive}" -C "$tmp_dir" \
    || err "failed to unpack private Node.js runtime"
  [[ -x "${tmp_dir}/${folder}/bin/node" ]] \
    || err "downloaded Node.js runtime is incomplete"
  mkdir -p "$RUNTIME_BIN"
  mv "${tmp_dir}/${folder}/bin/node" "${RUNTIME_BIN}/node.new"
  chmod 755 "${RUNTIME_BIN}/node.new"
  mv "${RUNTIME_BIN}/node.new" "${RUNTIME_BIN}/node"
  rm -rf -- "$tmp_dir"
  "${RUNTIME_BIN}/node" --version >/dev/null 2>&1 \
    || err "private Node.js runtime could not start; Avici was not installed"
}

node_is_usable() {
  command -v node >/dev/null 2>&1 || return 1
  local major
  major=$(node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || true)
  [[ "$major" =~ ^[0-9]+$ ]] && (( major >= 18 ))
}

ensure_node() {
  node_is_usable && return 0
  step "Installing private Node.js dependency"
  install_private_node
  ok "Node.js installed for Avici only"
}

ensure_jq
ensure_node

if ! jq_is_usable || ! node_is_usable; then
  err "Avici dependencies are unavailable; Avici was not installed"
fi

# Returns 0 if writing inside $1 needs sudo.
needs_sudo() {
  local candidate="$1"
  while [[ ! -e "$candidate" ]]; do
    candidate=$(dirname "$candidate")
  done
  [[ ! -w "$candidate" ]]
}

SUDO=""
if needs_sudo "$BIN_DIR" || needs_sudo "$LIB_DIR"; then
  SUDO="sudo"
fi

# download_to <url> <dest> [exec] [user]
# Atomically downloads $1 to $2. If "exec" is passed, marks it executable.
# Passing "user" as $4 prevents a system-prefix sudo from owning files in HOME.
download_to() {
  local url="$1" dest="$2" mode="${3:-}" scope="${4:-system}"
  local tmp http_code
  tmp=$(mktemp -t avici-install.XXXXXX)
  trap "rm -f '$tmp'" EXIT

  http_code=$(curl -fsSL -o "$tmp" -w '%{http_code}' "$url") \
    || err "download of $url failed"
  [[ "$http_code" == "200" ]] || err "download of $url returned HTTP $http_code"
  [[ -s "$tmp" ]] || err "downloaded file from $url is empty"

  [[ "$mode" == "exec" ]] && chmod +x "$tmp"
  if [[ "$scope" == "user" ]]; then
    mv "$tmp" "$dest"
  else
    $SUDO mv "$tmp" "$dest"
  fi
  trap - EXIT
}

install_skill_package() {
  local destination="$1"
  mkdir -p "${destination}/examples"
  download_to "${BASE_URL}/skills/avici/SKILL.md" \
    "${destination}/SKILL.md" "" user
  download_to "${BASE_URL}/skills/avici/reference.md" \
    "${destination}/reference.md" "" user
  download_to "${BASE_URL}/skills/avici/examples/post-login-overview.md" \
    "${destination}/examples/post-login-overview.md" "" user
  download_to "${BASE_URL}/skills/avici/examples/wallet-by-network.md" \
    "${destination}/examples/wallet-by-network.md" "" user
  download_to "${BASE_URL}/skills/avici/examples/card-details.md" \
    "${destination}/examples/card-details.md" "" user
}

# ─── banner ──────────────────────────────────────────────────────────────────
printf '\n  %s%savici installer%s\n\n' "$C_BOLD" "$C_CYAN" "$C_RESET"

# ─── prepare ─────────────────────────────────────────────────────────────────
step "Preparing install directories"
note "binary → $BIN_DIR"
note "libs   → $LIB_DIR"
if [[ -n "$SUDO" ]]; then
  warn "sudo required to write under $PREFIX"
fi
$SUDO mkdir -p "$BIN_DIR" "$LIB_DIR"

# Remove the transaction executors shipped by pre-0.2.0 installations. The
# read-only CLI must not leave an old executable surface behind after upgrade.
$SUDO rm -f "${LIB_DIR}/transaction.sh" "${LIB_DIR}/transaction.cjs"

# ─── download ────────────────────────────────────────────────────────────────
step "Downloading components"
item "avici"
download_to "${BASE_URL}/avici"          "$BIN_DEST"            exec
item "lib/auth.sh"
download_to "${BASE_URL}/lib/auth.sh"    "${LIB_DIR}/auth.sh"
item "lib/qr.cjs"
download_to "${BASE_URL}/lib/qr.cjs"         "${LIB_DIR}/qr.cjs"
item "lib/qr-login.cjs"
download_to "${BASE_URL}/lib/qr-login.cjs"   "${LIB_DIR}/qr-login.cjs" exec
item "lib/wallet.sh"
download_to "${BASE_URL}/lib/wallet.sh"  "${LIB_DIR}/wallet.sh"
item "lib/cards.sh"
download_to "${BASE_URL}/lib/cards.sh"        "${LIB_DIR}/cards.sh"
item "lib/rain.cjs"
download_to "${BASE_URL}/lib/rain.cjs"        "${LIB_DIR}/rain.cjs"
item "lib/activity.sh"
download_to "${BASE_URL}/lib/activity.sh"     "${LIB_DIR}/activity.sh"
item "lib/overview.sh"
download_to "${BASE_URL}/lib/overview.sh"     "${LIB_DIR}/overview.sh"

# ─── agent skills ────────────────────────────────────────────────────────────
step "Installing Avici agent skill"
item "Codex → ${CODEX_SKILL_DIR}"
install_skill_package "$CODEX_SKILL_DIR"
item "Claude → ${CLAUDE_SKILL_DIR}"
install_skill_package "$CLAUDE_SKILL_DIR"

# ─── configure ───────────────────────────────────────────────────────────────
step "Writing configuration"
umask 077
mkdir -p "$CONFIG_DIR"
{
  printf 'AVICI_API_URL=%q\n' "$AVICI_API_URL"
  printf 'AVICI_RUNTIME_BIN=%q\n' "$RUNTIME_BIN"
  # PEM has no single quotes; safe to wrap in single quotes verbatim (preserves newlines).
  printf "RAIN_SESSION_PUBLIC_KEY='%s'\n" "$RAIN_SESSION_PUBLIC_KEY"
} > "$ENV_FILE"
chmod 600 "$ENV_FILE"
note "config file: $ENV_FILE"

if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then
  warn "$BIN_DIR is not in your PATH"
  note 'add this to ~/.zshrc or ~/.bashrc:'
  note "  export PATH=\"$BIN_DIR:\$PATH\""
fi

# ─── summary ─────────────────────────────────────────────────────────────────
printf '\n'
ok "Installation complete"
printf '\n'
printf '  %sbinary%s   %s\n' "$C_DIM" "$C_RESET" "$BIN_DEST"
printf '  %slibs%s     %s\n' "$C_DIM" "$C_RESET" "$LIB_DIR"
printf '  %sconfig%s   %s\n' "$C_DIM" "$C_RESET" "$ENV_FILE"
printf '  %sCodex%s    %s\n' "$C_DIM" "$C_RESET" "$CODEX_SKILL_DIR"
printf '  %sClaude%s   %s\n' "$C_DIM" "$C_RESET" "$CLAUDE_SKILL_DIR"
printf '\n'
note "Open a new Claude or Codex chat so the updated Avici skill is loaded."
printf '\n'

if [[ "${AVICI_SKIP_LOGIN:-0}" == "1" ]]; then
  note "Automatic login skipped (AVICI_SKIP_LOGIN=1)."
  printf '\n'
else
  step "Starting Avici login"
  "$BIN_DEST" login --qr
  printf '\n'
fi
