#!/usr/bin/env bash
SLO_REF="${SLO_REF:-v0.6.1}"  # pinned at publish time by deploy-install-worker.sh
# install.sh — one-line installer for slo.
#
#   curl -fsSL <url>/install.sh | SLO_USERNAME='<user>' SLO_TOKEN='<token>' bash
#
# Installs slo as a normal pipx application straight from its git repository.
# pipx builds the package in its own isolated venv and puts the `slo` binary on
# your PATH. NOTHING is written to SLO_HOME (~/.slo) — that directory is slo's
# *runtime data* (sessions, logs, credentials), NOT a code checkout. The old
# installer cloned the repo into ~/.slo, which collides with that data and
# aborts ("destination path already exists") on any machine where slo has run.
#
# On a bare machine this first provisions the toolchain pipx needs: Homebrew,
# pipx, pyenv, the pinned Python (3.11.x), and git (pip's VCS fetch needs it).
# Every step is a no-op when already satisfied, so re-running upgrades slo in
# place. No interactive prompts.
#
# Configuration (all overridable from the environment):
#   SLO_USERNAME       credential username for the private repo fetch
#   SLO_TOKEN          credential secret for the private repo fetch
#   SLO_VERSION        version to install (default: pinned release baked in at
#                      publish time). Pass "dev" to track the main branch.
#   SLO_REPO_URL       git URL to install from (default below)
#   SLO_PYTHON_VERSION pinned interpreter version (default: 3.11.7)
#
# Testability: every function below is sourceable. The `main` dispatch at the
# bottom runs only when the script is executed directly, not when sourced — so
# the unit tests in tests/cli/test_install.py can source this file and exercise
# each function against a stubbed PATH without mutating the host.
set -euo pipefail

# ---- Configuration (single source of truth) --------------------------------
SLO_REPO_URL="${SLO_REPO_URL:-https://gitlab.com/core-27/slo.git}"
SLO_REF="${SLO_REF:-main}"
SLO_PYTHON_VERSION="${SLO_PYTHON_VERSION:-3.11.7}"

# ---- User-facing credential and version aliases ----------------------------
# SLO_USERNAME / SLO_TOKEN are the public names; map to internal deploy-token
# vars used by slo_pipx_spec(). SLO_VERSION controls the git ref to install:
#   unset / empty → use SLO_REF (default pinned at publish time by deploy script)
#   "dev"         → main branch
#   anything else → treated as a release tag or branch name
SLO_DEPLOY_TOKEN_USERNAME="${SLO_USERNAME:-${SLO_DEPLOY_TOKEN_USERNAME:-}}"
SLO_DEPLOY_TOKEN="${SLO_TOKEN:-${SLO_DEPLOY_TOKEN:-}}"
if [ "${SLO_VERSION:-}" = "dev" ]; then
    SLO_REF="main"
elif [ -n "${SLO_VERSION:-}" ]; then
    SLO_REF="$SLO_VERSION"
fi

# ---- Small helpers ---------------------------------------------------------
slo_log() {
    # Emit a progress line to stderr so stdout stays reserved for any future
    # machine-readable output.
    echo "→ $*" >&2
}

slo_has() {
    # Return success when an executable named "$1" is resolvable on PATH.
    command -v "$1" >/dev/null 2>&1
}

slo_os_kind() {
    # Echo a normalized OS token: "macos", "linux", or "unsupported".
    case "$(uname -s)" in
        Darwin) echo "macos" ;;
        Linux) echo "linux" ;;
        *) echo "unsupported" ;;
    esac
}

# ---- 1. Homebrew (prerequisite for installing pyenv via brew) --------------
slo_ensure_brew() {
    # Install Homebrew when absent, then put it on PATH for the rest of this run.
    if slo_has brew; then
        slo_log "brew present"
        return 0
    fi
    slo_log "installing Homebrew (non-interactive)"
    NONINTERACTIVE=1 /bin/bash -c \
        "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    slo_activate_brew
}

slo_activate_brew() {
    # Evaluate `brew shellenv` from the first Homebrew prefix that exists so brew
    # and its installed shims are usable later in this same script invocation.
    local prefix
    for prefix in /opt/homebrew /usr/local /home/linuxbrew/.linuxbrew; do
        if [ -x "$prefix/bin/brew" ]; then
            eval "$("$prefix/bin/brew" shellenv)"
            return 0
        fi
    done
}

# ---- 2. pipx (OS-aware) ----------------------------------------------------
slo_ensure_pipx() {
    # Install pipx when absent. macOS uses brew; Linux uses pip --user (the
    # platform-standard paths). No-op when pipx already resolves.
    if slo_has pipx; then
        slo_log "pipx present"
        return 0
    fi
    local os
    os="$(slo_os_kind)"
    slo_log "installing pipx for $os"
    if [ "$os" = "macos" ]; then
        brew install pipx
    else
        python3 -m pip install --user pipx
    fi
    pipx ensurepath || true
}

# ---- 3. pyenv (via brew) ---------------------------------------------------
slo_ensure_pyenv() {
    # Install pyenv via brew when absent, then initialize it so subsequent
    # `pyenv install` / shim lookups work within this script.
    if slo_has pyenv; then
        slo_log "pyenv present"
    else
        slo_log "installing pyenv via brew"
        brew install pyenv
    fi
    slo_activate_pyenv
}

slo_activate_pyenv() {
    # Put pyenv's bin + shims on PATH and run `pyenv init` for this shell so the
    # pinned interpreter is resolvable as `python3.11` after installation.
    export PYENV_ROOT="${PYENV_ROOT:-$HOME/.pyenv}"
    export PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"
    if slo_has pyenv; then
        eval "$(pyenv init - 2>/dev/null)" || true
    fi
}

# ---- 4. Pinned Python via pyenv --------------------------------------------
slo_ensure_python() {
    # Install the pinned Python version via pyenv (-s skips when already present)
    # and rehash shims so `python3.11` resolves.
    slo_log "ensuring Python $SLO_PYTHON_VERSION via pyenv"
    pyenv install -s "$SLO_PYTHON_VERSION"
    pyenv rehash || true
}

# ---- 5. SLO_HOME (runtime data dir — created, never used as a checkout) -----
slo_ensure_home() {
    # Honour an existing SLO_HOME; otherwise default to ~/.slo. This is slo's
    # runtime data directory (sessions, logs, credentials). We only ensure it
    # exists so first run has a home — the install itself writes nothing here.
    # Echoes the resolved path.
    if [ -z "${SLO_HOME:-}" ]; then
        SLO_HOME="$HOME/.slo"
        slo_log "SLO_HOME unset; defaulting to $SLO_HOME"
    else
        slo_log "SLO_HOME=$SLO_HOME (from environment)"
    fi
    mkdir -p "$SLO_HOME"
    export SLO_HOME
    echo "$SLO_HOME"
}

# ---- git (required for pip's git+ VCS fetch) -------------------------------
slo_ensure_git() {
    # git is mandatory for the VCS install. Install via brew on macOS; on Linux
    # defer to the system package manager (error with guidance) rather than guess.
    if slo_has git; then
        slo_log "git present"
        return 0
    fi
    local os
    os="$(slo_os_kind)"
    slo_log "installing git for $os"
    if [ "$os" = "macos" ]; then
        brew install git
        return 0
    fi
    echo "error: git not found. Install it via your package manager and re-run." >&2
    exit 1
}

# ---- 6. Install via pipx straight from the repo ----------------------------
slo_pipx_spec() {
    # Echo the pip VCS requirement pipx installs from:
    #   git+<url>@<ref>#subdirectory=<subdir>
    # The Python project lives in the repo's `slo/` subdirectory, not the repo
    # root, so pip must be told where pyproject.toml is via #subdirectory. Set
    # SLO_REPO_SUBDIR to override; set it to empty for a root-level project.
    # On an https URL, inject the deploy-token credential for this run.
    # SLO_USERNAME + SLO_TOKEN (deploy token) authenticate as <user>:<token>.
    # Credentials with URL-reserved characters (e.g. ':' '@' '/') must be
    # percent-encoded by the caller. The credential is only ever passed to git
    # for this fetch — it is not written to disk by this script.
    local url="$SLO_REPO_URL"
    local rest="${url#https://}"
    if [ "$rest" != "$url" ]; then
        if [ -n "${SLO_DEPLOY_TOKEN_USERNAME:-}" ] && [ -n "${SLO_DEPLOY_TOKEN:-}" ]; then
            url="https://${SLO_DEPLOY_TOKEN_USERNAME}:${SLO_DEPLOY_TOKEN}@${rest}"
        fi
    fi
    local subdir="${SLO_REPO_SUBDIR-slo}"
    local frag=""
    [ -n "$subdir" ] && frag="#subdirectory=${subdir}"
    echo "git+${url}@${SLO_REF}${frag}"
}

slo_python_bin() {
    # Resolve the concrete pinned interpreter to hand pipx. The bare `python3.11`
    # pyenv shim only resolves when SLO_PYTHON_VERSION is the *active* pyenv
    # version — which it usually is NOT on a multi-version machine (the active
    # version is whatever virtualenv the shell is in), so the shim aborts with
    # "command not found". Prefer the real binary under pyenv's versions dir;
    # fall back to a PATH python only when that is unavailable.
    local root="" bin=""
    if slo_has pyenv; then
        root="$(pyenv root 2>/dev/null || true)"
    fi
    bin="${root:+$root/versions/${SLO_PYTHON_VERSION}/bin/python3.11}"
    if [ -n "$bin" ] && [ -x "$bin" ]; then
        echo "$bin"
    elif slo_has python3.11; then
        echo "python3.11"
    else
        echo "python3"
    fi
}

slo_install() {
    # Install slo as an isolated pipx application straight from the repo: no
    # clone, no editable checkout, nothing written to SLO_HOME. --force makes
    # re-runs upgrade in place.
    #
    # pipx auto-selects a uv backend whenever any `uv` is on PATH but never
    # checks that it runs; a stale pyenv `uv` shim (uv present only in some
    # virtualenvs) then aborts the install. Fall back to pip when uv is present
    # but not runnable, unless the caller already pinned a backend.
    if [ -z "${PIPX_DEFAULT_BACKEND:-}" ] && slo_has uv && ! uv --version >/dev/null 2>&1; then
        slo_log "uv on PATH is not runnable; forcing pipx pip backend"
        export PIPX_DEFAULT_BACKEND=pip
    fi
    local spec python_bin
    spec="$(slo_pipx_spec)"
    python_bin="$(slo_python_bin)"
    slo_log "installing slo via pipx (python: $python_bin) from ${SLO_REPO_URL}@${SLO_REF}"
    pipx install --force --python "$python_bin" "$spec"
}

slo_ensure_local_bin_on_path() {
    # Detect the user's shell and ensure ~/.local/bin is on PATH in the rc file.
    # Idempotent: skips silently if the export line is already present in the rc
    # file, or if ~/.local/bin is already on the current PATH env var.
    local export_line='export PATH="$HOME/.local/bin:$PATH"'
    local rc_file=""

    # Detect rc file from $SHELL
    case "${SHELL:-}" in
        */zsh)  rc_file="$HOME/.zshrc" ;;
        */bash)
            # Prefer .bash_profile on macOS (login shell), .bashrc on Linux
            if [ "$(slo_os_kind)" = "macos" ] && [ -f "$HOME/.bash_profile" ]; then
                rc_file="$HOME/.bash_profile"
            else
                rc_file="$HOME/.bashrc"
            fi
            ;;
        *)
            # Unknown shell — fall back to ~/.profile
            rc_file="$HOME/.profile"
            ;;
    esac

    # Check current PATH env var first (already active in this session)
    case ":${PATH}:" in
        *":$HOME/.local/bin:"*)
            slo_log "~/.local/bin is already on PATH — no rc update needed"
            return 0
            ;;
    esac

    # Check if the export line is already in the rc file (idempotency guard)
    if [ -f "$rc_file" ] && grep -qF '.local/bin' "$rc_file" 2>/dev/null; then
        slo_log "~/.local/bin already referenced in $rc_file — skipping"
        return 0
    fi

    # Append the export to the rc file
    {
        echo ""
        echo "# Added by slo installer"
        echo "$export_line"
    } >> "$rc_file"

    slo_log "Added ~/.local/bin to PATH in $rc_file"
    slo_log "Restart your shell or run: source $rc_file"
}

slo_print_path() {
    # Print the verify / upgrade / uninstall cheatsheet.
    cat >&2 <<EOF

slo installed via pipx.

PATH updated: restart your shell or run: source ~/.zshrc

Verify:    slo --help
Upgrade:   re-run this installer, or:
           pipx install --force "git+${SLO_REPO_URL}@${SLO_REF}"
Uninstall: pipx uninstall slo

Runtime data (sessions, logs, credentials) lives in \${SLO_HOME:-~/.slo} and is
left untouched by install, upgrade, and uninstall.
EOF
}

# ---- main dispatch ---------------------------------------------------------
main() {
    # `--development` is an explicit alias for tracking the main branch (the
    # default); pin a release instead with SLO_REF=<tag>.
    if [ "${1:-}" = "--development" ]; then
        SLO_REF="main"
    fi
    slo_ensure_brew
    slo_ensure_pipx
    slo_ensure_pyenv
    slo_ensure_python
    slo_ensure_git
    slo_ensure_home >/dev/null
    slo_install
    slo_ensure_local_bin_on_path
    slo_print_path
}

# Run main only when executed directly. When sourced (e.g. by the unit tests),
# `return` succeeds and main is skipped so individual functions can be tested.
if ! (return 0 2>/dev/null); then
    main "$@"
fi
