#!/usr/bin/env bash
POE_REF="${POE_REF:-v0.4.0}"  # pinned at publish time by deploy-install-worker.sh
# install.sh — one-line installer for poe.
#
#   curl -fsSL <url>/install.sh | bash
#
# Installs poe as a normal pipx application straight from its git repository.
# pipx builds the package in its own isolated venv and puts the `poe` binary on
# your PATH. NOTHING is written to POE_HOME (~/.poe) — that directory is poe's
# *runtime data* (sessions, logs, credentials), NOT a code checkout. The old
# installer cloned the repo into ~/.poe, which collides with that data and
# aborts ("destination path already exists") on any machine where poe 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 poe in
# place. No interactive prompts.
#
# Private repositories: authenticate the git fetch one of these ways —
#   1. GitLab deploy token — export POE_DEPLOY_TOKEN_USERNAME=<user> and
#      POE_DEPLOY_TOKEN=<token>; injected as <user>:<token>@ on an https URL,
#      for the fetch only (not written to disk by this script);
#   2. personal access token — export POE_INSTALL_TOKEN=<token>; injected as
#      oauth2:<token>@; or
#   3. configure ~/.netrc (machine <host> login <user> password <token>) or an
#      SSH key, and point POE_REPO_URL at the matching https/ssh URL.
#
# Configuration (all overridable from the environment):
#   POE_REPO_URL              git URL to install from (default below)
#   POE_REF                   branch / tag / commit to install (default: main)
#   POE_DEPLOY_TOKEN_USERNAME GitLab deploy-token username (with POE_DEPLOY_TOKEN)
#   POE_DEPLOY_TOKEN          GitLab deploy-token secret (with the username above)
#   POE_INSTALL_TOKEN         personal access token for a private https fetch
#   POE_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) --------------------------------
POE_REPO_URL="${POE_REPO_URL:-https://gitlab.com/core-27/poe.git}"
POE_REF="${POE_REF:-main}"
POE_PYTHON_VERSION="${POE_PYTHON_VERSION:-3.11.7}"

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

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

poe_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) --------------
poe_ensure_brew() {
    # Install Homebrew when absent, then put it on PATH for the rest of this run.
    if poe_has brew; then
        poe_log "brew present"
        return 0
    fi
    poe_log "installing Homebrew (non-interactive)"
    NONINTERACTIVE=1 /bin/bash -c \
        "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    poe_activate_brew
}

poe_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) ----------------------------------------------------
poe_ensure_pipx() {
    # Install pipx when absent. macOS uses brew; Linux uses pip --user (the
    # platform-standard paths). No-op when pipx already resolves.
    if poe_has pipx; then
        poe_log "pipx present"
        return 0
    fi
    local os
    os="$(poe_os_kind)"
    poe_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) ---------------------------------------------------
poe_ensure_pyenv() {
    # Install pyenv via brew when absent, then initialize it so subsequent
    # `pyenv install` / shim lookups work within this script.
    if poe_has pyenv; then
        poe_log "pyenv present"
    else
        poe_log "installing pyenv via brew"
        brew install pyenv
    fi
    poe_activate_pyenv
}

poe_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 poe_has pyenv; then
        eval "$(pyenv init - 2>/dev/null)" || true
    fi
}

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

# ---- 5. POE_HOME (runtime data dir — created, never used as a checkout) -----
poe_ensure_home() {
    # Honour an existing POE_HOME; otherwise default to ~/.poe. This is poe'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 "${POE_HOME:-}" ]; then
        POE_HOME="$HOME/.poe"
        poe_log "POE_HOME unset; defaulting to $POE_HOME"
    else
        poe_log "POE_HOME=$POE_HOME (from environment)"
    fi
    mkdir -p "$POE_HOME"
    export POE_HOME
    echo "$POE_HOME"
}

# ---- git (required for pip's git+ VCS fetch) -------------------------------
poe_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 poe_has git; then
        poe_log "git present"
        return 0
    fi
    local os
    os="$(poe_os_kind)"
    poe_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 ----------------------------
poe_pipx_spec() {
    # Echo the pip VCS requirement pipx installs from:
    #   git+<url>@<ref>#subdirectory=<subdir>
    # The Python project lives in the repo's `poe/` subdirectory, not the repo
    # root, so pip must be told where pyproject.toml is via #subdirectory. Set
    # POE_REPO_SUBDIR to override; set it to empty for a root-level project.
    # On an https URL, inject a fetch credential for this run. Precedence:
    #   1. a GitLab deploy token — POE_DEPLOY_TOKEN_USERNAME + POE_DEPLOY_TOKEN
    #      (deploy tokens authenticate as <username>:<token>, NOT oauth2:<token>);
    #   2. a personal access token — POE_INSTALL_TOKEN (injected as oauth2:<token>).
    # Otherwise the URL is left untouched so ambient git auth (netrc / ssh) is
    # used. 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="$POE_REPO_URL"
    local rest="${url#https://}"
    if [ "$rest" != "$url" ]; then
        if [ -n "${POE_DEPLOY_TOKEN_USERNAME:-}" ] && [ -n "${POE_DEPLOY_TOKEN:-}" ]; then
            url="https://${POE_DEPLOY_TOKEN_USERNAME}:${POE_DEPLOY_TOKEN}@${rest}"
        elif [ -n "${POE_INSTALL_TOKEN:-}" ]; then
            url="https://oauth2:${POE_INSTALL_TOKEN}@${rest}"
        fi
    fi
    local subdir="${POE_REPO_SUBDIR-poe}"
    local frag=""
    [ -n "$subdir" ] && frag="#subdirectory=${subdir}"
    echo "git+${url}@${POE_REF}${frag}"
}

poe_python_bin() {
    # Resolve the concrete pinned interpreter to hand pipx. The bare `python3.11`
    # pyenv shim only resolves when POE_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 poe_has pyenv; then
        root="$(pyenv root 2>/dev/null || true)"
    fi
    bin="${root:+$root/versions/${POE_PYTHON_VERSION}/bin/python3.11}"
    if [ -n "$bin" ] && [ -x "$bin" ]; then
        echo "$bin"
    elif poe_has python3.11; then
        echo "python3.11"
    else
        echo "python3"
    fi
}

poe_install() {
    # Install poe as an isolated pipx application straight from the repo: no
    # clone, no editable checkout, nothing written to POE_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:-}" ] && poe_has uv && ! uv --version >/dev/null 2>&1; then
        poe_log "uv on PATH is not runnable; forcing pipx pip backend"
        export PIPX_DEFAULT_BACKEND=pip
    fi
    local spec python_bin
    spec="$(poe_pipx_spec)"
    python_bin="$(poe_python_bin)"
    poe_log "installing poe via pipx (python: $python_bin) from ${POE_REPO_URL}@${POE_REF}"
    pipx install --force --python "$python_bin" "$spec"
}

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

poe installed via pipx.

If 'poe' is not found on your PATH, add pipx's bin dir and restart your shell:

   pipx ensurepath

Verify:    poe --help
Upgrade:   re-run this installer, or:
           pipx install --force "git+${POE_REPO_URL}@${POE_REF}"
Uninstall: pipx uninstall poe

Runtime data (sessions, logs, credentials) lives in \${POE_HOME:-~/.poe} 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 POE_REF=<tag>.
    if [ "${1:-}" = "--development" ]; then
        POE_REF="main"
    fi
    poe_ensure_brew
    poe_ensure_pipx
    poe_ensure_pyenv
    poe_ensure_python
    poe_ensure_git
    poe_ensure_home >/dev/null
    poe_install
    poe_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
