#!/usr/bin/env bash
# =============================================================================
# FVM Installer
# =============================================================================
# v2.0.0 (2025-12)
#   - Install to ~/fvm/bin (no sudo required)
#   - FVM_INSTALL_DIR for custom location
#   - Auto-migrate from v1 (~/.fvm_flutter)
#
# v1.1.0
#   - Install to ~/.fvm_flutter/bin with /usr/local/bin symlink
#   - Auto-modify shell config
# =============================================================================
# ---- installer metadata ----
readonly INSTALLER_NAME="install_fvm.sh"
readonly INSTALLER_VERSION="2.0.0"

# ---- config ----
readonly REPO="btwld/fvm"
readonly OLD_SYSTEM_PATH="/usr/local/bin/fvm"
readonly OLD_USER_PATH="${HOME}/.fvm_flutter"

UNINSTALL_ONLY=0
REQUESTED_VERSION=""

# ---- helpers ----
resolve_install_base() {
  local base="${FVM_INSTALL_DIR:-}"
  if [ -z "$base" ]; then
    base="${HOME}/fvm"
  fi

  case "$base" in
    \~) base="$HOME" ;;
    \~/*) base="$HOME/${base#\~/}" ;;
  esac

  printf '%s\n' "$base"
}

INSTALL_BASE="$(resolve_install_base)"
readonly INSTALL_BASE
readonly BIN_DIR="${INSTALL_BASE}/bin"

validate_install_base() {
  local base="$1"
  local bin_dir="${base}/bin"

  if [ -z "$base" ] || [ "$base" = "/" ]; then
    echo "error: refusing to use unsafe install base: '${base:-<empty>}'" >&2
    echo "       Set FVM_INSTALL_DIR to a directory under your HOME (default: \$HOME/fvm)" >&2
    exit 1
  fi

  case "$base" in
    /*) ;;
    *)
      echo "error: FVM_INSTALL_DIR must be an absolute path (got: $base)" >&2
      exit 1
      ;;
  esac

  if [ "$base" = "$HOME" ]; then
    echo "error: refusing to use HOME as install base ($HOME). Use a subdirectory like \$HOME/fvm." >&2
    exit 1
  fi

  case "$base" in
    "$HOME"/*) ;;
    *)
      echo "error: refusing to install outside HOME: $base" >&2
      echo "       Use a directory under $HOME (e.g. $HOME/fvm), or use a package manager for system-wide installs." >&2
      exit 1
      ;;
  esac

  case "$bin_dir" in
    /bin|/usr/bin|/usr/local/bin|/sbin|/usr/sbin)
      echo "error: refusing to use unsafe bin directory: $bin_dir" >&2
      exit 1
      ;;
  esac
}

usage() {
  cat <<EOF
FVM Installer v${INSTALLER_VERSION} - User-Local Installation

USAGE:
  install.sh [FLAGS] [VERSION]

ARGUMENTS:
  VERSION               Version to install (e.g., 4.0.1 or v4.0.1)
                        If omitted, installs the latest version

ENVIRONMENT:
  FVM_INSTALL_DIR        Install base directory (default: \$HOME/fvm)

FLAGS:
  -h, --help            Show this help and exit
  -v, --version         Show installer version and exit
  -u, --uninstall       Remove FVM installation

EXAMPLES:
  # Install latest version
  curl -fsSL https://fvm.app/install.sh | bash

  # Install specific version
  ./install.sh 4.0.1

  # Uninstall
  ./install.sh --uninstall

AFTER INSTALLATION:
  Add FVM to your PATH by adding this line to your shell config:

    export PATH="$BIN_DIR:\$PATH"

  Then restart your shell or run: source ~/.bashrc

FOR MORE INFO:
  https://fvm.app/docs/getting_started/installation
EOF
}

print_installer_version() {
  printf '%s version %s\n' "$INSTALLER_NAME" "$INSTALLER_VERSION"
}

require() { command -v "$1" >/dev/null 2>&1 || { echo "error: $1 is required" >&2; exit 1; }; }

map_uname_arch() {
  local uname_arch="${1:-$(uname -m)}"

  case "$uname_arch" in
    x86_64|amd64) printf '%s\n' "x64" ;;
    aarch64|arm64) printf '%s\n' "arm64" ;;
    armv7l|armv7|armv6l|armv6|armhf) printf '%s\n' "arm" ;;
    riscv64) printf '%s\n' "riscv64" ;;
    *)
      echo "error: unsupported architecture: $uname_arch" >&2
      exit 1
      ;;
  esac
}

resolve_sysctl_cmd() {
  if [ -x "/usr/sbin/sysctl" ]; then
    printf '%s\n' "/usr/sbin/sysctl"
    return 0
  fi

  local _sysctl
  if _sysctl="$(command -v sysctl 2>/dev/null)"; then
    printf '%s\n' "$_sysctl"
    return 0
  fi

  return 1
}

detect_arch() {
  local uname_s="$1"
  local arm64_capability
  local sysctl_cmd

  if [ "$uname_s" = "Darwin" ]; then
    sysctl_cmd="$(resolve_sysctl_cmd || true)"
    if [ -n "$sysctl_cmd" ]; then
      arm64_capability="$("$sysctl_cmd" -n hw.optional.arm64 2>/dev/null || true)"
      case "$arm64_capability" in
        1) printf '%s\n' "arm64" ; return ;;
        0) printf '%s\n' "x64" ; return ;;
      esac
    fi
  fi

  map_uname_arch "$(uname -m)"
}

normalize_version() { printf '%s\n' "${1#v}"; }

get_latest_version() {
  # Follows redirect from /releases/latest -> .../tag/vX.Y.Z
  local url
  url="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/${REPO}/releases/latest")" || return 1
  normalize_version "${url##*/}"
}

print_path_instructions() {
  echo ""
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "✓ Installation complete!"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo ""
  echo "To use FVM, add it to your PATH:"
  echo ""
  echo "  # For bash (add to ~/.bashrc):"
  echo "  export PATH=\"$BIN_DIR:\$PATH\""
  echo ""
  echo "  # For zsh (add to ~/.zshrc):"
  echo "  export PATH=\"$BIN_DIR:\$PATH\""
  echo ""
  echo "  # For fish (run once):"
  echo "  fish_add_path \"$BIN_DIR\""
  echo ""
  echo "Then restart your shell or run:"
  echo "  source ~/.bashrc  # or ~/.zshrc"
  echo ""
  if is_ci; then
    echo "CI detected."
    echo "To use FVM in this same step, run:"
    echo "  export PATH=\"$BIN_DIR:\$PATH\""
    echo "To persist for later steps, use your CI's env file mechanism"
    echo "  (e.g., \$GITHUB_PATH on GitHub Actions, \$BASH_ENV on CircleCI)."
    echo ""
  else
    echo "Note: If you ran this via curl | bash, run the export command"
    echo "above in your current shell."
    echo ""
  fi
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}

is_ci() {
  if [ -n "${CI:-}" ] && [ "${CI}" != "false" ]; then
    return 0
  fi
  [ -n "${GITHUB_ACTIONS:-}" ] || [ -n "${GITLAB_CI:-}" ] || [ -n "${CIRCLECI:-}" ] || \
  [ -n "${TRAVIS:-}" ] || [ -n "${BUILDKITE:-}" ] || [ -n "${DRONE:-}" ] || \
  [ -n "${TF_BUILD:-}" ] || [ -n "${TEAMCITY_VERSION:-}" ] || \
  [ -n "${JENKINS_URL:-}" ] || [ -n "${APPVEYOR:-}" ]
}

migrate_from_v1() {
  local migrated=0

  # 1. Remove old system symlink (v1 or v2 --system)
  if [ -L "$OLD_SYSTEM_PATH" ]; then
    echo "" >&2
    echo "Detected old system installation at $OLD_SYSTEM_PATH" >&2

    # Try to remove without sudo first (|| true prevents set -e exit)
    rm -f "$OLD_SYSTEM_PATH" 2>/dev/null || true
    if [ ! -e "$OLD_SYSTEM_PATH" ] && [ ! -L "$OLD_SYSTEM_PATH" ]; then
      echo "✓ Removed old system symlink" >&2
      migrated=1
    else
      # Try with sudo if available
      if command -v sudo >/dev/null 2>&1; then
        sudo -n rm -f "$OLD_SYSTEM_PATH" 2>/dev/null || true
        if [ ! -e "$OLD_SYSTEM_PATH" ] && [ ! -L "$OLD_SYSTEM_PATH" ]; then
          echo "✓ Removed old system symlink (required sudo)" >&2
          migrated=1
        else
          echo "⚠ Could not remove $OLD_SYSTEM_PATH" >&2
          echo "  You may remove it manually: sudo rm $OLD_SYSTEM_PATH" >&2
        fi
      else
        echo "⚠ Could not remove $OLD_SYSTEM_PATH (need sudo)" >&2
        echo "  You may remove it manually: sudo rm $OLD_SYSTEM_PATH" >&2
      fi
    fi
  elif [ -e "$OLD_SYSTEM_PATH" ]; then
    echo "" >&2
    echo "⚠ Detected existing non-symlink file at $OLD_SYSTEM_PATH" >&2
    echo "  Not removing automatically. Remove it manually if it is an old FVM binary." >&2
  fi

  # 2. Remove old user directory (~/.fvm_flutter) - safe to nuke entirely
  if [ -d "$OLD_USER_PATH" ]; then
    echo "" >&2
    echo "Detected old installation at $OLD_USER_PATH" >&2

    rm -rf "$OLD_USER_PATH" 2>/dev/null || true
    if [ ! -d "$OLD_USER_PATH" ]; then
      echo "✓ Removed old user directory" >&2
      migrated=1
    else
      echo "⚠ Could not remove $OLD_USER_PATH" >&2
      echo "  You may remove it manually: rm -rf $OLD_USER_PATH" >&2
    fi
  fi

  # 3. Print PATH update notice if migrated
  if [ "$migrated" -eq 1 ]; then
    echo "" >&2
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
    echo "⚠ ACTION REQUIRED: Update your shell PATH" >&2
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
    echo "" >&2
    echo "  Old: export PATH=\"\$HOME/.fvm_flutter/bin:\$PATH\"" >&2
    echo "  New: export PATH=\"$BIN_DIR:\$PATH\"" >&2
    echo "" >&2
    echo "Your cached Flutter SDKs in $INSTALL_BASE/versions/ are preserved." >&2
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
  fi
}

do_uninstall() {
  local removed_any=0

  echo "Uninstalling FVM..." >&2
  echo "" >&2

  validate_install_base "$INSTALL_BASE"

  # 1. Remove the install bin directory only (NOT entire install base - preserve cached SDKs)
  # Note: This removes the entire $BIN_DIR directory.
  if [ -d "$BIN_DIR" ]; then
    rm -rf "$BIN_DIR" 2>/dev/null || true
    if [ ! -d "$BIN_DIR" ]; then
      echo "✓ Removed binary directory: $BIN_DIR" >&2
      removed_any=1
    else
      echo "⚠ Could not remove $BIN_DIR (check permissions)" >&2
    fi
  fi

  # 2. Remove old user directory (~/.fvm_flutter) - safe to nuke entirely
  if [ -d "$OLD_USER_PATH" ]; then
    rm -rf "$OLD_USER_PATH" 2>/dev/null || true
    if [ ! -d "$OLD_USER_PATH" ]; then
      echo "✓ Removed old directory: $OLD_USER_PATH" >&2
      removed_any=1
    else
      echo "⚠ Could not remove $OLD_USER_PATH" >&2
    fi
  fi

  # 3. Remove old system symlink (from v1/v2) — skip in CI (no legacy installs)
  if ! is_ci && [ -L "$OLD_SYSTEM_PATH" ]; then
    rm -f "$OLD_SYSTEM_PATH" 2>/dev/null || true
    if [ ! -e "$OLD_SYSTEM_PATH" ] && [ ! -L "$OLD_SYSTEM_PATH" ]; then
      echo "✓ Removed old system symlink: $OLD_SYSTEM_PATH" >&2
      removed_any=1
    else
      if command -v sudo >/dev/null 2>&1; then
        sudo -n rm -f "$OLD_SYSTEM_PATH" 2>/dev/null || true
        if [ ! -e "$OLD_SYSTEM_PATH" ] && [ ! -L "$OLD_SYSTEM_PATH" ]; then
          echo "✓ Removed old system symlink: $OLD_SYSTEM_PATH" >&2
          removed_any=1
        else
          echo "⚠ Could not remove $OLD_SYSTEM_PATH (may need sudo)" >&2
        fi
      else
        echo "⚠ Could not remove $OLD_SYSTEM_PATH (may need sudo)" >&2
      fi
    fi
  elif ! is_ci && [ -e "$OLD_SYSTEM_PATH" ]; then
    echo "⚠ Found existing non-symlink file at $OLD_SYSTEM_PATH; not removing automatically." >&2
  fi

  if [ "$removed_any" -eq 0 ]; then
    echo "No FVM installation found (ok)" >&2
  fi

  echo "" >&2
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
  echo "Uninstall complete." >&2
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
  echo "" >&2
  echo "Note: Cached Flutter SDKs remain in $INSTALL_BASE/versions/" >&2
  echo "      To remove them: rm -rf $INSTALL_BASE/" >&2
  echo "" >&2
  echo "Remove PATH entries from your shell config:" >&2
  echo "  - ~/.bashrc" >&2
  echo "  - ~/.zshrc" >&2
  echo "  - ~/.config/fish/config.fish" >&2
  echo "" >&2
  echo "Look for lines containing: $BIN_DIR" >&2

  exit 0
}

main() {
  set -euo pipefail
  umask 022

  # ---- arg parsing ----
  for arg in "$@"; do
    case "$arg" in
      -h|--help) usage; exit 0 ;;
      -v|--version) print_installer_version; exit 0 ;;
      -u|--uninstall) UNINSTALL_ONLY=1 ;;
      -*)
        echo "error: unknown option: $arg" >&2
        echo ""
        usage
        exit 1
        ;;
      *)
        if [ -n "$REQUESTED_VERSION" ]; then
          echo "error: multiple versions specified" >&2
          exit 1
        fi
        REQUESTED_VERSION="$arg"
        ;;
    esac
  done

  if [ -n "$REQUESTED_VERSION" ]; then
    if ! [[ "$REQUESTED_VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z._-]+)?$ ]]; then
      echo "error: invalid version format: $REQUESTED_VERSION (expected X.Y.Z or vX.Y.Z)" >&2
      exit 1
    fi
  fi

  # ---- handle uninstall ----
  if [ "$UNINSTALL_ONLY" -eq 1 ]; then
    do_uninstall
  fi

  # ---- root user handling ----
  if [ "${EUID:-$(id -u)}" -eq 0 ]; then
    echo "⚠ Warning: Running as root" >&2
    echo "  FVM will be installed to $BIN_DIR and likely won't be accessible to other users." >&2
    echo "  It is recommended that each user install FVM individually in their own home directory." >&2
    echo "" >&2
  fi

  validate_install_base "$INSTALL_BASE"

  # ---- prereqs ----
  require curl
  require tar
  [ -n "${BASH_VERSION:-}" ] || { echo "error: bash is required to run this installer" >&2; exit 1; }

  # ---- detect OS ----
  RAW_UNAME_S="$(uname -s)"
  readonly RAW_UNAME_S

  case "$RAW_UNAME_S" in
    Linux)  OS="linux" ;;
    Darwin) OS="macos" ;;
    *) echo "error: unsupported OS: $RAW_UNAME_S" >&2; exit 1 ;;
  esac
  readonly OS

  # ---- detect ARCH ----
  ARCH="$(detect_arch "$RAW_UNAME_S")"
  readonly ARCH

  # ---- detect libc (Linux only), musl suffix only for x64/arm64 ----
  LIBC_SUFFIX=""
  if [ "$OS" = "linux" ] && { [ "$ARCH" = "x64" ] || [ "$ARCH" = "arm64" ]; }; then
    # Detect glibc positively via getconf; otherwise check for musl
    if command -v getconf >/dev/null 2>&1 && getconf GNU_LIBC_VERSION >/dev/null 2>&1; then
      : # glibc detected
    elif command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then
      LIBC_SUFFIX="-musl"
      echo "" >&2
      echo "Note: Detected musl libc (Alpine Linux)." >&2
      echo "      Flutter SDK requires glibc. You may need: apk add gcompat" >&2
      echo "" >&2
    elif ls /lib/ld-musl-*.so.1 >/dev/null 2>&1 || ls /usr/lib/ld-musl-*.so.1 >/dev/null 2>&1; then
      LIBC_SUFFIX="-musl"
      echo "" >&2
      echo "Note: Detected musl libc (Alpine Linux)." >&2
      echo "      Flutter SDK requires glibc. You may need: apk add gcompat" >&2
      echo "" >&2
    fi
  fi
  readonly LIBC_SUFFIX

  # ---- resolve version ----
  if [ -n "$REQUESTED_VERSION" ]; then
    VERSION="$(normalize_version "$REQUESTED_VERSION")"
  else
    echo "Fetching latest FVM version..." >&2
    VERSION="$(get_latest_version)" || { echo "error: failed to determine latest version" >&2; exit 1; }
  fi

  echo "Installing FVM ${VERSION} for ${OS}-${ARCH}${LIBC_SUFFIX}..." >&2

  # ---- construct asset URL and validate existence, with musl->glibc fallback ----
  TARBALL="fvm-${VERSION}-${OS}-${ARCH}${LIBC_SUFFIX}.tar.gz"
  URL="https://github.com/${REPO}/releases/download/${VERSION}/${TARBALL}"

  if ! curl -fsSLI -o /dev/null "$URL"; then
    if [ -n "$LIBC_SUFFIX" ]; then
      ALT_URL="https://github.com/${REPO}/releases/download/${VERSION}/fvm-${VERSION}-${OS}-${ARCH}.tar.gz"
      if curl -fsSLI -o /dev/null "$ALT_URL"; then
        URL="$ALT_URL"
        TARBALL="fvm-${VERSION}-${OS}-${ARCH}.tar.gz"
        echo "Note: Using glibc variant (musl not available)" >&2
      else
        echo "error: no asset found for ${OS}/${ARCH} (tried musl and glibc variants)" >&2
        exit 1
      fi
    else
      echo "error: asset not found: $URL" >&2
      exit 1
    fi
  fi

  # ---- prep dirs and cleanup trap ----
  TMP_DIR=""  # Initialize for set -u (nounset)
  cleanup() { if [ -n "$TMP_DIR" ]; then rm -rf "$TMP_DIR" 2>/dev/null || true; fi; }
  trap cleanup EXIT

  TMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t 'fvm_install')" || {
    echo "error: failed to create temp directory" >&2
    exit 1
  }
  mkdir -p "$BIN_DIR"

  # ---- download ----
  ARCHIVE="${TMP_DIR}/${TARBALL}"
  echo "Downloading ${URL##*/}..." >&2
  curl -fsSL "$URL" -o "$ARCHIVE"

  # ---- validate archive ----
  if ! tar -tzf "$ARCHIVE" >/dev/null 2>&1; then
    echo "error: downloaded archive appears corrupted" >&2
    exit 1
  fi

  # ---- validate no path traversal ----
  if tar -tzf "$ARCHIVE" | grep -qE '^/|^\.\.$|^\.\./|/\.\.$|/\.\./'; then
    echo "error: archive contains unsafe paths (absolute or traversal)" >&2
    exit 1
  fi

  # ---- extract ----
  echo "Extracting..." >&2
  tar -xzf "$ARCHIVE" -C "$TMP_DIR"

  # ---- locate binary and copy contents per tarball structure ----
  if [ -d "${TMP_DIR}/fvm" ] && [ -f "${TMP_DIR}/fvm/fvm" ]; then
    cp -a "${TMP_DIR}/fvm/." "$BIN_DIR/"
  elif [ -f "${TMP_DIR}/fvm" ]; then
    cp -a "${TMP_DIR}/fvm" "${BIN_DIR}/fvm"
  else
    FOUND="$(find "$TMP_DIR" -type f -name 'fvm' 2>/dev/null | head -n1 || true)"
    [ -n "$FOUND" ] || { echo "error: fvm binary not found in archive" >&2; exit 1; }
    cp -a "$FOUND" "${BIN_DIR}/fvm"
  fi
  chmod +x "${BIN_DIR}/fvm"

  # ---- migrate from v1/v2 ----
  migrate_from_v1

  # ---- verify and report ----
  echo ""
  echo "Installed to: ${BIN_DIR}/fvm"

  if "${BIN_DIR}/fvm" --version >/dev/null 2>&1; then
    echo "FVM version: ${VERSION}"
    print_path_instructions
  else
    echo ""
    echo "⚠ Binary installed but cannot execute (missing libraries)."
    echo "  On Alpine Linux: apk add gcompat"
    echo "  Then verify: ${BIN_DIR}/fvm --version"
    echo ""
    echo "  PATH: export PATH=\"$BIN_DIR:\$PATH\""
    if is_ci; then
      exit 1
    fi
  fi
}

if ! (return 0 2>/dev/null); then
  main "$@"
fi
