#!/bin/sh
# llm-speed one-shot installer (bare-OS bootstrap).
#
#   curl -fsSL https://llm-speed.com/install.sh | sh
#
# The pipx / uv one-liners in the README assume Python >= 3.10 is ALREADY on
# the box. This script handles the bare case: no Python, an old Python, or no
# package installer at all. It prompts before doing anything and only
# auto-installs permissively licensed tooling (uv: Apache-2.0/MIT, Python via
# uv: PSF) — "prompt + guide, auto-install where licenses allow". Once the CLI
# is on PATH it hands off to `llm-speed doctor`, which sets up a benchmark
# backend the same way.
#
# Installer selection (most isolated first):
#   uv tool install     — preferred; brings its own Python, sidesteps PEP 668
#   pipx install        — if already present
#   pip install --user  — only when a usable system pip exists (e.g. python:slim)
#   else                — bootstrap uv (needs curl/wget) and use it
#
# Knobs (env or flags), used by the Docker test matrix:
#   --yes / LLM_SPEED_ASSUME_YES=1      assume "yes" to every prompt (CI)
#   --source S / LLM_SPEED_INSTALL_SOURCE=S
#                                       what to install (default: PyPI "llm-speed";
#                                       a local wheel path for offline tests)
#   --no-doctor / LLM_SPEED_RUN_DOCTOR=0
#                                       skip the trailing `llm-speed doctor`
#   LLM_SPEED_PYTHON_VERSION=3.12       Python uv provisions when needed
#   LLM_SPEED_INSTALL_ARGS="..."        extra args passed to the installer
#
# POSIX sh; no bashisms. shellcheck-clean.
set -eu

ASSUME_YES="${LLM_SPEED_ASSUME_YES:-0}"
INSTALL_SOURCE="${LLM_SPEED_INSTALL_SOURCE:-llm-speed}"
RUN_DOCTOR="${LLM_SPEED_RUN_DOCTOR:-1}"
PY_VERSION="${LLM_SPEED_PYTHON_VERSION:-3.12}"
INSTALL_ARGS="${LLM_SPEED_INSTALL_ARGS:-}"

usage() {
    cat <<'EOF'
llm-speed installer

Usage: install.sh [--yes] [--source SRC] [--no-doctor]

  --yes, -y         assume "yes" to all prompts (non-interactive)
  --source SRC      install target (default: llm-speed from PyPI;
                    pass a .whl path or an alternate index for testing)
  --no-doctor       don't run `llm-speed doctor` at the end
  --help, -h        show this help
EOF
}

while [ $# -gt 0 ]; do
    case "$1" in
        -y | --yes) ASSUME_YES=1 ;;
        --source)
            INSTALL_SOURCE="${2:?--source needs a value}"
            shift
            ;;
        --no-doctor) RUN_DOCTOR=0 ;;
        -h | --help)
            usage
            exit 0
            ;;
        *)
            printf 'unknown argument: %s\n' "$1" >&2
            usage >&2
            exit 2
            ;;
    esac
    shift
done

say() { printf '\033[1;36m==>\033[0m %s\n' "$1"; }
warn() { printf '\033[1;33mwarn:\033[0m %s\n' "$1" >&2; }
die() {
    printf '\033[1;31merror:\033[0m %s\n' "$1" >&2
    exit 1
}
have() { command -v "$1" >/dev/null 2>&1; }

# Ask a yes/no question. Honors --yes; on a non-TTY without --yes we refuse
# (return 1) rather than silently installing — same discipline as the CLI's
# consent prompt.
confirm() {
    if [ "$ASSUME_YES" = "1" ]; then
        return 0
    fi
    if [ ! -t 0 ]; then
        warn "stdin is not a TTY and --yes not given; skipping: $1"
        return 1
    fi
    printf '%s [y/N] ' "$1"
    read -r _ans || return 1
    case "$_ans" in
        y | Y | yes | YES) return 0 ;;
        *) return 1 ;;
    esac
}

# Download $1 to stdout using curl or wget (whichever exists).
fetch() {
    if have curl; then
        curl -LsSf "$1"
    elif have wget; then
        wget -qO- "$1"
    else
        die "need curl or wget to download $1"
    fi
}

add_local_bins_to_path() {
    for d in "$HOME/.local/bin" "$HOME/.cargo/bin"; do
        case ":$PATH:" in
            *":$d:"*) ;;
            *) [ -d "$d" ] && PATH="$d:$PATH" ;;
        esac
    done
    export PATH
}

# Find a python3 >= 3.10 on PATH (echoes the command name, or nothing).
find_python() {
    for cand in python3 python; do
        if have "$cand" && "$cand" -c 'import sys; raise SystemExit(0 if sys.version_info[:2] >= (3, 10) else 1)' 2>/dev/null; then
            printf '%s' "$cand"
            return 0
        fi
    done
    return 1
}

bootstrap_uv() {
    have uv && return 0
    if confirm "Install uv (Astral, Apache-2.0/MIT) to manage Python + install the CLI?"; then
        fetch https://astral.sh/uv/install.sh | sh
        add_local_bins_to_path
    fi
    have uv
}

# Install the CLI with the most isolated installer available, bootstrapping uv
# when nothing usable exists. INSTALL_ARGS is intentionally word-split.
install_cli() {
    if have uv; then
        say "Installing the CLI via: uv tool install (Python $PY_VERSION)"
        # shellcheck disable=SC2086
        uv tool install --python "$PY_VERSION" $INSTALL_ARGS "$INSTALL_SOURCE"
        return 0
    fi
    if have pipx; then
        say "Installing the CLI via: pipx install"
        # shellcheck disable=SC2086
        pipx install $INSTALL_ARGS "$INSTALL_SOURCE"
        return 0
    fi
    if [ -n "$PY" ] && "$PY" -m pip --version >/dev/null 2>&1; then
        say "Installing the CLI via: pip install --user"
        # shellcheck disable=SC2086
        if "$PY" -m pip install --user $INSTALL_ARGS "$INSTALL_SOURCE"; then
            return 0
        fi
        warn "pip install failed (often PEP 668 'externally managed') — falling back to uv."
    fi
    bootstrap_uv || die "no usable installer (need uv, pipx, or pip). Install one and re-run."
    say "Installing the CLI via: uv tool install (Python $PY_VERSION)"
    # shellcheck disable=SC2086
    uv tool install --python "$PY_VERSION" $INSTALL_ARGS "$INSTALL_SOURCE"
}

# ----------------------------------------------------------------------------

OS="$(uname -s 2>/dev/null || echo unknown)"
ARCH="$(uname -m 2>/dev/null || echo unknown)"
say "llm-speed installer  (os=$OS arch=$ARCH)"

add_local_bins_to_path
PY="$(find_python || true)"
if [ -z "$PY" ]; then
    say "No Python >= 3.10 found — uv will provision Python $PY_VERSION during install."
fi

install_cli
add_local_bins_to_path

# Report the version of the binary we just installed, not whatever an older
# PATH entry shadows it with (uv/pipx both land in ~/.local/bin; honor
# UV_TOOL_BIN_DIR when set).
LLM_SPEED_BIN=""
for cand in "${UV_TOOL_BIN_DIR:-}/llm-speed" "$HOME/.local/bin/llm-speed"; do
    [ -x "$cand" ] && { LLM_SPEED_BIN="$cand"; break; }
done
if [ -z "$LLM_SPEED_BIN" ] && have llm-speed; then
    LLM_SPEED_BIN="$(command -v llm-speed)"
fi
if [ -n "$LLM_SPEED_BIN" ]; then
    say "Installed: $("$LLM_SPEED_BIN" --version 2>/dev/null || echo 'llm-speed (version unknown)')"
else
    warn "llm-speed isn't on your PATH yet. Add ~/.local/bin (or the uv tools dir) to PATH, then open a new shell."
fi

if [ "$RUN_DOCTOR" = "1" ] && have llm-speed; then
    if confirm "Run 'llm-speed doctor' now to set up a benchmark backend?"; then
        llm-speed doctor || true
    else
        say "Skipped. Run 'llm-speed doctor' any time to set up a backend."
    fi
fi

say "Done. Next: 'llm-speed doctor' (set up a backend) then 'llm-speed bench --quick'."
