#!/bin/sh
#
# Uniicy CLI installer (TASK-035 / ADR 0058).
#
# Usage:
#   curl -fsSL https://get.uniicy.cloud/install.sh | sh
#   curl -fsSL https://get.uniicy.cloud/install.sh | sh -s -- --version v0.1.0
#   curl -fsSL https://get.uniicy.cloud/install.sh | sh -s -- --bin-dir ~/.local/bin
#
# The script downloads the latest (or specified) `uc` release
# from the Uniicy public mirror at https://get.uniicy.cloud,
# verifies the keyless cosign signature over the SHA256SUMS
# sidecar, verifies the archive SHA256 against that signed
# manifest, and installs the binary into a writable directory
# on the PATH.
#
# The mirror is a Hetzner Object Storage bucket
# (`uniicy-releases-prod`, hel1 region) populated by the
# release workflow on every `v*` tag. The GitHub release at
# github.com/Uniicy/uniicy-cloud/releases is the canonical
# source of truth; the mirror is a public-read copy so alpha
# users don't need repo access.
#
# Supported platforms: linux-amd64, linux-arm64, darwin-amd64,
# darwin-arm64. Windows users install manually (the .zip
# archive from the mirror).
#
# Dependencies: curl, tar, sha256sum or shasum. cosign is downloaded
# automatically when missing (pinned release from sigstore/cosign).
# No bash or python required.

set -eu

UNIICY_VERSION="${UNIICY_VERSION:-latest}"
UNIICY_BIN_DIR="${UNIICY_BIN_DIR:-}"
UNIICY_INSTALL_BASE_URL="${UNIICY_INSTALL_BASE_URL:-https://get.uniicy.cloud}"
UNIICY_COSIGN_VERSION="${UNIICY_COSIGN_VERSION:-v2.4.2}"
UNIICY_COSIGN_DOWNLOAD_BASE="${UNIICY_COSIGN_DOWNLOAD_BASE:-https://github.com/sigstore/cosign/releases/download}"
COSIGN_OIDC_ISSUER="https://token.actions.githubusercontent.com"
COSIGN_IDENTITY_REGEXP='^https://github.com/Uniicy/uniicy-cloud/.github/workflows/release.yml@refs/tags/v.+$'

usage() {
    cat <<USAGE
Install the Uniicy CLI.

Flags:
  --version <tag>     install a specific release (default: latest)
  --bin-dir <dir>     install location (default: /usr/local/bin or ~/.local/bin)
  --base-url <url>    override the public mirror (used by tests)
  --help              show this help

Environment overrides:
  UNIICY_VERSION              same as --version
  UNIICY_BIN_DIR              same as --bin-dir
  UNIICY_INSTALL_BASE_URL     same as --base-url
  UNIICY_COSIGN_VERSION       cosign release to bootstrap (default: v2.4.2)
  UNIICY_COSIGN_DOWNLOAD_BASE override cosign download host (used by tests)
USAGE
}

while [ $# -gt 0 ]; do
    case "$1" in
        --version)  UNIICY_VERSION="$2"; shift 2 ;;
        --bin-dir)  UNIICY_BIN_DIR="$2"; shift 2 ;;
        --base-url) UNIICY_INSTALL_BASE_URL="$2"; shift 2 ;;
        --help|-h)  usage; exit 0 ;;
        *) echo "uc-installer: unknown flag '$1'" >&2; usage >&2; exit 2 ;;
    esac
done

log()  { printf '==> %s\n' "$*"; }
warn() { printf 'uc-installer: %s\n' "$*" >&2; }
die()  { warn "$@"; exit 1; }

require_cmd() {
    if ! command -v "$1" >/dev/null 2>&1; then
        die "missing required command: $1"
    fi
}

require_cmd curl
require_cmd tar
require_cmd uname

# Pick a SHA256 implementation. sha256sum on Linux,
# `shasum -a 256` on macOS.
sha256_cmd() {
    if command -v sha256sum >/dev/null 2>&1; then
        sha256sum "$1" | awk '{print $1}'
    elif command -v shasum >/dev/null 2>&1; then
        shasum -a 256 "$1" | awk '{print $1}'
    else
        die "no SHA256 tool found (need sha256sum or shasum)"
    fi
}

detect_os() {
    case "$(uname -s)" in
        Linux*)  echo linux ;;
        Darwin*) echo darwin ;;
        *) die "unsupported OS: $(uname -s)" ;;
    esac
}

detect_arch() {
    case "$(uname -m)" in
        x86_64|amd64)  echo amd64 ;;
        arm64|aarch64) echo arm64 ;;
        *) die "unsupported architecture: $(uname -m)" ;;
    esac
}

OS="$(detect_os)"
ARCH="$(detect_arch)"

tmp_dir="$(mktemp -d -t uc-install.XXXXXX)"
# shellcheck disable=SC2064
trap "rm -rf '$tmp_dir'" EXIT

ensure_cosign() {
    if command -v cosign >/dev/null 2>&1; then
        COSIGN=cosign
        return 0
    fi

    cosign_name="cosign-${OS}-${ARCH}"
    cosign_url="${UNIICY_COSIGN_DOWNLOAD_BASE}/${UNIICY_COSIGN_VERSION}/${cosign_name}"
    log "cosign not found; downloading ${cosign_name} (${UNIICY_COSIGN_VERSION})…"
    if ! curl -fsSL "$cosign_url" -o "$tmp_dir/cosign"; then
        die "failed to download cosign from $cosign_url"
    fi
    chmod +x "$tmp_dir/cosign"
    COSIGN="$tmp_dir/cosign"
}

ensure_cosign

# Pick a default install directory: prefer ~/.local/bin so the
# install does not need root; fall back to /usr/local/bin when
# the caller asked.
if [ -z "$UNIICY_BIN_DIR" ]; then
    if [ -w "/usr/local/bin" ] 2>/dev/null; then
        UNIICY_BIN_DIR="/usr/local/bin"
    else
        UNIICY_BIN_DIR="$HOME/.local/bin"
    fi
fi
mkdir -p "$UNIICY_BIN_DIR"

# Resolve the version. `latest` reads `version.json` from the
# mirror — a 1-key JSON document maintained by the release
# workflow; an explicit tag is taken at face value.
if [ "$UNIICY_VERSION" = "latest" ]; then
    log "Resolving latest Uniicy CLI release"
    version_url="${UNIICY_INSTALL_BASE_URL}/version.json"
    tag="$(curl -fsSL "$version_url" \
        | sed -n 's/.*"latest":[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)"
    if [ -z "$tag" ]; then
        die "could not determine latest release tag (check $version_url)"
    fi
    UNIICY_VERSION="$tag"
fi

# Asset names mirror the Makefile output: leading `v` is stripped
# so `uc_0.1.0_linux_amd64.tar.gz` is what we look for under
# /v0.1.0/. The /latest/ tree renames the version slug to `latest`
# but we always download from the canonical versioned prefix so
# the SHA256SUMS we verify matches what the GitHub release lists.
asset_version="${UNIICY_VERSION#v}"
asset_name="uc_${asset_version}_${OS}_${ARCH}.tar.gz"

log "Platform:  $OS/$ARCH"
log "Version:   $UNIICY_VERSION"
log "Archive:   $asset_name"
log "Install:   $UNIICY_BIN_DIR/uc"

download_base="${UNIICY_INSTALL_BASE_URL}/${UNIICY_VERSION}"

download_required_asset() {
    asset="$1"
    if ! curl -fsSL "$download_base/$asset" -o "$tmp_dir/$asset"; then
        die "unsigned_release: missing required release asset $asset at $download_base/"
    fi
}

log "Downloading archive…"
curl -fsSL "$download_base/$asset_name" -o "$tmp_dir/$asset_name"

log "Downloading SHA256SUMS…"
download_required_asset "SHA256SUMS"
download_required_asset "SHA256SUMS.sig"
download_required_asset "SHA256SUMS.pem"
download_required_asset "SHA256SUMS.bundle"

log "Verifying signed checksum manifest…"
if ! "$COSIGN" verify-blob "$tmp_dir/SHA256SUMS" \
    --signature "$tmp_dir/SHA256SUMS.sig" \
    --certificate "$tmp_dir/SHA256SUMS.pem" \
    --bundle "$tmp_dir/SHA256SUMS.bundle" \
    --certificate-oidc-issuer "$COSIGN_OIDC_ISSUER" \
    --certificate-identity-regexp "$COSIGN_IDENTITY_REGEXP"; then
    die "signature verification failed for SHA256SUMS"
fi
log "Signature verified"

want="$(grep " $asset_name\$" "$tmp_dir/SHA256SUMS" \
    | awk '{print $1}' | head -n1)"
if [ -z "$want" ]; then
    die "checksum for $asset_name not found in SHA256SUMS"
fi
got="$(sha256_cmd "$tmp_dir/$asset_name")"
if [ "$want" != "$got" ]; then
    die "checksum mismatch for $asset_name (want $want, got $got)"
fi
log "Checksum verified ($got)"

log "Extracting…"
tar -xzf "$tmp_dir/$asset_name" -C "$tmp_dir"

# The archive carries `uc` at the top level (root or inside a
# directory). Try both shapes.
src_bin=""
if [ -f "$tmp_dir/uc" ]; then
    src_bin="$tmp_dir/uc"
elif [ -f "$tmp_dir/uc_${asset_version}_${OS}_${ARCH}/uc" ]; then
    src_bin="$tmp_dir/uc_${asset_version}_${OS}_${ARCH}/uc"
fi
if [ -z "$src_bin" ]; then
    die "extracted archive did not contain a 'uc' binary"
fi

log "Installing to $UNIICY_BIN_DIR/uc"
install -m 0755 "$src_bin" "$UNIICY_BIN_DIR/uc"

# A best-effort PATH hint so a fresh user does not wonder why
# the binary is "missing".
case ":$PATH:" in
    *":$UNIICY_BIN_DIR:"*) ;;
    *)
        warn "$UNIICY_BIN_DIR is not on \$PATH; add it to use 'uc'."
        warn "  export PATH=\"$UNIICY_BIN_DIR:\$PATH\""
        ;;
esac

log "Done. Try 'uc version'."
