#!/bin/sh
# Nool CLI Unified Installer
# Usage: curl -fsSL https://www.nool.dev/nool-install.sh | bash
#
# Re-running is safe and cheap: an install that is already current exits before
# downloading anything. This script doubles as the upgrade path (`nool upgrade`
# shells out to it), so it has to be idempotent rather than assume a clean box.
#
# Options (pass with `| bash -s -- <opt>`, or set the env var):
#   --force   reinstall even if the same or a newer version is present
#             (env: NOOL_INSTALL_FORCE=1)

set -e

VERSION="7.24.0"
BASE_URL="https://www.nool.dev/artifacts"

# This endpoint serves the Community edition only. Team and Enterprise are the
# same command surface plus the paid features, and are delivered through your
# account (`nool upgrade --edition team|enterprise`) so the lease travels with
# the download; a Team archive left on a public URL would be inert anyway.
# Releases from 7.3.0 on carry the edition in the archive name.
EDITION="community"

FORCE="${NOOL_INSTALL_FORCE:-0}"
for arg in "$@"; do
    case "$arg" in
        (--force|-f) FORCE=1 ;;
    esac
done

# The installed version, or empty if nool is absent or too old to report one.
# Parsed by pattern rather than field position so a changed prefix ("nool 6.7.0"
# vs "nool-cli 6.7.0") does not silently yield a wrong answer.
installed_version() {
    command -v nool >/dev/null 2>&1 || return 0
    nool --version 2>/dev/null | tr -cs '0-9.' '\n' \
        | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n1
}

# Print the newer of two semvers. Falls back to declaring them different (by
# echoing neither) when `sort -V` is unavailable, so an unknown ordering leads
# to a reinstall rather than a wrong skip.
newer_of() {
    printf '%s\n%s\n' "$1" "$2" | sort -V 2>/dev/null | tail -n1
}

CURRENT="$(installed_version || true)"

if [ -n "$CURRENT" ] && [ "$FORCE" != "1" ]; then
    if [ "$CURRENT" = "$VERSION" ]; then
        printf "==> %s\n" "Nool $CURRENT is already installed and up to date."
        printf "    %s\n" "Nothing to do. Reinstall anyway with: --force"
        exit 0
    fi

    NEWEST="$(newer_of "$CURRENT" "$VERSION")"
    if [ "$NEWEST" = "$CURRENT" ]; then
        # Downgrading silently would be worse than doing nothing: someone
        # running a newer build (a local release, a pre-release) did not ask
        # for it to be replaced by whatever this endpoint currently serves.
        printf "==> %s\n" "Installed Nool $CURRENT is newer than $VERSION offered here."
        printf "    %s\n" "Keeping it. Force a downgrade with: --force"
        exit 0
    fi

    printf "==> %s\n" "Nool $CURRENT is installed; updating to $VERSION..."
elif [ -n "$CURRENT" ]; then
    printf "==> %s\n" "Nool $CURRENT is installed; reinstalling $VERSION (--force)..."
fi

# Detect OS
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"

# Linux ships both glibc and musl builds for x86_64; pick the one this system can run.
detect_linux_libc() {
    for loader in /lib/ld-musl-*.so.1; do
        [ -e "$loader" ] && { printf 'musl\n'; return 0; }
    done
    if command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then
        printf 'musl\n'
        return 0
    fi
    printf 'gnu\n'
}

case "$OS" in
    (darwin)
        case "$ARCH" in
            (arm64|aarch64)
                FILE="nool-${VERSION}-${EDITION}-aarch64-apple-darwin.tar.gz"
                ;;
            (x86_64)
                # No Intel macOS build is published as of $VERSION. If this is an
                # Apple Silicon Mac running a Rosetta-translated shell, a native
                # arm64 terminal will report arm64 instead and hit the case above.
                printf "error: %s\n" "Intel macOS (x86_64) is not supported as of Nool $VERSION." >&2
                printf "       %s\n" "If this is an Apple Silicon Mac, relaunch your terminal natively (not under Rosetta) and retry." >&2
                exit 1
                ;;
            (*)
                printf "error: %s\n" "Unsupported macOS architecture: $ARCH" >&2
                exit 1
                ;;
        esac
        ;;
    (linux)
        case "$ARCH" in
            (x86_64)
                FILE="nool-${VERSION}-${EDITION}-x86_64-unknown-linux-$(detect_linux_libc).tar.gz"
                ;;
            (arm64|aarch64)
                FILE="nool-${VERSION}-${EDITION}-aarch64-unknown-linux-gnu.tar.gz"
                ;;
            (*)
                printf "error: %s\n" "Unsupported Linux architecture: $ARCH" >&2
                exit 1
                ;;
        esac
        ;;
    (*)
        printf "error: %s\n" "Unsupported OS: $OS. This installer supports macOS and Linux." >&2
        exit 1
        ;;
esac

INSTALL_DIR="$HOME/.local/bin"
mkdir -p "$INSTALL_DIR"
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/nool-install.XXXXXX")"

trap 'rm -rf "$TMP_DIR"' EXIT

printf "==> %s\n" "Downloading Nool CLI ($FILE)..."
ARCHIVE_PATH="$TMP_DIR/$FILE"
CHECKSUM_PATH="$ARCHIVE_PATH.sha256"
curl -fL "$BASE_URL/$FILE" -o "$ARCHIVE_PATH"
curl -fL "$BASE_URL/$FILE.sha256" -o "$CHECKSUM_PATH"

printf "==> %s\n" "Verifying archive..."
EXPECTED_SUM="$(awk '{print $1}' < "$CHECKSUM_PATH")"

if command -v shasum >/dev/null 2>&1; then
    ACTUAL_SUM="$(shasum -a 256 "$ARCHIVE_PATH" | awk '{print $1}')"
elif command -v sha256sum >/dev/null 2>&1; then
    ACTUAL_SUM="$(sha256sum "$ARCHIVE_PATH" | awk '{print $1}')"
else
    printf "error: %s\n" "Missing required tool: shasum or sha256sum" >&2
    exit 1
fi

if [ "$EXPECTED_SUM" != "$ACTUAL_SUM" ]; then
    printf "error: %s\n" "Checksum verification failed for $FILE" >&2
    exit 1
fi

# Signed-manifest verification (defence in depth beyond the per-file checksum).
# Each release publishes a run-wide SHA256SUMS and its minisign signature; we
# verify the archive against that manifest and the manifest against the key
# pinned here. minisign is optional: where it is absent the checksum above still
# stands, so a zero-dependency install keeps working. Set NOOL_REQUIRE_SIGNATURE=1
# to refuse installing unless the signature is checked.
NOOL_MINISIGN_PUBKEY="RWT0vGBCEIz27HfLSal/dVFhklQJmgDGIIA9mq9O7MhtUdSiawHtyn9J"
REQUIRE_SIG="${NOOL_REQUIRE_SIGNATURE:-0}"
if command -v minisign >/dev/null 2>&1; then
    if curl -fsL "$BASE_URL/nool-$VERSION-SHA256SUMS" -o "$TMP_DIR/SHA256SUMS" \
       && curl -fsL "$BASE_URL/nool-$VERSION-SHA256SUMS.minisig" -o "$TMP_DIR/SHA256SUMS.minisig"; then
        if minisign -V -P "$NOOL_MINISIGN_PUBKEY" \
             -m "$TMP_DIR/SHA256SUMS" -x "$TMP_DIR/SHA256SUMS.minisig" >/dev/null 2>&1; then
            MANIFEST_SUM="$(awk -v n="$FILE" '$2 == n || $2 == "*"n {print $1}' "$TMP_DIR/SHA256SUMS")"
            if [ -n "$MANIFEST_SUM" ] && [ "$MANIFEST_SUM" != "$ACTUAL_SUM" ]; then
                printf "error: %s\n" "$FILE does not match the signed SHA256SUMS manifest" >&2
                exit 1
            fi
            printf "==> %s\n" "Signature verified (minisign)."
        else
            printf "error: %s\n" "SHA256SUMS signature failed to verify against the pinned Nool key" >&2
            exit 1
        fi
    elif [ "$REQUIRE_SIG" = "1" ]; then
        printf "error: %s\n" "NOOL_REQUIRE_SIGNATURE=1 but the signed manifest could not be fetched" >&2
        exit 1
    else
        printf "==> %s\n" "Note: signed manifest unavailable; relied on per-file checksum."
    fi
elif [ "$REQUIRE_SIG" = "1" ]; then
    printf "error: %s\n" "NOOL_REQUIRE_SIGNATURE=1 but minisign is not installed" >&2
    exit 1
else
    printf "==> %s\n" "Note: minisign not installed; skipped signature check (checksum verified)."
fi

printf "==> %s\n" "Installing..."
tar -xzmf "$ARCHIVE_PATH" -C "$TMP_DIR"

if [ ! -f "$TMP_DIR/nool" ]; then
    printf "error: %s\n" "Archive did not contain a 'nool' binary." >&2
    exit 1
fi

for bin in nool nool-daemon nool-mcp git-remote-nool nool-harness-claude nool-harness-gemini nool-harness-ollama nool-harness-openai; do
    if [ -f "$TMP_DIR/$bin" ]; then
        chmod +x "$TMP_DIR/$bin"
        mv "$TMP_DIR/$bin" "$INSTALL_DIR/$bin"
        printf "  ✓ Installed %s\n" "$bin"
    fi
done

# Update any other `nool` copy on PATH (an old /usr/local/bin or ~/.cargo/bin
# install), replacing only the binaries that directory already holds, so a
# stale binary earlier on PATH cannot keep shadowing the one just installed.
# No sudo: a directory we cannot write gets the exact command instead.
INSTALL_REAL="$(cd "$INSTALL_DIR" && pwd -P)"
SEEN=":$INSTALL_REAL:"
OLD_IFS="$IFS"; IFS=':'
for dir in $PATH; do
    IFS="$OLD_IFS"
    [ -n "$dir" ] && [ -f "$dir/nool" ] || continue
    real="$(cd "$dir" 2>/dev/null && pwd -P)" || continue
    case "$SEEN" in *":$real:"*) continue ;; esac
    SEEN="$SEEN$real:"
    printf "==> %s\n" "Updating other copy at $real"
    for bin in nool nool-daemon nool-mcp git-remote-nool nool-harness-claude nool-harness-gemini nool-harness-ollama nool-harness-openai; do
        [ -f "$real/$bin" ] && [ -f "$INSTALL_DIR/$bin" ] || continue
        if cp "$INSTALL_DIR/$bin" "$real/$bin" 2>/dev/null; then
            printf "  ✓ Updated %s\n" "$real/$bin"
        else
            printf "  ! cannot write %s, run: sudo cp \"%s\" \"%s\"\n" "$real/$bin" "$INSTALL_DIR/$bin" "$real/$bin"
        fi
    done
done
IFS="$OLD_IFS"

printf "%s\n" "Nool CLI installed successfully!"
printf "==> %s\n" "Ensure $INSTALL_DIR is in your PATH."
