#!/bin/sh
#
# JoyStream CLI installer.
#
#   curl -fsSL https://get.joystream.ai | sh
#
# Detects your platform, downloads the matching binary, VERIFIES ITS CHECKSUM,
# and installs it as `jstm`. Re-run it to upgrade.
#
# You are piping a script into a shell, so the checksum step is not optional:
# nothing is installed unless the downloaded bytes match the published digest,
# and a missing digest is treated as a failure rather than a skip.
#
# Prefer to read before running? That is the right instinct:
#   curl -fsSL https://get.joystream.ai -o install.sh && less install.sh && sh install.sh
#
# Environment overrides:
#   JSTM_VERSION       install a specific version (e.g. 0.22.0) instead of latest
#   JSTM_INSTALL_DIR   where to install (default: /usr/local/bin, else ~/.local/bin)
#   JSTM_BASE_URL      base URL to fetch assets from (testing / mirrors)

set -eu

RELEASES="https://github.com/joystream-ai/cli-releases/releases"
BIN_NAME="jstm"

TMPDIR_="" # cleaned up on exit, see trap below

cleanup() {
	[ -n "$TMPDIR_" ] && rm -rf "$TMPDIR_"
}
trap cleanup EXIT INT TERM

die() {
	echo "error: $*" >&2
	exit 1
}

info() {
	echo "$*"
}

# --------------------------------------------------------------------- platform

detect_target() {
	os="$(uname -s)"
	arch="$(uname -m)"

	case "$os" in
	Darwin) os_name="macos" ;;
	Linux) os_name="linux" ;;
	*) die "unsupported operating system: $os (supported: macOS, Linux)" ;;
	esac

	# uname reports x86_64; release assets say amd64.
	case "$arch" in
	arm64 | aarch64) arch_name="arm64" ;;
	x86_64 | amd64) arch_name="amd64" ;;
	*) die "unsupported architecture: $arch (supported: arm64, x86_64)" ;;
	esac

	target="${os_name}-${arch_name}"

	# Only three combinations are actually built. Linux/arm64 in particular is a
	# valid-looking pair with no asset behind it, so reject it here rather than
	# 404ing later with a confusing message.
	case "$target" in
	macos-arm64 | macos-amd64 | linux-amd64) ;;
	*) die "unsupported platform: ${os}/${arch} — no ${BIN_NAME}-${target} is published" ;;
	esac

	echo "${BIN_NAME}-${target}"
}

# --------------------------------------------------------------------- download

fetch() {
	# $1 = url, $2 = destination
	if command -v curl >/dev/null 2>&1; then
		curl -fsSL "$1" -o "$2"
	elif command -v wget >/dev/null 2>&1; then
		wget -qO "$2" "$1"
	else
		die "need curl or wget to download files"
	fi
}

sha256_of() {
	if command -v sha256sum >/dev/null 2>&1; then
		sha256sum "$1" | cut -d' ' -f1
	elif command -v shasum >/dev/null 2>&1; then
		shasum -a 256 "$1" | cut -d' ' -f1
	else
		die "need sha256sum or shasum to verify the download"
	fi
}

# --------------------------------------------------------------------- install dir

choose_install_dir() {
	if [ -n "${JSTM_INSTALL_DIR:-}" ]; then
		echo "$JSTM_INSTALL_DIR"
		return
	fi
	# /usr/local/bin is on the default PATH; ~/.local/bin often is not, so it is
	# the fallback rather than the default, and we warn when we land there.
	if [ -w /usr/local/bin ] 2>/dev/null; then
		echo /usr/local/bin
	else
		echo "$HOME/.local/bin"
	fi
}

# --------------------------------------------------------------------- main

asset="$(detect_target)"

if [ -n "${JSTM_BASE_URL:-}" ]; then
	base_url="$JSTM_BASE_URL"
	label="$JSTM_BASE_URL"
elif [ -n "${JSTM_VERSION:-}" ]; then
	base_url="${RELEASES}/download/cli-v${JSTM_VERSION}"
	label="v${JSTM_VERSION}"
else
	# `latest` resolves to the newest non-prerelease, which is why the rolling
	# cli-latest build is published as a prerelease and never picked up here.
	base_url="${RELEASES}/latest/download"
	label="latest"
fi

info "Installing ${BIN_NAME} (${asset}, ${label})"

TMPDIR_="$(mktemp -d)"

if ! fetch "${base_url}/${asset}" "${TMPDIR_}/${asset}"; then
	die "download failed: ${base_url}/${asset}"
fi
if ! fetch "${base_url}/checksums.txt" "${TMPDIR_}/checksums.txt"; then
	die "download failed: ${base_url}/checksums.txt (cannot verify without it)"
fi

expected="$(grep " ${asset}\$" "${TMPDIR_}/checksums.txt" | cut -d' ' -f1 || true)"
[ -n "$expected" ] || die "no checksum published for ${asset} — refusing to install unverified"

actual="$(sha256_of "${TMPDIR_}/${asset}")"
[ "$expected" = "$actual" ] || die "checksum mismatch for ${asset}
  expected: ${expected}
  actual:   ${actual}
Nothing was installed."

install_dir="$(choose_install_dir)"
mkdir -p "$install_dir" || die "cannot create ${install_dir}"

chmod +x "${TMPDIR_}/${asset}"
# mv, not cp: replacing a running binary in place fails on some systems, and mv
# is atomic within a filesystem so an interrupted upgrade cannot leave a partial file.
if ! mv -f "${TMPDIR_}/${asset}" "${install_dir}/${BIN_NAME}" 2>/dev/null; then
	die "cannot write to ${install_dir} — set JSTM_INSTALL_DIR to a writable directory"
fi

info "Installed ${install_dir}/${BIN_NAME}"

case ":${PATH}:" in
*":${install_dir}:"*) ;;
*)
	info ""
	info "warning: ${install_dir} is not on your PATH. Add it:"
	info "    export PATH=\"${install_dir}:\$PATH\""
	;;
esac

"${install_dir}/${BIN_NAME}" --version 2>/dev/null || true

# A bare version string leaves the reader with a working binary and no idea what
# to type next; a design partner asked for the first command outright. Printed
# through info() so it shares the installer's single output channel.
info ""
info "Next:"
info "    jstm login              sign in"
info "    jstm agent plan --new   draft your first agent"
info "    jstm --help             everything else"
