#!/bin/sh

set -eu

SPIKE_VERSION=${SPIKE_VERSION:-latest}
SPIKE_INSTALL_BASE_URL=${SPIKE_INSTALL_BASE_URL:-https://spikeai.de/spike/cli}
SPIKE_RELEASE_BASE_URL=${SPIKE_RELEASE_BASE_URL:-${SPIKE_INSTALL_BASE_URL%/}/releases}
SPIKE_HOME_DIR=${SPIKE_HOME:-$HOME/.spike}
SPIKE_INSTALL_ROOT=${SPIKE_INSTALL_ROOT:-$SPIKE_HOME_DIR/local}
SPIKE_BIN_DIR=${SPIKE_BIN_DIR:-$HOME/.local/bin}
SPIKE_ALLOW_DOWNGRADE=${SPIKE_ALLOW_DOWNGRADE:-false}

releases_dir="$SPIKE_INSTALL_ROOT/releases"
current_file="$SPIKE_INSTALL_ROOT/current"
bin_dir="$SPIKE_INSTALL_ROOT/bin"
visible_launcher="$SPIKE_BIN_DIR/spike"
lock_dir="$SPIKE_INSTALL_ROOT/install.lock.d"
tmp_dir=""
path_action="already"
path_profile=""
visible_launcher_state="pending"

step() {
  printf '==> %s\n' "$1"
}

die() {
  printf 'Spike Agent install failed: %s\n' "$1" >&2
  exit 1
}

cleanup() {
  if [ -n "$tmp_dir" ] && [ -d "$tmp_dir" ]; then
    rm -rf "$tmp_dir"
  fi
  if [ -d "$lock_dir" ]; then
    rm -rf "$lock_dir"
  fi
}

download() {
  url=$1
  output=$2
  if command -v curl >/dev/null 2>&1; then
    curl -fsSL --connect-timeout 15 --max-time 300 "$url" -o "$output"
  elif command -v wget >/dev/null 2>&1; then
    wget -q -T 300 -O "$output" "$url"
  else
    die "curl or wget is required."
  fi
}

sha256_file() {
  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
    die "sha256sum or shasum is required."
  fi
}

normalize_target() {
  os=$(uname -s)
  arch=$(uname -m)
  case "$os:$arch" in
    Darwin:arm64 | Darwin:aarch64) printf 'aarch64-apple-darwin\n' ;;
    Darwin:x86_64) printf 'x86_64-apple-darwin\n' ;;
    Linux:aarch64 | Linux:arm64) printf 'aarch64-unknown-linux-musl\n' ;;
    Linux:x86_64 | Linux:amd64) printf 'x86_64-unknown-linux-musl\n' ;;
    *) die "unsupported platform: $os/$arch" ;;
  esac
}

validate_version() {
  printf '%s\n' "$1" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$' ||
    die "invalid release version: $1"
}

# Prints -1, 0 or 1 when A is older than, equal to or newer than B.
compare_semver() {
  awk -v a="$1" -v b="$2" '
    function parse(v, out, dash, core, count, i) {
      sub(/^v/, "", v)
      sub(/\+.*/, "", v)
      dash = index(v, "-")
      if (dash) {
        core = substr(v, 1, dash - 1)
        out[4] = substr(v, dash + 1)
      } else {
        core = v
        out[4] = ""
      }
      count = split(core, parts, ".")
      for (i = 1; i <= 3; i++) out[i] = i <= count ? parts[i] + 0 : 0
    }
    function precompare(left, right, lc, rc, i, ln, rn) {
      if (left == right) return 0
      if (left == "") return 1
      if (right == "") return -1
      lc = split(left, lp, ".")
      rc = split(right, rp, ".")
      for (i = 1; i <= lc || i <= rc; i++) {
        if (i > lc) return -1
        if (i > rc) return 1
        ln = lp[i] ~ /^[0-9]+$/
        rn = rp[i] ~ /^[0-9]+$/
        if (ln && rn && lp[i] + 0 != rp[i] + 0) return (lp[i] + 0 < rp[i] + 0 ? -1 : 1)
        if (ln != rn) return (ln ? -1 : 1)
        if (lp[i] != rp[i]) return (lp[i] < rp[i] ? -1 : 1)
      }
      return 0
    }
    BEGIN {
      parse(a, av)
      parse(b, bv)
      for (i = 1; i <= 3; i++) {
        if (av[i] != bv[i]) {
          print (av[i] < bv[i] ? -1 : 1)
          exit
        }
      }
      print precompare(av[4], bv[4])
    }
  '
}

pick_profile() {
  case "$(uname -s):${SHELL:-}" in
    Darwin:*/zsh) printf '%s\n' "$HOME/.zprofile" ;;
    Darwin:*/bash) printf '%s\n' "$HOME/.bash_profile" ;;
    Linux:*/zsh) printf '%s\n' "$HOME/.zshrc" ;;
    Linux:*/bash) printf '%s\n' "$HOME/.bashrc" ;;
    *) printf '%s\n' "$HOME/.profile" ;;
  esac
}

append_path_block() {
  profile=$1
  begin_marker=$2
  end_marker=$3
  path_line=$4
  {
    printf '\n%s\n' "$begin_marker"
    printf '%s\n' "$path_line"
    printf '%s\n' "$end_marker"
  } >> "$profile"
}

rewrite_path_block() {
  profile=$1
  begin_marker=$2
  end_marker=$3
  path_line=$4
  tmp_profile="$tmp_dir/profile.$$.tmp"

  awk -v begin="$begin_marker" -v end="$end_marker" -v line="$path_line" '
    BEGIN { in_block = 0; replaced = 0 }
    $0 == begin {
      if (!replaced) {
        print begin
        print line
        print end
        replaced = 1
      }
      in_block = 1
      next
    }
    in_block {
      if ($0 == end) in_block = 0
      next
    }
    { print }
    END { if (in_block != 0) exit 1 }
  ' "$profile" > "$tmp_profile"
  mv "$tmp_profile" "$profile"
}

configure_visible_path() {
  path_action="already"
  path_profile=""
  case ":$PATH:" in
    *":$SPIKE_BIN_DIR:"*) return ;;
  esac

  profile=$(pick_profile)
  path_profile=$profile
  begin_marker="# >>> Spike Agent installer >>>"
  end_marker="# <<< Spike Agent installer <<<"
  path_line="export PATH=\"$SPIKE_BIN_DIR:\$PATH\""

  if [ -f "$profile" ] && grep -F "$begin_marker" "$profile" >/dev/null 2>&1; then
    if grep -F "$path_line" "$profile" >/dev/null 2>&1; then
      path_action="configured"
      return
    fi
    if grep -F "$end_marker" "$profile" >/dev/null 2>&1; then
      rewrite_path_block "$profile" "$begin_marker" "$end_marker" "$path_line"
      path_action="updated"
      return
    fi
  fi

  append_path_block "$profile" "$begin_marker" "$end_marker" "$path_line"
  path_action="added"
}

install_visible_launcher() {
  managed_launcher="$bin_dir/spike"
  visible_launcher_state="installed"
  if [ "$visible_launcher" = "$managed_launcher" ]; then
    return
  fi

  mkdir -p "$SPIKE_BIN_DIR"
  if [ -e "$visible_launcher" ] || [ -L "$visible_launcher" ]; then
    if [ ! -L "$visible_launcher" ] ||
      [ "$(readlink "$visible_launcher" 2>/dev/null || true)" != "$managed_launcher" ]; then
      visible_launcher_state="conflict"
      return
    fi
  fi

  tmp_launcher="$SPIKE_BIN_DIR/.spike.new.$$"
  rm -f "$tmp_launcher"
  ln -s "$managed_launcher" "$tmp_launcher"
  mv -f "$tmp_launcher" "$visible_launcher"
}

case "$SPIKE_RELEASE_BASE_URL" in
  https://* | http://localhost:* | http://127.0.0.1:*) ;;
  *) die "SPIKE_RELEASE_BASE_URL must use HTTPS (localhost HTTP is allowed for tests)." ;;
esac

mkdir -p "$SPIKE_INSTALL_ROOT" "$releases_dir" "$bin_dir"
chmod 700 "$SPIKE_INSTALL_ROOT" "$releases_dir"
[ ! -e "$current_file" ] || [ -f "$current_file" ] ||
  die "$current_file exists and is not a managed version file."

if ! mkdir "$lock_dir" 2>/dev/null; then
  if [ -f "$lock_dir/pid" ]; then
    lock_pid=$(cat "$lock_dir/pid" 2>/dev/null || true)
    case "$lock_pid" in
      '' | *[!0-9]*) ;;
      *)
        if kill -0 "$lock_pid" 2>/dev/null; then
          die "another installer is running (pid $lock_pid)."
        fi
        ;;
    esac
  fi
  rm -rf "$lock_dir"
  mkdir "$lock_dir" || die "could not acquire the install lock."
fi
printf '%s\n' "$$" > "$lock_dir/pid"
trap cleanup EXIT HUP INT TERM

tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/spike-install.XXXXXX")

if [ "$SPIKE_VERSION" = latest ]; then
  step "Resolving the latest Spike Agent release"
  download "${SPIKE_RELEASE_BASE_URL%/}/latest/VERSION" "$tmp_dir/VERSION"
  SPIKE_VERSION=$(tr -d '\r\n ' < "$tmp_dir/VERSION")
fi
case "$SPIKE_VERSION" in
  rust-v*) SPIKE_VERSION=${SPIKE_VERSION#rust-v} ;;
  v*) SPIKE_VERSION=${SPIKE_VERSION#v} ;;
esac
validate_version "$SPIKE_VERSION"

if [ -f "$current_file" ]; then
  current_version=$(tr -d '\r\n ' < "$current_file")
  validate_version "$current_version"
  if [ "$(compare_semver "$SPIKE_VERSION" "$current_version")" -lt 0 ]; then
    case "$SPIKE_ALLOW_DOWNGRADE" in
      1 | true | yes) ;;
      *) die "refusing to downgrade from $current_version to $SPIKE_VERSION; set SPIKE_ALLOW_DOWNGRADE=true to override." ;;
    esac
  fi
fi

target=$(normalize_target)
asset="spike-agent-package-${target}.tar.gz"
release_url="${SPIKE_RELEASE_BASE_URL%/}/${SPIKE_VERSION}"
manifest="$tmp_dir/spike-agent-package_SHA256SUMS"
archive="$tmp_dir/$asset"

step "Downloading Spike Agent $SPIKE_VERSION for $target"
download "$release_url/spike-agent-package_SHA256SUMS" "$manifest"
expected=$(awk -v asset="$asset" '$2 == asset { print tolower($1); found = 1; exit } END { if (!found) exit 1 }' "$manifest") ||
  die "the release manifest does not contain $asset."
printf '%s\n' "$expected" | grep -Eq '^[0-9a-f]{64}$' ||
  die "the release manifest contains an invalid checksum."

download "$release_url/$asset" "$archive"
actual=$(sha256_file "$archive" | tr 'A-F' 'a-f')
[ "$actual" = "$expected" ] || die "checksum mismatch for $asset."

if ! tar -tzf "$archive" | awk '
  /^\// { exit 1 }
  /(^|\/)\.\.($|\/)/ { exit 1 }
  END { if (NR == 0) exit 1 }
'; then
  die "the release archive contains an unsafe path."
fi

extract_dir="$tmp_dir/package"
mkdir "$extract_dir"
tar -xzf "$archive" -C "$extract_dir"
[ -x "$extract_dir/bin/spike" ] || die "the release archive has no executable bin/spike."
[ -f "$extract_dir/codex-package.json" ] || die "the release archive has no package metadata."
printf '%s\n' "$SPIKE_VERSION" > "$extract_dir/VERSION"
printf '%s\n' "$expected" > "$extract_dir/PACKAGE_SHA256"

release_dir="$releases_dir/$SPIKE_VERSION"
if [ ! -d "$release_dir" ]; then
  release_stage="$releases_dir/.${SPIKE_VERSION}.new.$$"
  mv "$extract_dir" "$release_stage"
  mv "$release_stage" "$release_dir"
fi
[ -x "$release_dir/bin/spike" ] || die "the existing $SPIKE_VERSION install is incomplete."
[ -f "$release_dir/codex-package.json" ] || die "the existing $SPIKE_VERSION install has no package metadata."
[ "$(tr -d '\r\n ' < "$release_dir/PACKAGE_SHA256" 2>/dev/null || true)" = "$expected" ] ||
  die "the existing $SPIKE_VERSION install does not match the release checksum."

new_current="$SPIKE_INSTALL_ROOT/.current.new.$$"
printf '%s\n' "$SPIKE_VERSION" > "$new_current"
mv -f "$new_current" "$current_file"

new_launcher="$bin_dir/.spike.new.$$"
cat > "$new_launcher" <<'EOF'
#!/bin/sh
set -eu
spike_launcher=$0
while [ -L "$spike_launcher" ]; do
  spike_launcher_dir=$(CDPATH= cd -- "$(dirname -- "$spike_launcher")" && pwd)
  spike_link=$(readlink "$spike_launcher")
  case "$spike_link" in
    /*) spike_launcher=$spike_link ;;
    *) spike_launcher=$spike_launcher_dir/$spike_link ;;
  esac
done
spike_bin_dir=$(CDPATH= cd -- "$(dirname -- "$spike_launcher")" && pwd)
spike_root=$(dirname "$spike_bin_dir")
spike_version=$(tr -d '\r\n ' < "$spike_root/current")
case "$spike_version" in
  *[!0-9A-Za-z.-]* | '' | .* | *..*)
    printf 'Spike Agent install has an invalid active version.\n' >&2
    exit 1
    ;;
esac
exec "$spike_root/releases/$spike_version/bin/spike" "$@"
EOF
chmod 755 "$new_launcher"
mv -f "$new_launcher" "$bin_dir/spike"

install_visible_launcher
if [ "$visible_launcher_state" = installed ]; then
  configure_visible_path
  "$visible_launcher" --version >/dev/null
fi

step "Installed Spike Agent $SPIKE_VERSION"
printf 'Binary: %s\n' "$bin_dir/spike"
if [ "$visible_launcher_state" = conflict ]; then
  printf 'Warning: %s already exists and was not replaced.\n' "$visible_launcher" >&2
  printf 'Run this managed install directly: %s\n' "$bin_dir/spike"
else
  printf 'Command: %s\n' "$visible_launcher"
  case "$path_action" in
    added | updated | configured)
      printf 'PATH configured in %s.\n' "$path_profile"
      printf 'Current terminal: export PATH="%s:$PATH"\n' "$SPIKE_BIN_DIR"
      ;;
    *) printf 'Run now: spike\n' ;;
  esac
fi
printf 'Restart any running Spike Agent sessions to use this version.\n'
