#!/usr/bin/env bash
#
# PocketShell host agent installer
#
#   curl -fsSL https://pocketshell.app/install.sh | bash
#
# What it does:
#   1. detects your OS / arch (macOS or Linux, x86_64 or arm64; glibc/musl on Linux)
#   2. fetches the matching pre-built `pocketshell` binary from the release host
#   3. verifies the SHA-256 checksum
#   4. installs it to ~/.local/bin (or /usr/local/bin if running as root)
#
# Releases live on GitHub. $POCKETSHELL_BASE_URL is a repo URL; assets are
# resolved via GitHub's standard release URLs:
#
#   <base>/releases/latest                                            # 302 → /releases/tag/vX.Y.Z
#   <base>/releases/download/<tag>/pocketshell-<tag>-<triple>.tar.gz
#   <base>/releases/download/<tag>/pocketshell-<tag>-<triple>.tar.gz.sha256
#
# The in-binary `pocketshell update` uses the same convention, so a single
# release on GitHub serves both fresh installs and self-updates.
#
# Optional env vars (override the defaults):
#   POCKETSHELL_VERSION       e.g. v0.1.0  (default: latest release tag)
#   POCKETSHELL_INSTALL_DIR   e.g. /usr/local/bin  (default: ~/.local/bin, or /usr/local/bin as root)
#   POCKETSHELL_VARIANT       gnu | musl  (default: auto-detect on Linux)
#   POCKETSHELL_BASE_URL      e.g. https://github.com/<owner>/<repo>  (default: yashagldit/PocketShell)
#   POCKETSHELL_SKIP_COSIGN   1 to skip Sigstore signature verification (insecure — only use
#                             if you've manually verified the artifact out of band)
#

set -euo pipefail

BASE_URL="${POCKETSHELL_BASE_URL:-https://github.com/yashagldit/PocketShell}"
# Strip a single trailing slash so concatenation never double-slashes.
BASE_URL="${BASE_URL%/}"
VERSION="${POCKETSHELL_VERSION:-latest}"

# ---- pretty output ---------------------------------------------------------
if [ -t 1 ]; then
  R=$'\033[0m'; B=$'\033[1m'; DIM=$'\033[2m'
  G=$'\033[32m'; Y=$'\033[33m'; RED=$'\033[31m'
else
  R=; B=; DIM=; G=; Y=; RED=
fi
say()  { printf '%s\n' "$*"; }
info() { printf '%s==>%s %s\n' "$G" "$R" "$*"; }
warn() { printf '%swarn:%s %s\n' "$Y" "$R" "$*" >&2; }
die()  { printf '%serror:%s %s\n' "$RED" "$R" "$*" >&2; exit 1; }

# ---- preflight -------------------------------------------------------------
need() { command -v "$1" >/dev/null 2>&1 || die "missing dependency: $1"; }
need curl
need tar
need uname
need install
need mktemp

if command -v shasum >/dev/null 2>&1; then
  SHA_CHECK="shasum -a 256 -c"
elif command -v sha256sum >/dev/null 2>&1; then
  SHA_CHECK="sha256sum -c"
else
  die "neither shasum nor sha256sum is available; cannot verify download"
fi

# ---- platform detection ----------------------------------------------------
OS="$(uname -s)"
ARCH="$(uname -m)"

case "$OS" in
  Darwin) os_kind=macos ;;
  Linux)  os_kind=linux ;;
  MINGW*|MSYS*|CYGWIN*)
    die "Windows is not yet supported." ;;
  *) die "unsupported OS: $OS" ;;
esac

case "$ARCH" in
  x86_64|amd64)   arch_kind=x86_64 ;;
  arm64|aarch64)  arch_kind=aarch64 ;;
  *) die "unsupported architecture: $ARCH" ;;
esac

# Detect musl on Linux unless the caller forced a variant. The release
# matrix only ships musl for x86_64, so we only branch on that.
variant="${POCKETSHELL_VARIANT:-}"
if [ "$os_kind" = "linux" ] && [ -z "$variant" ]; then
  if [ -f /lib/ld-musl-x86_64.so.1 ] \
      || [ -f /lib/ld-musl-aarch64.so.1 ] \
      || [ -f /etc/alpine-release ] \
      || (ldd --version 2>&1 | grep -qi musl); then
    variant=musl
  else
    variant=gnu
  fi
fi

# ---- map to release target -------------------------------------------------
case "$os_kind/$arch_kind" in
  macos/aarch64)  target=aarch64-apple-darwin ;;
  macos/x86_64)
    die "Intel Macs are not supported. PocketShell only ships Apple Silicon (M-series) macOS binaries." ;;
  linux/aarch64)  target=aarch64-unknown-linux-gnu ;;
  linux/x86_64)
    if [ "$variant" = "musl" ]; then
      target=x86_64-unknown-linux-musl
    else
      target=x86_64-unknown-linux-gnu
    fi ;;
  *) die "no prebuilt binary for $os_kind/$arch_kind." ;;
esac

# ---- resolve version -------------------------------------------------------
# GitHub's `/releases/latest` 302s to `/releases/tag/vX.Y.Z`. We don't parse
# the body — just follow the redirect and read the final URL's last path
# segment. Pure curl, no API quota, no JSON parsing.
if [ "$VERSION" = "latest" ]; then
  info "resolving latest version from ${BASE_URL}/releases/latest"
  final="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "${BASE_URL}/releases/latest" || true)"
  resolved="${final##*/}"
  if [ -z "$resolved" ] || [ "$resolved" = "latest" ]; then
    die "could not resolve latest version — is ${BASE_URL} a GitHub repo with a published release?"
  fi
  VERSION="$resolved"
fi

# Canonicalize so users can pass either `0.1.0` or `v0.1.0` via env.
case "$VERSION" in
  v*) ;;
  *)  VERSION="v${VERSION}" ;;
esac

# ---- install location ------------------------------------------------------
if [ -n "${POCKETSHELL_INSTALL_DIR:-}" ]; then
  install_dir="$POCKETSHELL_INSTALL_DIR"
elif [ "$(id -u)" = "0" ]; then
  install_dir=/usr/local/bin
else
  install_dir="$HOME/.local/bin"
fi

# ---- download & verify -----------------------------------------------------
archive="pocketshell-${VERSION}-${target}.tar.gz"
url="${BASE_URL}/releases/download/${VERSION}/${archive}"

tmp="$(mktemp -d -t pocketshell.XXXXXX)"
trap 'rm -rf "$tmp"' EXIT

info "downloading ${B}${archive}${R}"
say  "${DIM}${url}${R}"
curl -fL --progress-bar -o "$tmp/$archive"        "$url" \
  || die "download failed. check that ${VERSION} exists at ${BASE_URL}/releases/tag/${VERSION}"
curl -fsSL              -o "$tmp/$archive.sha256" "$url.sha256" \
  || die "checksum file missing for this release"

info "verifying checksum"
( cd "$tmp" && $SHA_CHECK "$archive.sha256" >/dev/null ) || die "checksum mismatch — refusing to install"

# ---- cosign keyless verification -------------------------------------------
# Verifies the Sigstore bundle (signature + Fulcio cert + Rekor inclusion
# proof) against the pinned GitHub workflow identity that produced the
# release. The SHA-256 above only proves integrity against the release origin
# — same origin signed both the binary and the checksum, so a GitHub Releases
# compromise alone would pass that check. The cosign signature is bound to a
# specific OIDC token from a specific workflow path, so forging it requires
# stealing both the GitHub release flow AND minting a fake OIDC token.
#
# On first install cosign is soft-recommended (loud warning if missing, install
# proceeds). Subsequent `pocketshell update` invocations REQUIRE it, so users
# who run updates will be prompted to install it before their next upgrade.
COSIGN_IDENTITY_REGEXP='^https://github\.com/yashagldit/PocketShell/\.github/workflows/release-host-agent\.yml@refs/tags/v[0-9][0-9A-Za-z.-]*$'
COSIGN_OIDC_ISSUER='https://token.actions.githubusercontent.com'

if [ "${POCKETSHELL_SKIP_COSIGN:-0}" = "1" ]; then
  warn "POCKETSHELL_SKIP_COSIGN=1 set; skipping Sigstore signature verification."
  warn "The SHA-256 check above only proves integrity against the release origin (GitHub Releases)."
  warn "Continuing without authenticity verification at your explicit request."
elif ! command -v cosign >/dev/null 2>&1; then
  warn "cosign not found — skipping signature verification (SHA-256 checked). Install cosign for stronger guarantees on future updates."
else
  info "verifying Sigstore signature"
  if ! curl -fsSL -o "$tmp/$archive.cosign-bundle.json" "$url.cosign-bundle.json"; then
    die "cosign bundle is missing for this release. If you have manually verified the SHA-256 out of band, set POCKETSHELL_SKIP_COSIGN=1 to bypass."
  fi
  if ! cosign verify-blob \
        --new-bundle-format \
        --bundle "$tmp/$archive.cosign-bundle.json" \
        --certificate-identity-regexp "$COSIGN_IDENTITY_REGEXP" \
        --certificate-oidc-issuer "$COSIGN_OIDC_ISSUER" \
        "$tmp/$archive" >/dev/null 2>"$tmp/cosign.err"; then
    say "${DIM}$(cat "$tmp/cosign.err")${R}" >&2
    die "cosign verification failed — refusing to install. Identity expected: $COSIGN_IDENTITY_REGEXP"
  fi
fi

info "extracting"
tar -xzf "$tmp/$archive" -C "$tmp"
[ -f "$tmp/pocketshell" ] || die "tarball did not contain a pocketshell binary"
chmod +x "$tmp/pocketshell"

# ---- install ---------------------------------------------------------------
mkdir -p "$install_dir" 2>/dev/null || die "cannot create $install_dir"

if [ -w "$install_dir" ]; then
  install -m 0755 "$tmp/pocketshell" "$install_dir/pocketshell"
elif command -v sudo >/dev/null 2>&1; then
  warn "$install_dir is not writable; using sudo"
  sudo install -m 0755 "$tmp/pocketshell" "$install_dir/pocketshell"
else
  die "$install_dir is not writable and sudo is unavailable. Set POCKETSHELL_INSTALL_DIR to a writable path."
fi

# macOS curl-downloaded binaries don't normally carry the quarantine xattr,
# but tar-extracted ones can inherit it on some versions of macOS. Strip it
# so the user doesn't hit Gatekeeper on first run.
if [ "$os_kind" = "macos" ] && command -v xattr >/dev/null 2>&1; then
  xattr -d com.apple.quarantine "$install_dir/pocketshell" 2>/dev/null || true
fi

# ---- post-install ----------------------------------------------------------
case ":$PATH:" in
  *":$install_dir:"*) on_path=1 ;;
  *) on_path=0 ;;
esac

say
info "${B}installed${R} pocketshell ${VERSION} → ${install_dir}/pocketshell"
"$install_dir/pocketshell" --version 2>/dev/null || true
say

if [ "$on_path" = "0" ]; then
  warn "$install_dir is not on your PATH. add this to your shell rc (then open a new shell):"
  say  "    ${DIM}export PATH=\"$install_dir:\$PATH\"${R}"
  say  "until then, use the full path below for the commands in 'Next steps'."
  say
  cmd="$install_dir/pocketshell"
else
  cmd="pocketshell"
fi

next_steps() {
  cat <<NEXT
${B}Next steps${R}

  1. Pair with your phone:  ${DIM}${cmd} pair${R}
     (a QR appears — scan it from the PocketShell app; the daemon installs
      and starts automatically once the scan succeeds)
  2. Check status / logs:   ${DIM}${cmd} status${R}
  3. Self-update later:     ${DIM}${cmd} update${R}

Website: https://pocketshell.app
NEXT
}

# ---- auto-pair ---------------------------------------------------------------
# One-paste setup: when a real terminal is attached, launch `pocketshell pair`
# immediately so the QR shows up without the user needing a second command.
# `pair` finishes the whole job itself — after the phone scans, it persists
# auth and installs/starts the daemon.
#
# Skipped (falls back to printed next steps) when:
#   - POCKETSHELL_NO_PAIR=1            (automation / config-management opt-out)
#   - no usable /dev/tty               (CI, provisioning scripts)
#   - ~/.pocketshell/state.json exists (already paired — a reinstall shouldn't
#                                       surprise the user with a device-add QR)
state_file="${HOME}/.pocketshell/state.json"
if [ "${POCKETSHELL_NO_PAIR:-0}" = "1" ]; then
  next_steps
elif ! { : < /dev/tty; } 2>/dev/null; then
  next_steps
elif [ -f "$state_file" ]; then
  say "existing PocketShell state found (${DIM}${state_file}${R}) — skipping auto-pairing."
  next_steps
else
  say
  info "starting pairing — scan the QR below with the PocketShell app"
  say  "${DIM}(on the phone: Hosts → Pair host → Scan QR · press Ctrl-C to pair later with \`${cmd} pair\`)${R}"
  say
  "$install_dir/pocketshell" pair < /dev/tty || {
    warn "pairing did not complete — you can retry any time:"
    say
    next_steps
  }
fi
