#!/usr/bin/env bash
# Public acli installer. Keep this Bash 3.2 compatible for stock macOS.

set -euo pipefail

readonly DEFAULT_RELEASE_BASE_URL="https://www.loxal.net/acli/releases"
readonly INSTALLER_NAME="acli installer"
# Emergency release kill switch. A site deployment changes it atomically with
# the versioned files below DEFAULT_RELEASE_BASE_URL.
readonly RELEASE_ENABLED=1

ACLI_STAGING_DIR=""
ACLI_STAGED_DESTINATION=""

cleanup() {
    [ -z "$ACLI_STAGED_DESTINATION" ] || rm -f "$ACLI_STAGED_DESTINATION"
    [ -z "$ACLI_STAGING_DIR" ] || rm -rf "$ACLI_STAGING_DIR"
}

say() {
    printf '%s\n' "$*"
}

fail() {
    printf '%s: %s\n' "$INSTALLER_NAME" "$*" >&2
    exit 1
}

download() {
    local url="$1"
    local destination="$2"
    local protocols="=https"

    case "$url" in
        https://*) ;;
        file://*)
            [ "${ACLI_INSTALLER_TESTING:-}" = "1" ] \
                || fail "refusing non-HTTPS download URL"
            protocols="=https,file"
            ;;
        *) fail "refusing non-HTTPS download URL" ;;
    esac

    # --disable must be the first curl option so a user curlrc cannot inject
    # headers, credentials, output paths, or weaker transport settings.
    curl --disable --fail --silent --show-error --location \
        --proto "$protocols" --proto-redir "$protocols" --tlsv1.2 \
        --output "$destination" "$url"
}

detect_target() {
    local os="${1:-$(uname -s)}"
    local arch="${2:-$(uname -m)}"

    case "$os:$arch" in
        Darwin:arm64|Darwin:aarch64) printf '%s\n' "aarch64-apple-darwin" ;;
        Darwin:x86_64|Darwin:amd64) printf '%s\n' "x86_64-apple-darwin" ;;
        Linux:x86_64|Linux:amd64)
            if [ -e /etc/alpine-release ] \
                || (command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl); then
                fail "Alpine/musl is not supported by this release; use a glibc-based distribution"
            fi
            printf '%s\n' "x86_64-unknown-linux-gnu"
            ;;
        Linux:aarch64|Linux:arm64)
            fail "Linux ARM64 is not available yet"
            ;;
        *) fail "unsupported platform: $os $arch" ;;
    esac
}

validate_version() {
    [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
}

sha256_file() {
    local file="$1"
    if command -v sha256sum >/dev/null 2>&1; then
        sha256sum "$file" | awk '{print $1}'
    elif command -v shasum >/dev/null 2>&1; then
        shasum -a 256 "$file" | awk '{print $1}'
    elif command -v openssl >/dev/null 2>&1; then
        openssl dgst -sha256 "$file" | awk '{print $NF}'
    else
        fail "no SHA-256 tool found (tried sha256sum, shasum, and openssl)"
    fi
}

expected_sha256() {
    local sums_file="$1"
    local asset="$2"
    awk -v asset="$asset" '
        NF == 2 {
            name = $2
            sub(/^\*/, "", name)
            if (name == asset) {
                digest = $1
                matches += 1
            }
        }
        END {
            if (matches != 1) exit 1
            print digest
        }
    ' "$sums_file"
}

verify_digest() {
    local file="$1"
    local expected="$2"
    local actual

    case "$expected" in
        ''|*[!0-9a-fA-F]*) fail "release checksum is malformed" ;;
    esac
    [ "${#expected}" -eq 64 ] || fail "release checksum is malformed"

    actual="$(sha256_file "$file")"
    expected="$(printf '%s' "$expected" | tr '[:upper:]' '[:lower:]')"
    actual="$(printf '%s' "$actual" | tr '[:upper:]' '[:lower:]')"
    [ "$actual" = "$expected" ] || fail "checksum mismatch; refusing to install"
}

main() {
    if [ "$RELEASE_ENABLED" != "1" ] && [ "${ACLI_INSTALLER_TESTING:-}" != "1" ]; then
        fail "downloads are not available yet; see https://www.loxal.net/acli.html"
    fi
    command -v curl >/dev/null 2>&1 || fail "curl is required"

    local release_base_url="$DEFAULT_RELEASE_BASE_URL"
    local version="${ACLI_VERSION:-}"
    local target asset install_dir destination staging_dir staging_binary sums_file expected

    if [ -n "${ACLI_RELEASE_BASE_URL:-}" ]; then
        [ "${ACLI_INSTALLER_TESTING:-}" = "1" ] \
            || fail "ACLI_RELEASE_BASE_URL is available only to installer tests"
        release_base_url="$ACLI_RELEASE_BASE_URL"
    fi
    release_base_url="${release_base_url%/}"
    staging_dir="$(mktemp -d "${TMPDIR:-/tmp}/acli-install.XXXXXX")" \
        || fail "could not create a temporary directory"
    ACLI_STAGING_DIR="$staging_dir"
    trap cleanup EXIT
    trap 'exit 129' HUP
    trap 'exit 130' INT
    trap 'exit 143' TERM

    if [ -z "$version" ]; then
        download "$release_base_url/latest/download/version.txt" "$staging_dir/version.txt"
        version="$(tr -d '[:space:]' < "$staging_dir/version.txt")"
    fi
    validate_version "$version" || fail "invalid release version: $version"

    target="$(detect_target)"
    asset="acli-$target"
    staging_binary="$staging_dir/$asset"
    sums_file="$staging_dir/SHA256SUMS"

    download "$release_base_url/download/v$version/$asset" "$staging_binary"
    download "$release_base_url/download/v$version/SHA256SUMS" "$sums_file"
    if ! expected="$(expected_sha256 "$sums_file" "$asset")"; then
        fail "release checksums contain no unique entry for $asset"
    fi
    verify_digest "$staging_binary" "$expected"
    chmod 0755 "$staging_binary"

    "$staging_binary" --version 2>/dev/null | grep -Fqx "acli $version" \
        || fail "downloaded binary did not report the expected version"

    if [ -n "${ACLI_INSTALL_DIR:-}" ]; then
        install_dir="$ACLI_INSTALL_DIR"
    else
        [ -n "${HOME:-}" ] || fail "HOME is not set; set ACLI_INSTALL_DIR explicitly"
        install_dir="$HOME/.local/bin"
    fi
    if [ ! -d "$install_dir" ]; then
        mkdir -p "$install_dir"
        chmod 0755 "$install_dir"
    fi
    [ -w "$install_dir" ] || fail "install directory is not writable: $install_dir"

    destination="$install_dir/acli"
    # The temporary binary and destination are on the same filesystem only
    # after this copy. The final rename is atomic and never leaves a partial CLI.
    local staged_destination
    staged_destination="$(mktemp "$install_dir/.acli.XXXXXX")" \
        || fail "could not stage the binary in $install_dir"
    ACLI_STAGED_DESTINATION="$staged_destination"
    cp "$staging_binary" "$staged_destination"
    chmod 0755 "$staged_destination"
    mv -f "$staged_destination" "$destination"
    ACLI_STAGED_DESTINATION=""

    say "Installed acli $version to $destination"
    case ":${PATH:-}:" in
        *":$install_dir:"*) ;;
        *)
            say "Add it to Fish PATH with:"
            if [ "$install_dir" = "${HOME:-}/.local/bin" ]; then
                say "  fish_add_path ~/.local/bin"
            else
                say "  fish_add_path '$install_dir'"
            fi
            ;;
    esac
}

if [ "${ACLI_INSTALLER_NO_RUN:-}" != "1" ]; then
    main "$@"
fi
