#!/bin/sh
#
# Install:
#   curl -fsSL https://sfc-repo.snowflakecomputing.com/snowbots/downloads/latest/install.sh | sh
#
# Pin a version:
#   curl -fsSL https://sfc-repo.snowflakecomputing.com/snowbots/downloads/latest/install.sh | SNOWBOTS_VERSION=0.1.18 sh
#
# Direct latest archives:
#   https://sfc-repo.snowflakecomputing.com/snowbots/downloads/latest/SnowBots-cli-mac-arm64.tar.gz
#   https://sfc-repo.snowflakecomputing.com/snowbots/downloads/latest/SnowBots-cli-mac-x64.tar.gz
#   https://sfc-repo.snowflakecomputing.com/snowbots/downloads/latest/SnowBots-cli-linux-x64.tar.gz
#   https://sfc-repo.snowflakecomputing.com/snowbots/downloads/latest/SnowBots-cli-win-x64.zip
set -eu

PUBLIC_BASE="https://sfc-repo.snowflakecomputing.com/snowbots"
DEFAULT_MANIFEST_URL="$PUBLIC_BASE/downloads/latest/cli-latest.json"
FALLBACK_MANIFEST_URL="$PUBLIC_BASE/updates/dev/versions/cli-latest.json"
MANIFEST_URL="${SNOWBOTS_CLI_POINTER_URL:-$DEFAULT_MANIFEST_URL}"
CLI_ROOT="${SNOWBOTS_CLI_ROOT:-${HOME:?HOME is required}/.snowbots/cli}"
REQUESTED_VERSION="${SNOWBOTS_VERSION:-}"
PLATFORM="${SNOWBOTS_INSTALL_PLATFORM:-}"
ARCH="${SNOWBOTS_INSTALL_ARCH:-}"
MODIFY_PATH=0
WORK=""

fail() {
  printf 'snowbots installer: %s\n' "$*" >&2
  exit 1
}

usage() {
  printf '%s\n' 'Usage: install-snowbots.sh [--version <semver>] [--modify-path]'
}

while [ "$#" -gt 0 ]; do
  case "$1" in
    --version)
      [ "$#" -ge 2 ] || fail "--version needs a semver"
      REQUESTED_VERSION="$2"
      shift 2
      ;;
    --modify-path)
      MODIFY_PATH=1
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      fail "unknown argument: $1"
      ;;
  esac
done

case "$MANIFEST_URL" in
  https://*) ;;
  *) fail "manifest URL must use HTTPS: $MANIFEST_URL" ;;
esac

command -v curl >/dev/null 2>&1 || fail "curl is required"
command -v tar >/dev/null 2>&1 || fail "tar is required"
command -v awk >/dev/null 2>&1 || fail "awk is required"
command -v grep >/dev/null 2>&1 || fail "grep is required"

if [ -z "$PLATFORM" ]; then
  case "$(uname -s)" in
    Darwin) PLATFORM="darwin" ;;
    Linux) PLATFORM="linux" ;;
    *) fail "unsupported operating system: $(uname -s)" ;;
  esac
fi
if [ -z "$ARCH" ]; then
  case "$(uname -m)" in
    arm64|aarch64) ARCH="arm64" ;;
    x86_64|amd64) ARCH="x64" ;;
    *) fail "unsupported architecture: $(uname -m)" ;;
  esac
fi
case "$PLATFORM-$ARCH" in
  darwin-arm64|darwin-x64|linux-x64) ;;
  *) fail "no SnowBots CLI archive is published for $PLATFORM-$ARCH" ;;
esac

mkdir -p "$CLI_ROOT/.staging" "$CLI_ROOT/versions"
WORK="$CLI_ROOT/.staging/install-$$"
rm -rf "$WORK"
mkdir -p "$WORK"
cleanup() {
  [ -n "$WORK" ] && rm -rf "$WORK"
}
trap cleanup EXIT HUP INT TERM

fetch() {
  curl --fail --location --silent --show-error --proto '=https' "$1" -o "$2"
}

manifest="$WORK/cli-latest.json"
if [ -z "$REQUESTED_VERSION" ]; then
  printf 'Resolving latest SnowBots CLI for %s-%s...\n' "$PLATFORM" "$ARCH"
  if ! fetch "$MANIFEST_URL" "$manifest" 2>/dev/null; then
    if [ -z "${SNOWBOTS_CLI_POINTER_URL:-}" ]; then
      printf 'Latest download pointer is not published yet; trying the update feed...\n'
      fetch "$FALLBACK_MANIFEST_URL" "$manifest" ||
        fail "could not download a CLI manifest"
    else
      fail "could not download CLI manifest: $MANIFEST_URL"
    fi
  fi
  version="$(awk -F '"' '/^[[:space:]]*"version"[[:space:]]*:/ { print $4; exit }' "$manifest")"
  artifact="$(
    awk -v key="$PLATFORM-$ARCH" '
      index($0, "\"" key "\"") { found=1; next }
      found && /"name"[[:space:]]*:/ { split($0, value, "\""); name=value[4] }
      found && /"sha256"[[:space:]]*:/ { split($0, value, "\""); sha=value[4] }
      found && /"url"[[:space:]]*:/ {
        split($0, value, "\""); url=value[4]
        print name "|" sha "|" url
        exit
      }
    ' "$manifest"
  )"
  [ -n "$artifact" ] || fail "manifest has no artifact for $PLATFORM-$ARCH"
  old_ifs="$IFS"
  IFS='|'
  set -- $artifact
  IFS="$old_ifs"
  name="${1:-}"
  expected_sha="${2:-}"
  url="${3:-}"
else
  version="$REQUESTED_VERSION"
  case "$PLATFORM" in
    darwin) archive_platform="mac" ;;
    linux) archive_platform="linux" ;;
  esac
  name="SnowBots-cli-$version-$archive_platform-$ARCH.tar.gz"
  base="${SNOWBOTS_CLI_UPDATE_URL:-https://sfc-repo.snowflakecomputing.com/snowbots}"
  channel="${SNOWBOTS_CLI_UPDATE_CHANNEL:-dev}"
  url="${base%/}/updates/$channel/versions/$version/cli/$name"
  checksum="$WORK/$name.sha256"
  printf 'Resolving SnowBots CLI %s for %s-%s...\n' "$version" "$PLATFORM" "$ARCH"
  fetch "$url.sha256" "$checksum"
  expected_sha="$(awk 'NR == 1 { print $1 }' "$checksum")"
fi

printf '%s\n' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' ||
  fail "invalid release version: $version"
case "$expected_sha" in
  *[!0-9a-fA-F]*|'') fail "invalid sha256 for $name" ;;
esac
[ "${#expected_sha}" -eq 64 ] || fail "invalid sha256 for $name"
case "$url" in
  https://*/updates/*/versions/"$version"/cli/"$name") ;;
  *) fail "artifact URL is not immutable HTTPS: $url" ;;
esac

archive="$WORK/$name"
printf 'Downloading %s...\n' "$name"
fetch "$url" "$archive"
if command -v sha256sum >/dev/null 2>&1; then
  actual_sha="$(sha256sum "$archive" | awk '{ print $1 }')"
elif command -v shasum >/dev/null 2>&1; then
  actual_sha="$(shasum -a 256 "$archive" | awk '{ print $1 }')"
else
  fail "sha256sum or shasum is required"
fi
[ "$actual_sha" = "$expected_sha" ] || fail "sha256 mismatch for $name"
printf 'Verified sha256 %s\n' "$actual_sha"

unpack="$WORK/unpack"
mkdir -p "$unpack"
tar -xzf "$archive" -C "$unpack" || fail "could not extract $name"
if [ -f "$unpack/version" ] && [ -d "$unpack/runtime" ]; then
  package="$unpack"
else
  set -- "$unpack"/*
  [ "$#" -eq 1 ] && [ -d "$1" ] || fail "archive did not contain one package directory"
  package="$1"
fi
[ "$(awk 'NR == 1 { print; exit }' "$package/version" 2>/dev/null)" = "$version" ] ||
  fail "archive version does not match $version"
[ -x "$package/runtime/node" ] || fail "archive is missing runtime/node"
[ -x "$package/bin/snowbots" ] || fail "archive is missing bin/snowbots"

version_dir="$CLI_ROOT/versions/$version"
if [ -e "$version_dir" ]; then
  [ -f "$version_dir/version" ] &&
    [ "$(awk 'NR == 1 { print; exit }' "$version_dir/version")" = "$version" ] &&
    [ -x "$version_dir/bin/snowbots" ] ||
    fail "existing version tree is invalid: $version_dir"
  printf 'Version %s is already installed.\n' "$version"
else
  mv "$package" "$version_dir"
  printf 'Installed SnowBots CLI %s in %s\n' "$version" "$version_dir"
fi

# Replace a symlink in place. Do not `mv` onto an existing directory symlink:
# macOS mv follows that dest and drops the new link *inside* the old tree.
replace_link() {
  dest="$1"
  target="$2"
  [ ! -e "$dest" ] || [ -L "$dest" ] || fail "$dest is not a symlink; refusing to replace it"
  tmp="$dest.new-$$"
  ln -s "$target" "$tmp"
  rm -f "$dest"
  mv "$tmp" "$dest"
}

current="$CLI_ROOT/current"
replace_link "$current" "$version_dir"

bin_dir="${SNOWBOTS_BIN_DIR:-$HOME/.local/bin}"
mkdir -p "$bin_dir"
launcher="$bin_dir/snowbots"
replace_link "$launcher" "$current/bin/snowbots"
printf 'Linked snowbots at %s\n' "$launcher"

detect_path_config() {
  case "${SHELL:-}" in
    */zsh)
      path_rc="$HOME/.zshrc"
      path_line='export PATH="$HOME/.local/bin:$PATH"'
      ;;
    */bash)
      if [ -f "$HOME/.bash_profile" ] || [ "$(uname -s)" = "Darwin" ]; then
        path_rc="$HOME/.bash_profile"
      else
        path_rc="$HOME/.bashrc"
      fi
      path_line='export PATH="$HOME/.local/bin:$PATH"'
      ;;
    */fish)
      path_rc="$HOME/.config/fish/config.fish"
      path_line='fish_add_path "$HOME/.local/bin"'
      ;;
    *)
      return 1
      ;;
  esac
}

print_path_instruction() {
  if detect_path_config; then
    printf '%s is not on PATH. Add this line to %s, then restart your shell:\n' "$bin_dir" "$path_rc"
    printf '  %s\n' "$path_line"
  else
    printf '%s is not on PATH. Add it to your shell configuration, then restart your shell:\n' "$bin_dir"
    printf '  export PATH="%s:$PATH"\n' "$bin_dir"
  fi
}

append_path_config() {
  detect_path_config || return 1
  mkdir -p "$(dirname "$path_rc")"
  if [ -f "$path_rc" ] && grep -Fqx "$path_line" "$path_rc"; then
    printf 'PATH is already configured in %s\n' "$path_rc"
    return 0
  fi
  printf '\n%s\n' "$path_line" >> "$path_rc"
  printf 'Added %s to %s. Restart your shell to use snowbots.\n' "$bin_dir" "$path_rc"
}

case ":${PATH:-}:" in
  *":$bin_dir:"*) ;;
  *)
    printf '\n'
    if ! detect_path_config; then
      print_path_instruction
    elif [ "$MODIFY_PATH" -eq 1 ]; then
      append_path_config
    elif [ "${SKIP_PATH_PROMPT:-}" = "1" ] || [ "${NON_INTERACTIVE:-}" = "1" ]; then
      print_path_instruction
    elif [ -t 0 ]; then
      printf 'Add %s to PATH in %s? [y/N] ' "$bin_dir" "$path_rc"
      read answer
      case "$answer" in
        y|Y|yes|YES|Yes) append_path_config ;;
        *) print_path_instruction ;;
      esac
    else
      printf 'Non-interactive install; shell configuration was not changed.\n'
      print_path_instruction
      printf 'Re-run with --modify-path to update it explicitly.\n'
    fi
    ;;
esac
printf 'SnowBots CLI %s is ready.\n' "$version"
