#!/bin/sh
# Sefaly CLI installer.
#
# Usage:
#   curl -fsSL https://www.sefaly.com/install.sh | sh
#
# What it does:
#   1. Detects your OS (Linux, macOS) and arch (amd64, arm64).
#   2. Pulls the latest release tag from the GitHub API.
#   3. Downloads the matching tarball + sha256sums.txt + .sig + .pem.
#   4. If `cosign` is on your PATH, verifies the cosign keyless
#      signature on sha256sums.txt against the GitHub Actions OIDC
#      identity of this repo's release workflow. If verification
#      fails, the install aborts. If cosign isn't installed, a
#      one-line warning is printed and the install proceeds (SHA-256
#      check below is still enforced).
#   5. Verifies the SHA-256 of the tarball against the (now-trusted)
#      checksums file. Mismatch aborts.
#   6. Extracts `sef` into ~/.local/bin (override with SEF_INSTALL_DIR).
#   7. Tells you to add the directory to PATH if it isn't already.
#
# Environment overrides:
#   SEF_VERSION       Pin to a specific tag (default: latest).
#                     Example: SEF_VERSION=v0.1.0 sh install.sh
#   SEF_INSTALL_DIR   Install target dir (default: $HOME/.local/bin).
#
# Doesn't touch sudo, doesn't write outside the install dir, doesn't
# modify your shell rc files. If you need it on PATH globally, copy
# the binary into /usr/local/bin yourself.

set -eu

REPO="shokace/sefaly-cli"
INSTALL_DIR="${SEF_INSTALL_DIR:-$HOME/.local/bin}"

# ── Helpers ──────────────────────────────────────────────────────────

err() {
  printf '\033[31merror:\033[0m %s\n' "$*" >&2
  exit 1
}

info() {
  printf '\033[36m==>\033[0m %s\n' "$*"
}

need() {
  command -v "$1" >/dev/null 2>&1 || err "'$1' is required but not installed."
}

# Both Linux and macOS pre-installs at least one of these. Try both
# so we don't fail on either.
sha256_of() {
  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
    err "neither sha256sum nor shasum is installed; can't verify download integrity."
  fi
}

# ── Sanity checks ────────────────────────────────────────────────────

need curl
need tar
need uname
need mktemp

# ── Detect platform ──────────────────────────────────────────────────

OS_RAW="$(uname -s)"
case "$OS_RAW" in
  Linux)  OS=linux ;;
  Darwin) OS=darwin ;;
  *)      err "unsupported OS '$OS_RAW'. The installer supports Linux and macOS; for Windows, download the .zip from https://github.com/${REPO}/releases/latest" ;;
esac

ARCH_RAW="$(uname -m)"
case "$ARCH_RAW" in
  x86_64|amd64)    ARCH=amd64 ;;
  arm64|aarch64)   ARCH=arm64 ;;
  *)               err "unsupported architecture '$ARCH_RAW'. Supported: x86_64, arm64." ;;
esac

# ── Resolve version ──────────────────────────────────────────────────

VERSION="${SEF_VERSION:-}"
if [ -z "$VERSION" ]; then
  info "Resolving latest release..."
  # GitHub's /releases/latest returns the most recent non-draft,
  # non-prerelease tag. We extract the tag_name field. The regex
  # matches `"tag_name": "..."` with optional whitespace and an
  # optional leading `v` — robust against both `v0.1.0` and bare
  # `0.1.0` tag styles. Grep is less elegant than jq but doesn't
  # add a dependency.
  VERSION=$(
    curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
      | grep '"tag_name":' \
      | head -n 1 \
      | sed -E 's/.*"tag_name":[[:space:]]*"([^"]+)".*/\1/'
  )
  [ -n "$VERSION" ] || err "could not determine latest release. Try setting SEF_VERSION manually."
fi

# Validate before substituting into URLs. A tag must look like
# (optional v)(semver-ish): v1.2.3, 0.1.0, v0.1.0-beta.1, etc.
# If the parse upstream produced junk (the API changed shape, a
# weird tag slipped through), refuse rather than build a malformed
# URL.
case "$VERSION" in
  v[0-9]*|[0-9]*) ;;
  *) err "could not parse a release tag from GitHub. Got: '$VERSION'. Try setting SEF_VERSION manually." ;;
esac

# Normalise: tags are stored with the leading 'v' but the GoReleaser
# archive name uses the bare semver.
TAG="$VERSION"
case "$TAG" in
  v*) ;;
  *)  TAG="v$TAG" ;;
esac
SEMVER="${TAG#v}"

ASSET="sef_${SEMVER}_${OS}_${ARCH}.tar.gz"
RELEASE_BASE="https://github.com/${REPO}/releases/download/${TAG}"
ASSET_URL="${RELEASE_BASE}/${ASSET}"
CHECKSUMS_URL="${RELEASE_BASE}/sha256sums.txt"
CHECKSUMS_SIG_URL="${RELEASE_BASE}/sha256sums.txt.sig"
CHECKSUMS_PEM_URL="${RELEASE_BASE}/sha256sums.txt.pem"

info "Installing sef ${TAG} for ${OS}/${ARCH}"

# ── Download + verify ────────────────────────────────────────────────

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT INT TERM

curl -fsSL --retry 3 --retry-delay 2 -o "$TMP/$ASSET" "$ASSET_URL" \
  || err "download failed: $ASSET_URL"

curl -fsSL --retry 3 --retry-delay 2 -o "$TMP/sha256sums.txt" "$CHECKSUMS_URL" \
  || err "checksums download failed. Refusing to install an unverified binary."

# Cosign signature verification of the checksums file. If cosign is on
# PATH, this is mandatory: a failed verification aborts the install.
# If cosign is missing, print a single-line note and proceed — the
# SHA-256 check below is still enforced, so an attacker who only
# tampered with the binary (not the checksums) is still caught. An
# attacker who replaced BOTH the binary and the checksums would slip
# past a no-cosign install; this is the documented gap.
if command -v cosign >/dev/null 2>&1; then
  info "Verifying cosign signature on sha256sums.txt..."
  curl -fsSL --retry 3 --retry-delay 2 -o "$TMP/sha256sums.txt.sig" "$CHECKSUMS_SIG_URL" \
    || err "cosign signature download failed: $CHECKSUMS_SIG_URL"
  curl -fsSL --retry 3 --retry-delay 2 -o "$TMP/sha256sums.txt.pem" "$CHECKSUMS_PEM_URL" \
    || err "cosign certificate download failed: $CHECKSUMS_PEM_URL"

  # The --certificate-identity-regexp + --certificate-oidc-issuer
  # flags are what bind the signature to "must have come from a
  # GitHub Actions run inside the shokace/sefaly-cli repo." Without
  # them, ANY valid Sigstore signature would pass verify-blob.
  if ! cosign verify-blob \
       --certificate "$TMP/sha256sums.txt.pem" \
       --signature "$TMP/sha256sums.txt.sig" \
       --certificate-identity-regexp '^https://github.com/shokace/sefaly-cli/' \
       --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
       "$TMP/sha256sums.txt" >/dev/null 2>&1; then
    err "cosign signature verification FAILED — refusing to install. The release may have been tampered with."
  fi
  info "Cosign signature verified."
else
  printf '\033[33mnote:\033[0m cosign not installed — skipping signature verification. SHA-256 check still enforced.\n' >&2
  printf '      To verify signatures, install cosign: https://docs.sigstore.dev/cosign/installation/\n' >&2
fi

EXPECTED="$(grep "  ${ASSET}\$" "$TMP/sha256sums.txt" | awk '{print $1}' || true)"
[ -n "$EXPECTED" ] || err "no checksum for ${ASSET} in sha256sums.txt. The release may be malformed."

ACTUAL="$(sha256_of "$TMP/$ASSET")"
if [ "$EXPECTED" != "$ACTUAL" ]; then
  err "checksum mismatch for ${ASSET} — refusing to install.
  expected: $EXPECTED
  actual:   $ACTUAL"
fi

info "SHA-256 verified."

# ── Install ──────────────────────────────────────────────────────────

tar -xzf "$TMP/$ASSET" -C "$TMP" sef \
  || err "could not extract 'sef' from $ASSET."

mkdir -p "$INSTALL_DIR" \
  || err "could not create install directory $INSTALL_DIR."

# mv won't move across devices on every shell, but the same-fs case
# is overwhelmingly common; fall back to cp+rm if mv fails.
if ! mv "$TMP/sef" "$INSTALL_DIR/sef" 2>/dev/null; then
  cp "$TMP/sef" "$INSTALL_DIR/sef" || err "could not write $INSTALL_DIR/sef."
  rm -f "$TMP/sef"
fi
chmod +x "$INSTALL_DIR/sef"

info "Installed to $INSTALL_DIR/sef"

# ── Post-install hint ────────────────────────────────────────────────

# If the install dir isn't on PATH, print a single copy-pasteable line
# for the user's shell. Don't modify rc files ourselves; the user
# decides where their PATH config lives.
case ":$PATH:" in
  *":$INSTALL_DIR:"*)
    printf '\n'
    printf 'Run: \033[36msef login\033[0m to get started.\n'
    ;;
  *)
    printf '\n'
    printf '\033[33mNote:\033[0m %s is not on your PATH. Add this to your shell rc:\n' "$INSTALL_DIR"
    printf '\n'
    printf '  \033[36mexport PATH="%s:$PATH"\033[0m\n' "$INSTALL_DIR"
    printf '\n'
    printf 'Then run: \033[36msef login\033[0m\n'
    ;;
esac
