#!/bin/bash
set -euo pipefail

RED='\033[0;31m'
MUTED='\033[0;2m'
NC='\033[0m'

# Sentry error telemetry — fire-and-forget error reporting via envelope API.
# Uses the CLI's public write-only DSN. No PII collected.
# Opt-out: SENTRY_CLI_NO_TELEMETRY=1
SENTRY_DSN_KEY="1188a86f3f8168f089450587b00bca66"
SENTRY_INGEST="https://o1.ingest.us.sentry.io"
SENTRY_PROJECT_ID="4510776311808000"

# Generate a UUID for the event. Tries /proc, uuidgen, then awk fallback.
gen_uuid() {
  if [[ -r /proc/sys/kernel/random/uuid ]]; then
    cat /proc/sys/kernel/random/uuid
  elif command -v uuidgen >/dev/null 2>&1; then
    uuidgen | tr '[:upper:]' '[:lower:]'
  else
    awk 'BEGIN{srand();for(i=1;i<=32;i++)printf "%c",substr("0123456789abcdef",int(rand()*16)+1,1);print ""}'
  fi
}

# Send an error event to Sentry. Runs in a subshell in the background so it
# never blocks installation or propagates failures.
# Usage: report_error "message" "step-name"
report_error() {
  # Respect the same opt-out as the CLI binary
  [[ "${SENTRY_CLI_NO_TELEMETRY:-}" == "1" ]] && return 0

  (
    set +e  # Telemetry must never fail the script
    local msg="${1:-unknown error}"
    local step="${2:-unknown}"
    local event_id
    event_id=$(gen_uuid | tr -d '-')
    local timestamp
    timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")

    # Escape a value for safe JSON string interpolation
    _esc() { printf '%s' "$1" | sed 's/\\/\\\\/g;s/"/\\"/g' | tr '\n' ' '; }

    local json_msg; json_msg=$(_esc "$msg")
    local json_step; json_step=$(_esc "$step")
    local json_channel; json_channel=$(_esc "${requested_version:-stable}")
    local json_version; json_version=$(_esc "${version:-unknown}")

    local envelope
    envelope=$(printf '%s\n%s\n%s' \
      "{\"event_id\":\"${event_id}\",\"dsn\":\"https://${SENTRY_DSN_KEY}@o1.ingest.us.sentry.io/${SENTRY_PROJECT_ID}\"}" \
      '{"type":"event"}' \
      "{\"event_id\":\"${event_id}\",\"timestamp\":\"${timestamp}\",\"platform\":\"other\",\"level\":\"error\",\"logger\":\"install\",\"server_name\":\"install-script\",\"message\":{\"formatted\":\"${json_msg}\"},\"tags\":{\"os\":\"${os:-unknown}\",\"arch\":\"${arch:-unknown}\",\"libc\":\"${libc_variant:-glibc}\",\"channel\":\"${json_channel}\",\"step\":\"${json_step}\",\"install.version\":\"${json_version}\"},\"contexts\":{\"runtime\":{\"name\":\"bash\",\"version\":\"${BASH_VERSION:-unknown}\"}}}")

    curl -sf --max-time 2 \
      -H "Content-Type: application/x-sentry-envelope" \
      -H "X-Sentry-Auth: Sentry sentry_key=${SENTRY_DSN_KEY},sentry_version=7" \
      -d "$envelope" \
      "${SENTRY_INGEST}/api/${SENTRY_PROJECT_ID}/envelope/" \
      >/dev/null 2>&1
  ) &
}

# Print error message, report to Sentry, and exit.
# Usage: die "message" "step-name"
die() {
  echo -e "${RED}$1${NC}" >&2
  report_error "$1" "${2:-unknown}"
  wait 2>/dev/null || true  # Let the background curl finish; ignore its exit status
  exit 1
}

# Catch unexpected failures from set -e / pipefail (e.g., gunzip failing)
trap 'die "Unexpected failure at line $LINENO" "trap"' ERR

usage() {
  cat <<EOF
Sentry CLI Installer

Usage: install [options]

Options:
    -h, --help              Display this help message
    -v, --version <version> Install a specific version (e.g., 0.2.0) or "nightly"
        --no-modify-path    Don't modify shell config files (.zshrc, .bashrc, etc.)
        --no-completions    Don't install shell completions
        --no-agent-skills   Don't install agent skills

Environment Variables:
    SENTRY_INSTALL_DIR      Override the installation directory
    SENTRY_VERSION          Install a specific version (e.g., 0.19.0, nightly)
    SENTRY_INIT             Set to 1 to run \`sentry init\` after installing

Examples:
    curl -fsSL https://cli.sentry.dev/install | bash
    curl -fsSL https://cli.sentry.dev/install | SENTRY_INIT=1 bash
    curl -fsSL https://cli.sentry.dev/install | bash -s -- --version nightly
    curl -fsSL https://cli.sentry.dev/install | bash -s -- --version 0.19.0
    SENTRY_VERSION=nightly curl -fsSL https://cli.sentry.dev/install | bash
    SENTRY_INSTALL_DIR=~/.local/bin curl -fsSL https://cli.sentry.dev/install | bash
EOF
}

requested_version="${SENTRY_VERSION:-}"
no_modify_path=false
no_completions=false
no_agent_skills=false
while [[ $# -gt 0 ]]; do
  case "$1" in
    -h|--help) usage; exit 0 ;;
    -v|--version)
      if [[ -n "${2:-}" ]]; then
        requested_version="$2"
        shift 2
      else
        die "Error: --version requires a version argument" "args"
      fi
      ;;
    --no-modify-path)
      no_modify_path=true
      shift
      ;;
    --no-completions)
      no_completions=true
      shift
      ;;
    --no-agent-skills)
      no_agent_skills=true
      shift
      ;;
    *) shift ;;
  esac
done

# Detect OS
case "$(uname -s)" in
  Darwin*) os="darwin" ;;
  Linux*) os="linux" ;;
  MINGW*|MSYS*|CYGWIN*) os="windows" ;;
  *) die "Unsupported OS: $(uname -s)" "detect-os" ;;
esac

# Detect architecture
arch=$(uname -m)
case "$arch" in
  x86_64) arch="x64" ;;
  aarch64|arm64) arch="arm64" ;;
  *) die "Unsupported architecture: $arch" "detect-arch" ;;
esac

# Detect C library variant (musl vs glibc) on Linux.
# musl-based systems (Alpine, Void, etc.) need a different binary.
libc_suffix=""
if [[ "$os" == "linux" ]]; then
  detect_musl() {
    # Heuristic 1: Check for musl dynamic linker
    local musl_arch
    case "$arch" in
      x64) musl_arch="x86_64" ;;
      arm64) musl_arch="aarch64" ;;
    esac
    [ -f "/lib/ld-musl-${musl_arch}.so.1" ] && return 0

    # Heuristic 2: ldd --version outputs "musl libc" on musl systems
    if command -v ldd >/dev/null 2>&1; then
      ldd --version 2>&1 | grep -qi musl && return 0
    fi

    return 1
  }

  if detect_musl; then
    libc_suffix="-musl"
    libc_variant="musl"

    # Bun musl binaries dynamically link libstdc++ and libgcc_s.
    # Auto-install them on Alpine when running as root (typical in Docker).
    # When not root, warn with install instructions.
    if ! ldconfig -p 2>/dev/null | grep -q libstdc++ && ! [ -f /usr/lib/libstdc++.so.6 ]; then
      if command -v apk >/dev/null 2>&1; then
        if [ "$(id -u)" = "0" ]; then
          echo -e "${MUTED}Installing required C++ runtime libraries...${NC}"
          apk add --no-cache libstdc++ libgcc >/dev/null 2>&1 \
            || die "Failed to install libstdc++ and libgcc (required for musl binary)" "deps"
        else
          echo -e "${RED}Missing required libraries: libstdc++ libgcc${NC}" >&2
          echo -e "${RED}Run: apk add libstdc++ libgcc${NC}" >&2
          die "Cannot install dependencies without root. Run as root or install manually." "deps"
        fi
      fi
    fi
  fi
fi

# Validate supported combinations
suffix=""
if [[ "$os" == "windows" ]]; then
  suffix=".exe"
  if [[ "$arch" != "x64" ]]; then
    die "Unsupported: windows-$arch (only windows-x64 is supported)" "detect-arch"
  fi
fi

# Download binary to a temp location
tmpdir="${TMPDIR:-${TMP:-${TEMP:-/tmp}}}"
tmp_binary="${tmpdir}/sentry-install-$$${suffix}"
version=""

# Clean up temp binary on failure (setup handles cleanup on success)
trap 'rm -f "$tmp_binary"' EXIT

if [[ "$requested_version" == "nightly" ]]; then
  # Nightly build: download from GHCR via OCI blob protocol.
  # No jq needed — parse JSON with awk.
  # ghcr.io blob downloads redirect to Azure Blob Storage. curl -L would
  # forward the Authorization header to Azure, which returns 404. Instead,
  # extract the redirect URL and follow it without the auth header.

  echo -e "${MUTED}Fetching nightly build from GHCR...${NC}"

  # Step 1: Get anonymous pull token
  GHCR_TOKEN=$(curl -sf \
    "https://ghcr.io/token?scope=repository:getsentry/cli:pull" \
    | awk -F'"' '{for(i=1;i<=NF;i++) if($i=="token"){print $(i+2);exit}}')
  if [[ -z "$GHCR_TOKEN" ]]; then
    die "Failed to get GHCR token" "ghcr-token"
  fi

  # Step 2: Fetch the OCI manifest for the :nightly tag
  MANIFEST=$(curl -sf \
    -H "Authorization: Bearer $GHCR_TOKEN" \
    -H "Accept: application/vnd.oci.image.manifest.v1+json" \
    "https://ghcr.io/v2/getsentry/cli/manifests/nightly")
  if [[ -z "$MANIFEST" ]]; then
    die "Failed to fetch nightly manifest from GHCR" "ghcr-manifest"
  fi

  # Step 3: Extract version from manifest annotation
  version=$(echo "$MANIFEST" \
    | awk -F'"' '{for(i=1;i<=NF;i++) if($i=="version"){print $(i+2);exit}}')
  if [[ -z "$version" ]]; then
    die "Failed to extract version from nightly manifest" "ghcr-version"
  fi

  echo -e "${MUTED}Installing nightly sentry ${version}...${NC}"

  # Step 4: Find the blob digest for this platform's .gz file.
  # Each OCI layer has "digest" before "org.opencontainers.image.title".
  # Track the last-seen digest and print it when the target filename matches.
  # This avoids sed newline replacement which differs between GNU and BSD.
  gz_filename="sentry-${os}-${arch}${libc_suffix}${suffix}.gz"
  digest=$(echo "$MANIFEST" \
    | awk -F'"' -v target="$gz_filename" '{
        for(i=1;i<=NF;i++){
          if($i=="digest") d=$(i+2)
          if($i=="org.opencontainers.image.title"&&$(i+2)==target){print d;exit}
        }
      }')
  if [[ -z "$digest" ]]; then
    die "No nightly build found for ${gz_filename}" "ghcr-digest"
  fi

  # Step 5: Get the redirect URL from the blob endpoint (don't use -L: auth
  # header must NOT be forwarded to the Azure Blob Storage redirect target)
  redir_url=$(curl -s -w '\n%{redirect_url}' -o /dev/null \
    -H "Authorization: Bearer $GHCR_TOKEN" \
    "https://ghcr.io/v2/getsentry/cli/blobs/${digest}" | tail -1)
  if [[ -z "$redir_url" ]]; then
    die "Failed to get blob redirect URL from GHCR" "ghcr-redirect"
  fi

  # Step 6: Download the .gz blob and decompress (without auth header)
  curl -sf "$redir_url" | gunzip > "$tmp_binary"

else
  # Stable build: resolve version and download from GitHub Releases.

  if [[ -z "$requested_version" ]]; then
    version=$(curl -fsSL https://api.github.com/repos/getsentry/cli/releases/latest \
      | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
    if [[ -z "$version" ]]; then
      die "Failed to fetch latest version" "gh-version"
    fi
  else
    version="$requested_version"
  fi

  # Strip leading 'v' if present (releases use version without 'v' prefix)
  version="${version#v}"
  filename="sentry-${os}-${arch}${libc_suffix}${suffix}"
  url="https://github.com/getsentry/cli/releases/download/${version}/${filename}"

  echo -e "${MUTED}Downloading sentry v${version}...${NC}"

  # Try gzip-compressed download first (~60% smaller, ~37 MB vs ~99 MB).
  # gunzip is POSIX and available on all Unix systems.
  # Falls back to raw binary if the .gz asset doesn't exist yet.
  if curl -fsSL "${url}.gz" 2>/dev/null | gunzip > "$tmp_binary" 2>/dev/null; then
    : # Compressed download succeeded
  else
    curl -fsSL --progress-bar "$url" -o "$tmp_binary"
  fi
fi

chmod +x "$tmp_binary"

# Delegate installation and configuration to the binary itself.
# setup --install handles: directory selection, binary placement, PATH,
# completions, agent skills, and the welcome message.
# --channel persists the release channel so future `sentry cli upgrade`
# calls track the same channel without requiring a flag.
if [[ "$requested_version" == "nightly" ]]; then
  channel="nightly"
else
  channel="stable"
fi
setup_args="--install --method curl --channel $channel"
if [[ "$no_modify_path" == "true" ]]; then
  setup_args="$setup_args --no-modify-path"
fi
if [[ "$no_completions" == "true" ]]; then
  setup_args="$setup_args --no-completions"
fi
if [[ "$no_agent_skills" == "true" ]]; then
  setup_args="$setup_args --no-agent-skills"
fi

# Remove trap — setup will handle temp cleanup on success
trap - EXIT

# shellcheck disable=SC2086
"$tmp_binary" cli setup $setup_args

# Optionally launch the setup wizard after install.
# Reconnect stdin to the real terminal so the wizard can prompt
# interactively — when piped (curl | bash), stdin is the pipe.
if [[ "${SENTRY_INIT:-}" == "1" ]]; then
  sentry_bin=""
  for dir in "${SENTRY_INSTALL_DIR:-}" "$HOME/.local/bin" "$HOME/bin" "$HOME/.sentry/bin"; do
    [[ -z "$dir" ]] && continue
    if [[ -x "${dir}/sentry" ]]; then
      sentry_bin="${dir}/sentry"
      break
    fi
  done
  if [[ -z "$sentry_bin" ]]; then
    die "Cannot find installed sentry binary" "init"
  fi
  if [[ ! -e /dev/tty ]]; then
    die "SENTRY_INIT=1 requires an interactive terminal (/dev/tty not available)" "init"
  fi
  exec "$sentry_bin" init </dev/tty
fi
