#!/bin/sh
# Sink CLI installer.
#
#   curl -fsSL https://usesink.co/install.sh | sh
#
# Installs a self-contained copy of the `sink` command under
# ~/.local/share/sink and links it into ~/.local/bin. It needs no system
# Python, no pip, and no sudo — a private, pinned copy of uv fetches its own
# interpreter.
#
# To remove everything:
#
#   curl -fsSL https://usesink.co/install.sh | sh -s -- --uninstall
#
# Environment overrides:
#   SINK_URL               base URL of the Sink server (baked in when served)
#   SINK_INSTALL_DIR       where to keep the private venv and uv
#   SINK_BIN_DIR           where to link the `sink` command
#   SINK_PYTHON_VERSION    interpreter uv should fetch (default 3.12)
#   SINK_UV_VERSION        pinned uv release
set -eu

# The server substitutes its own address here when it serves this file. The
# fallback keeps the script usable straight from a checkout.
SINK_BASE_URL="${SINK_URL:-https://usesink.co}"

INSTALL_DIR="${SINK_INSTALL_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/sink}"
BIN_DIR="${SINK_BIN_DIR:-$HOME/.local/bin}"
PYTHON_VERSION="${SINK_PYTHON_VERSION:-3.12}"
UV_VERSION="${SINK_UV_VERSION:-0.12.1}"

VENV_DIR="$INSTALL_DIR/venv"
UV_BIN="$INSTALL_DIR/bin/uv"

say()  { printf '%s\n' "$*"; }
info() { printf '  %s\n' "$*"; }
err()  { printf 'error: %s\n' "$*" >&2; exit 1; }

need_cmd() {
    command -v "$1" >/dev/null 2>&1 || err "this installer needs '$1' on PATH"
}

curl_protos() {
    case "$1" in
        https://*) printf '=https' ;;
        *)         printf '=http,https' ;;
    esac
}

download() {
    if command -v curl >/dev/null 2>&1; then
        protos=$(curl_protos "$1")
        curl -fsSL --proto "$protos" --proto-redir "$protos" --tlsv1.2 -o "$2" "$1" \
            || err "could not download $1"
    elif command -v wget >/dev/null 2>&1; then
        wget -qO "$2" "$1" || err "could not download $1"
    else
        err "this installer needs curl or wget"
    fi
}

download_stdout() {
    if command -v curl >/dev/null 2>&1; then
        protos=$(curl_protos "$1")
        curl -fsSL --proto "$protos" --proto-redir "$protos" --tlsv1.2 "$1" \
            || err "could not fetch $1"
    else
        wget -qO- "$1" || err "could not fetch $1"
    fi
}

sha256_of() {
    if command -v sha256sum >/dev/null 2>&1; then
        sha256sum "$1" | cut -d' ' -f1
    elif command -v shasum >/dev/null 2>&1; then
        shasum -a 256 "$1" | cut -d' ' -f1
    else
        err "this installer needs sha256sum or shasum to verify downloads"
    fi
}

verify_sha256() {
    actual=$(sha256_of "$1")
    if [ "$actual" != "$2" ]; then
        err "checksum mismatch for $3
  expected $2
  actual   $actual
Refusing to install."
    fi
}

# Reads one string field out of a flat JSON object.
json_field() {
    sed -n 's/.*"'"$1"'"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1
}

detect_uv_target() {
    os=$(uname -s)
    arch=$(uname -m)
    case "$os" in
        Darwin)
            case "$arch" in
                arm64|aarch64) printf 'aarch64-apple-darwin' ;;
                x86_64)        printf 'x86_64-apple-darwin' ;;
                *) err "unsupported macOS architecture: $arch" ;;
            esac
            ;;
        Linux)
            libc=gnu
            # musl systems have no glibc-linked ldd output to speak of.
            if command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then
                libc=musl
            fi
            case "$arch" in
                x86_64)        printf 'x86_64-unknown-linux-%s' "$libc" ;;
                aarch64|arm64) printf 'aarch64-unknown-linux-%s' "$libc" ;;
                *) err "unsupported Linux architecture: $arch" ;;
            esac
            ;;
        *)
            err "unsupported operating system: $os (install manually with pipx)"
            ;;
    esac
}

ensure_uv() {
    if [ -x "$UV_BIN" ]; then
        info "using previously downloaded uv"
        return
    fi
    if command -v uv >/dev/null 2>&1; then
        UV_BIN=$(command -v uv)
        info "using uv already on PATH ($UV_BIN)"
        return
    fi

    target=$(detect_uv_target)
    archive="uv-$target.tar.gz"
    base="https://github.com/astral-sh/uv/releases/download/$UV_VERSION"

    info "downloading uv $UV_VERSION for $target"
    mkdir -p "$INSTALL_DIR/bin"
    tmp=$(mktemp -d)
    trap "rm -rf '$tmp'" EXIT

    download "$base/$archive" "$tmp/$archive"

    expected=$(download_stdout "$base/$archive.sha256" | cut -d' ' -f1)
    [ -n "$expected" ] || err "could not fetch the uv checksum"
    verify_sha256 "$tmp/$archive" "$expected" "uv $UV_VERSION"

    tar -xzf "$tmp/$archive" -C "$tmp"
    found=$(find "$tmp" -type f -name uv -perm -u+x | head -n 1)
    [ -n "$found" ] || err "uv binary not found in $archive"
    mv "$found" "$UV_BIN"
    chmod +x "$UV_BIN"

    rm -rf "$tmp"
    trap - EXIT
}

install_cli() {
    # Note: this must not mention the placeholder token itself — the server
    # substitutes every occurrence when it serves the file.
    case "$SINK_BASE_URL" in
        https://*) ;;
        http://localhost*|http://127.0.0.1*) ;;
        http://*)
            say "warning: $SINK_BASE_URL is plain HTTP; the download is not encrypted" ;;
        *)
            err "no server address. Set SINK_URL, e.g.
  curl -fsSL https://sink.example.com/install.sh | SINK_URL=https://sink.example.com sh" ;;
    esac

    say "Installing the Sink CLI from $SINK_BASE_URL"

    info "reading release manifest"
    manifest=$(download_stdout "$SINK_BASE_URL/cli/manifest.json")
    version=$(printf '%s' "$manifest" | json_field version)
    wheel=$(printf '%s' "$manifest" | json_field wheel)
    sha256=$(printf '%s' "$manifest" | json_field sha256)

    [ -n "$version" ] && [ -n "$wheel" ] && [ -n "$sha256" ] \
        || err "the server did not return a usable manifest. Has scripts/build-cli.sh been run?"

    info "sink-cli $version"

    ensure_uv

    tmp=$(mktemp -d)
    # shellcheck disable=SC2064
    trap "rm -rf '$tmp'" EXIT

    info "downloading $wheel"
    download "$SINK_BASE_URL/cli/$wheel" "$tmp/$wheel"
    verify_sha256 "$tmp/$wheel" "$sha256" "$wheel"
    info "checksum verified"

    info "creating an isolated environment (python $PYTHON_VERSION)"
    rm -rf "$VENV_DIR"
    "$UV_BIN" venv --python "$PYTHON_VERSION" "$VENV_DIR" >/dev/null 2>&1 \
        || err "uv could not create the environment"

    info "installing"
    VIRTUAL_ENV="$VENV_DIR" "$UV_BIN" pip install --quiet "$tmp/$wheel" \
        || err "installation failed"

    rm -rf "$tmp"
    trap - EXIT

    mkdir -p "$BIN_DIR"
    ln -sf "$VENV_DIR/bin/sink" "$BIN_DIR/sink"

    say ""
    say "Installed sink $version"
    info "command:  $BIN_DIR/sink"
    info "files:    $INSTALL_DIR"

    if ! printf '%s' ":$PATH:" | grep -q ":$BIN_DIR:"; then
        say ""
        say "$BIN_DIR is not on your PATH. Add it:"
        say ""
        say "    export PATH=\"$BIN_DIR:\$PATH\""
        say ""
    else
        say ""
        say "Next:  sink login"
    fi
}

uninstall_cli() {
    removed=0
    if [ -L "$BIN_DIR/sink" ] || [ -f "$BIN_DIR/sink" ]; then
        rm -f "$BIN_DIR/sink"
        info "removed $BIN_DIR/sink"
        removed=1
    fi
    if [ -d "$INSTALL_DIR" ]; then
        rm -rf "$INSTALL_DIR"
        info "removed $INSTALL_DIR"
        removed=1
    fi
    [ "$removed" -eq 1 ] || info "nothing to remove"
    say ""
    say "The Sink CLI has been removed."
    say "Your stored credential and config were left alone:"
    say "  ${XDG_CONFIG_HOME:-$HOME/.config}/sink"
    say "Run 'sink logout' before uninstalling if you want the key forgotten."
}

need_cmd uname
need_cmd tar

case "${1:-}" in
    --uninstall) uninstall_cli ;;
    "")          install_cli ;;
    *)           err "unknown option: $1 (expected --uninstall)" ;;
esac
