#!/bin/sh
#
# Resolves the plugin-pinned Revyl runtime, then replaces this process with it.

set -eu

SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
PLUGIN_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
MANIFEST_PATH=${REVYL_RUNTIME_MANIFEST:-"$PLUGIN_DIR/runtime-manifest.json"}
TEMPORARY_PATH=
DOWNLOAD_ATTEMPTS=3

# cleanup removes only this process's incomplete download.
cleanup() {
  if [ -n "$TEMPORARY_PATH" ]; then
    rm -f "$TEMPORARY_PATH"
  fi
}

# fail reports a bootstrap failure without corrupting MCP stdout.
fail() {
  printf 'Revyl plugin runtime error: %s\n' "$1" >&2
  exit 1
}

# manifest_string reads one flat string property from the generated manifest.
manifest_string() {
  key=$1
  sed -n "s/^[[:space:]]*\"${key}\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\"[[:space:]]*,\\{0,1\\}[[:space:]]*$/\\1/p" "$MANIFEST_PATH"
}

# manifest_scalar reads one flat number or boolean property.
manifest_scalar() {
  key=$1
  sed -n "s/^[[:space:]]*\"${key}\"[[:space:]]*:[[:space:]]*\\([^,[:space:]]*\\)[[:space:]]*,\\{0,1\\}[[:space:]]*$/\\1/p" "$MANIFEST_PATH"
}

# absolute_path prints one path in absolute form without resolving symlinks.
absolute_path() {
  case "$1" in
    /*) printf '%s\n' "$1" ;;
    *)
      parent_directory=$(CDPATH= cd -- "$(dirname -- "$1")" && pwd)
      printf '%s/%s\n' "$parent_directory" "$(basename -- "$1")"
      ;;
  esac
}

# resolve_override returns an explicitly selected executable when configured.
resolve_override() {
  requested=$1
  if command -v "$requested" >/dev/null 2>&1; then
    absolute_path "$(command -v "$requested")"
    return
  fi
  if [ -f "$requested" ] && [ -x "$requested" ]; then
    absolute_path "$requested"
    return
  fi
  fail "REVYL_BINARY is not executable: $requested"
}

# run_runtime exposes the executable for setup remediation, then replaces this process.
run_runtime() {
  selected_binary=$1
  shift
  REVYL_MCP_EXECUTABLE=$selected_binary
  export REVYL_MCP_EXECUTABLE
  REVYL_NO_UPDATE_NOTIFIER=1
  export REVYL_NO_UPDATE_NOTIFIER
  REVYL_CLIENT_SOURCE=cursor_plugin
  if [ "$PLUGIN_HOST" = codex ]; then
    REVYL_CLIENT_SOURCE=
  fi
  export REVYL_CLIENT_SOURCE
  trap - EXIT HUP INT TERM
  exec "$selected_binary" "$@"
}

# detect_platform maps the current host to one release manifest prefix.
detect_platform() {
  case "$(uname -s 2>/dev/null || true)" in
    Darwin) operating_system=darwin ;;
    Linux) operating_system=linux ;;
    *) fail "unsupported operating system: $(uname -s 2>/dev/null || printf unknown)" ;;
  esac

  case "$(uname -m 2>/dev/null || true)" in
    x86_64|amd64) architecture=amd64 ;;
    arm64|aarch64) architecture=arm64 ;;
    *) fail "unsupported architecture: $(uname -m 2>/dev/null || printf unknown)" ;;
  esac

  printf '%s_%s\n' "$operating_system" "$architecture"
}

# sha256_file returns the lowercase SHA256 digest for one file.
sha256_file() {
  path=$1
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$path" | awk '{print tolower($1)}'
    return
  fi
  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$path" | awk '{print tolower($1)}'
    return
  fi
  fail "sha256sum or shasum is required to verify the Revyl runtime"
}

# checksum_matches verifies one file without writing output.
checksum_matches() {
  path=$1
  expected=$2
  [ -f "$path" ] && [ "$(sha256_file "$path")" = "$expected" ]
}

# installed_runtime_candidates lists already-installed Revyl CLI paths in trust order.
installed_runtime_candidates() {
  command -v revyl 2>/dev/null || true
  printf '%s\n' /usr/local/bin/revyl
  if [ -n "${HOME:-}" ]; then
    printf '%s\n' "$HOME/.revyl/bin/revyl"
    printf '%s\n' "$HOME/.local/bin/revyl"
  fi
}

# resolve_installed_runtime prints the first installed CLI byte-identical to the pin.
resolve_installed_runtime() {
  expected=$1
  installed_runtime_candidates | while IFS= read -r candidate; do
    [ -n "$candidate" ] || continue
    { [ -f "$candidate" ] && [ -x "$candidate" ]; } || continue
    if checksum_matches "$candidate" "$expected"; then
      absolute_path "$candidate"
      break
    fi
  done
}

# ensure_runtime_directory creates the private versioned cache directory.
ensure_runtime_directory() {
  mkdir -p "$runtime_directory" &&
    chmod 0700 "$runtime_directory"
}

# adopt_installed_runtime copies a verified installed CLI into the plugin cache.
adopt_installed_runtime() {
  source_binary=$1
  ensure_runtime_directory || return 1
  TEMPORARY_PATH="$runtime_directory/.revyl.adopt.$$"
  cp -- "$source_binary" "$TEMPORARY_PATH" || return 1
  chmod 0700 "$TEMPORARY_PATH" || return 1
  checksum_matches "$TEMPORARY_PATH" "$expected_checksum" || return 1
  mv -f "$TEMPORARY_PATH" "$runtime_binary" || return 1
  TEMPORARY_PATH=
  checksum_matches "$runtime_binary" "$expected_checksum"
}

# publish_user_runtime copies a verified pin onto a user PATH location.
#
# Prefers ~/.revyl/bin/revyl, then ~/.local/bin/revyl. A different existing CLI
# is left in place. Failures never block launching the verified runtime.
publish_user_runtime() {
  source_binary=$1
  [ -n "${HOME:-}" ] || return 0

  for destination in "$HOME/.revyl/bin/revyl" "$HOME/.local/bin/revyl"; do
    if checksum_matches "$destination" "$expected_checksum"; then
      return 0
    fi
    if [ -e "$destination" ] || [ -L "$destination" ]; then
      continue
    fi

    destination_directory=$(dirname -- "$destination")
    mkdir -p "$destination_directory" || continue
    publish_temporary="$destination_directory/.revyl.publish.$$"
    if cp -- "$source_binary" "$publish_temporary" &&
      chmod 0700 "$publish_temporary" &&
      checksum_matches "$publish_temporary" "$expected_checksum" &&
      mv -f "$publish_temporary" "$destination"; then
      checksum_matches "$destination" "$expected_checksum" && return 0
    fi
    rm -f "$publish_temporary"
  done
  return 0
}

# run_verified_runtime publishes a verified pin onto PATH, then replaces this process.
run_verified_runtime() {
  if [ "$PLUGIN_HOST" = cursor ]; then
    publish_user_runtime "$1"
  fi
  run_runtime "$@"
}

# download_attempt fetches one bounded HTTPS resource exactly once.
download_attempt() {
  url=$1
  destination=$2
  if command -v curl >/dev/null 2>&1; then
    curl --fail --silent --show-error --location \
      --connect-timeout 10 \
      --max-time 180 \
      --output "$destination" \
      "$url"
    return
  fi
  if command -v wget >/dev/null 2>&1; then
    wget --quiet \
      --timeout=30 \
      --tries=1 \
      --output-document="$destination" \
      "$url"
    return
  fi
  fail "curl or wget is required for the first Revyl plugin run"
}

# download retries one bounded fetch, backing off between transient failures.
download() {
  url=$1
  destination=$2
  attempt=1
  delay=1
  while :; do
    if download_attempt "$url" "$destination"; then
      return 0
    fi
    rm -f "$destination"
    if [ "$attempt" -ge "$DOWNLOAD_ATTEMPTS" ]; then
      return 1
    fi
    printf 'Revyl plugin runtime: download attempt %s of %s failed; retrying in %ss\n' \
      "$attempt" "$DOWNLOAD_ATTEMPTS" "$delay" >&2
    sleep "$delay"
    attempt=$((attempt + 1))
    delay=$((delay * 2))
  done
}

trap cleanup EXIT HUP INT TERM
umask 077

PLUGIN_HOST=${REVYL_PLUGIN_HOST:-cursor}
case "$PLUGIN_HOST" in
  cursor|codex) ;;
  *) fail "unsupported plugin host; expected cursor or codex" ;;
esac

if [ "${REVYL_API_KEY:-}" = '${env:REVYL_API_KEY}' ]; then
  unset REVYL_API_KEY
fi

if [ -n "${REVYL_BINARY:-}" ] && [ "$REVYL_BINARY" != '${env:REVYL_BINARY}' ]; then
  selected_binary=$(resolve_override "$REVYL_BINARY")
  run_runtime "$selected_binary" "$@"
fi

[ -f "$MANIFEST_PATH" ] || fail "runtime manifest not found at $MANIFEST_PATH"

schema_version=$(manifest_scalar schema_version)
prepared=$(manifest_scalar prepared)
plugin_version=$(manifest_string plugin_version)
runtime_version=$(manifest_string runtime_version)
release_tag=$(manifest_string release_tag)
release_base_url=$(manifest_string release_base_url)

[ "$schema_version" = "1" ] || fail "unsupported runtime manifest schema: ${schema_version:-missing}"
[ "$prepared" = "true" ] || fail "this plugin release has no prepared runtime; reinstall or update the Revyl plugin"
printf '%s' "$plugin_version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$' ||
  fail "invalid plugin version in runtime manifest"
printf '%s' "$runtime_version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$' ||
  fail "invalid runtime version in runtime manifest"
[ "$release_tag" = "v$runtime_version" ] || fail "runtime release tag does not match its version"
expected_base_url="https://github.com/RevylAI/revyl-cli/releases/download/$release_tag"
[ "$release_base_url" = "$expected_base_url" ] || fail "runtime release URL is not immutable"

platform=$(detect_platform)
asset=$(manifest_string "${platform}_asset")
expected_checksum=$(manifest_string "${platform}_sha256")

[ -n "$asset" ] || fail "runtime manifest has no asset for $platform"
printf '%s' "$asset" | grep -Eq '^revyl-(darwin|linux)-(amd64|arm64)$' ||
  fail "runtime manifest contains an invalid asset name for $platform"
printf '%s' "$expected_checksum" | grep -Eq '^[0-9a-f]{64}$' ||
  fail "runtime manifest contains an invalid checksum for $platform"

cache_root=${REVYL_PLUGIN_CACHE_DIR:-"${XDG_CACHE_HOME:-$HOME/.cache}/revyl/$PLUGIN_HOST-plugin"}
runtime_directory="$cache_root/$runtime_version/$platform"
runtime_binary="$runtime_directory/revyl"

if checksum_matches "$runtime_binary" "$expected_checksum"; then
  chmod 0700 "$runtime_binary"
  run_verified_runtime "$runtime_binary" "$@"
fi

# An already-installed CLI is byte-identical to the pinned asset when its digest
# matches, so adopting it is equivalent to the download it replaces.
installed_binary=$(resolve_installed_runtime "$expected_checksum")
if [ -n "$installed_binary" ]; then
  if adopt_installed_runtime "$installed_binary"; then
    run_verified_runtime "$runtime_binary" "$@"
  fi
  cleanup
  TEMPORARY_PATH=
  printf 'Revyl plugin runtime: could not populate the plugin cache; running verified %s\n' \
    "$installed_binary" >&2
  run_verified_runtime "$installed_binary" "$@"
fi

# Callers on a short time budget opt out of the download so they fail fast
# instead of blocking on the network for a runtime a later run will cache.
if [ "${REVYL_RUNTIME_NO_DOWNLOAD:-}" = "1" ]; then
  fail "the pinned Revyl runtime is not cached yet and this invocation may not download it"
fi

ensure_runtime_directory
TEMPORARY_PATH="$runtime_directory/.revyl.download.$$"
download "$release_base_url/$asset" "$TEMPORARY_PATH" ||
  fail "could not download $asset from $release_base_url/$asset after $DOWNLOAD_ATTEMPTS attempts; install the Revyl CLI or set REVYL_BINARY to an executable Revyl CLI path"

checksum_matches "$TEMPORARY_PATH" "$expected_checksum" ||
  fail "checksum verification failed for $asset"

chmod 0700 "$TEMPORARY_PATH"
mv -f "$TEMPORARY_PATH" "$runtime_binary"
TEMPORARY_PATH=

checksum_matches "$runtime_binary" "$expected_checksum" ||
  fail "cached runtime verification failed after installation"

run_verified_runtime "$runtime_binary" "$@"
