#!/bin/bash

set -euo pipefail
shopt -s extglob

check_arch() {
  ARCH=$(uname -m)
  if [[ "$ARCH" != @("x86_64"|"aarch64") ]]; then
    echo "Your architecture '$ARCH' is not supported by $SCYLLA_PRODUCT, for supported OS and arch please refer to https://docs.scylladb.com/getting-started/os-support"
    exit 1
  fi
}

check_os() {
  OS=$(uname -s)

  case "$OS" in
    Linux)   ;;
    *)        echo -e "Your operating system $OS is not supported by this installer.\n\nPlease consider using Docker to run Scylla on this machine:\n\nhttps://hub.docker.com/r/scylladb/scylla" && exit 1 ;;
  esac
}

echo_msg() {
  if [ $DRY_RUN -eq 0 ]; then
    echo $*
  fi
}

run_cmd() {
  CMD=("$@")
  if [ $DRY_RUN -eq 0 ]; then
   eval "${CMD[@]}"
  else
    echo "${CMD[@]}"
  fi
}

check_product() {
  case "$SCYLLA_PRODUCT" in
    "scylla")
      VERSION_CHECK_SYSTEM="scylla"
      BRANCH_WORD="branch-"
      ;;
    "scylla-enterprise")
      VERSION_CHECK_SYSTEM="enterprise"
      BRANCH_WORD="enterprise-"
      ;;
    *)
      echo "The product '$SCYLLA_PRODUCT' is not supported by this installer."
      exit 1
      ;;
  esac
}

is_supported_version() {
  if [[ -n "$SCYLLA_VERSION" ]] && [[ $SCYLLA_VERSION != *"nightly"* ]]; then
    if [[ "$SCYLLA_VERSION" != @(202[3-9].*|5.[4-9]*|[6-9].*) ]]; then
      supported_versions_message
      exit 1
    fi
  fi
}

supported_versions_message() {
  echo "The specified scylla-version '$SCYLLA_VERSION' has reached End of Life (EOL) or not available.
  • For OSS supported ScyllaDB versions please refer to https://docs.scylladb.com/stable/getting-started/os-support
  • For Enterprise supported ScyllaDB versions please refer to https://enterprise.docs.scylladb.com/stable/getting-started/os-support"
}

is_rc_version() {
  if [[ $SCYLLA_VERSION == *.rc* ]]; then
    # replace the 2nd '.' with '~' for RC versions, ex. x.y.rc0 -> x.y~rc0
    SCYLLA_VERSION=$(echo $SCYLLA_VERSION | sed 's/\(.*\)\.)*/\1~/')
  elif [[ $SCYLLA_VERSION == *-rc* ]]; then
    # replace '-' with '~' for RC versions, ex. x.y.0-rc1 -> x.y.0~rc1
    SCYLLA_VERSION=$(echo $SCYLLA_VERSION | sed 's/-/~/')
  fi
}

query_default_version() {
  DEFAULT_SCYLLA_VERSION_RAW=$(curl -s --retry 3 --max-time 5 "https://repositories.scylladb.com/scylla/check_version?system=$VERSION_CHECK_SYSTEM")

  DEFAULT_SCYLLA_VERSION=$(echo $DEFAULT_SCYLLA_VERSION_RAW | sed -e "s/.*version\":\"\(.*\)\".*/\1/g")
}

get_latest_patch_for_series() {
  local series=$1
  
  # Try the version-specific API endpoint first for accurate patch versions
  local series_info=$(curl -s --retry 3 --max-time 5 "https://repositories.scylladb.com/scylla/check_version?version=$series" 2>/dev/null)

  if [ $? -eq 0 ] && [ -n "$series_info" ]; then
    local latest_patch=$(echo "$series_info" | sed -e "s/.*latest_patch_version\":\"\(.*\)\".*/\1/g")
    
    # Check if we got a valid patch version for our series
    if [ -n "$latest_patch" ] && [ "$latest_patch" != "$series_info" ] && [[ "$latest_patch" == "$series"* ]]; then
      echo "$latest_patch"
      return
    fi
  fi
  
}

# Discover all release series by probing the API
discover_all_series() {
  local current_version="$1"
  local discovered_series=""
  
  # Parse current version to get a starting point
  local current_year=$(echo "$current_version" | cut -d'.' -f1)
  local current_minor=$(echo "$current_version" | cut -d'.' -f2)
  
  if [ $VERBOSE -eq 1 ]; then
    echo "  Debug: Starting discovery from current version $current_version"
    echo "  Debug: Current year: $current_year, current minor: $current_minor"
  fi
  
  # Smart discovery strategy:
  # 1. Start with current year and probe series around the current version
  # 2. Check previous year for LTS (.1) releases
  # 3. Validate each series exists before adding it
  
  # Check current year series (from .1 up to current minor + 1 for future releases)
  local max_minor=$((current_minor + 1))
  if [ $max_minor -lt 4 ]; then
    max_minor=4  # Check at least up to .4 to catch future releases
  fi
  
  for minor in $(seq 1 $max_minor); do
    series="$current_year.$minor"
    
    if [ $VERBOSE -eq 1 ]; then
      echo "  Debug: Testing current year series $series" >&2
    fi
    
    # Quick test - try to get version info for this series
    test_response=$(curl -s --retry 3 --max-time 5 "https://repositories.scylladb.com/scylla/check_version?version=$series" 2>/dev/null)
    
    # Check if we got a valid response
    if echo "$test_response" | grep -q '"version"' && ! echo "$test_response" | grep -q '"error"'; then
      extracted_version=$(echo "$test_response" | sed -e "s/.*version\":\"\([^\"]*\)\".*/\1/g")
      
      # Validate the extracted version looks like a proper version
      if echo "$extracted_version" | grep -q "^$series\.[0-9]\+$"; then
        discovered_series="$discovered_series $series"
        if [ $VERBOSE -eq 1 ]; then
          echo "  Debug: ✓ Found series $series (version: $extracted_version)" >&2
        fi
      fi
    else
      if [ $VERBOSE -eq 1 ]; then
        echo "  Debug: ✗ Series $series not found or invalid" >&2
      fi
    fi
  done
  
  # Check previous year LTS (.1 release) - ScyllaDB maintains 2 LTS at a time
  prev_year=$((current_year - 1))
  if [ $prev_year -ge 2024 ]; then  # ScyllaDB started new versioning in 2024
    series="$prev_year.1"
    
    if [ $VERBOSE -eq 1 ]; then
      echo "  Debug: Testing previous year LTS $series" >&2
    fi

    test_response=$(curl -s --retry 3 --max-time 5 "https://repositories.scylladb.com/scylla/check_version?version=$series" 2>/dev/null)

    if echo "$test_response" | grep -q '"version"' && ! echo "$test_response" | grep -q '"error"'; then
      extracted_version=$(echo "$test_response" | sed -e "s/.*version\":\"\([^\"]*\)\".*/\1/g")
      
      if echo "$extracted_version" | grep -q "^$series\.[0-9]\+$"; then
        discovered_series="$discovered_series $series"
        if [ $VERBOSE -eq 1 ]; then
          echo "  Debug: ✓ Found previous year LTS $series (version: $extracted_version)" >&2
        fi
      fi
    fi
  fi
  
  echo "$discovered_series"
}

# Check if a series is actively supported (not EOL)
is_series_supported() {
  local series="$1"
  local current_version="$2"
  
  # Parse versions
  local series_year=$(echo "$series" | cut -d'.' -f1)
  local series_minor=$(echo "$series" | cut -d'.' -f2)
  local current_year=$(echo "$current_version" | cut -d'.' -f1)
  local current_minor=$(echo "$current_version" | cut -d'.' -f2)
  
  # LTS series (.1 releases) - ScyllaDB supports 2 LTS releases at a time
  if [ "$series_minor" = "1" ]; then
    # Current year LTS is always supported
    if [ "$series_year" = "$current_year" ]; then
      return 0
    fi
    
    # Previous year LTS is supported if current year has an LTS
    if [ "$series_year" = "$((current_year - 1))" ] && [ "$current_minor" -ge 1 ]; then
      return 0
    fi
    
    # No longer supported if it's 2+ years old
    return 1
  fi
  
  # Non-LTS series - only current year series are supported
  if [ "$series_year" = "$current_year" ]; then
    return 0
  fi
  
  # Non-LTS from previous years are EOL
  return 1
}

list_active_releases() {
  echo "ScyllaDB Active Releases (Latest to Oldest):"
  echo "============================================="
  
  # Check if curl is available
  if ! command -v curl &> /dev/null; then
    echo "Error: curl is not installed or not available in PATH"
    echo "Please install curl to use this feature"
    exit 1
  fi
  
  system="scylla"
  
  # Get current version to understand what's latest
  if [ $VERBOSE -eq 1 ]; then
    echo "  Debug: Querying https://repositories.scylladb.com/scylla/check_version?system=$system"
  fi
  
  current_version_raw=$(curl -s --retry 3 --max-time 5 "https://repositories.scylladb.com/scylla/check_version?system=$system" 2>/dev/null)
  curl_exit_code=$?
  
  if [ $VERBOSE -eq 1 ]; then
    echo "  Debug: curl exit code: $curl_exit_code"
    echo "  Debug: raw response: $current_version_raw"
  fi
  
  current_version=$(echo "$current_version_raw" | sed -e "s/.*version\":\"\(.*\)\".*/\1/g")
  
  if [ $VERBOSE -eq 1 ]; then
    echo "  Debug: extracted version: $current_version"
  fi
  
  if [ $curl_exit_code -ne 0 ]; then
    echo "  Network error: Failed to connect to ScyllaDB repository (curl exit code: $curl_exit_code)"
    echo "  Please check your internet connection and try again"
    return
  elif [ -z "$current_version" ] || [ "$current_version" = "" ] || [ "$current_version" = "$current_version_raw" ]; then
    echo "  Unable to parse version information"
    echo "  Raw response: $current_version_raw"
    echo "  Please try again later or check if the API format has changed"
    return
  fi
  
  # Dynamically discover all available series
  if [ $VERBOSE -eq 1 ]; then
    echo "  Debug: Starting series discovery..." >&2
  fi
  
  all_series=$(discover_all_series "$current_version")
  
  if [ $VERBOSE -eq 1 ]; then
    echo "  Debug: Discovered series: $all_series" >&2
  fi
  
  # Filter to only supported/active series
  active_series=""
  for series in $all_series; do
    if is_series_supported "$series" "$current_version"; then
      active_series="$active_series $series"
      if [ $VERBOSE -eq 1 ]; then
        echo "  Debug: $series is supported" >&2
      fi
    else
      if [ $VERBOSE -eq 1 ]; then
        echo "  Debug: $series is EOL" >&2
      fi
    fi
  done
  
  # Sort active series in descending order (latest to oldest)
  active_series=$(echo "$active_series" | tr ' ' '\n' | sort -V -r | uniq | tr '\n' ' ')
  
  if [ -z "$active_series" ]; then
    echo "  No active releases found. This might indicate an API issue."
    return
  fi
  
  # Parse current version info for latest marker
  current_year=$(echo "$current_version" | cut -d'.' -f1)
  current_minor=$(echo "$current_version" | cut -d'.' -f2)
  current_series="$current_year.$current_minor"
  
  # Display each active series with latest patch version
  for series in $active_series; do
    series=$(echo "$series" | xargs) # trim whitespace
    if [ -n "$series" ]; then
      latest_patch=$(get_latest_patch_for_series "$series")
      
      # Determine if this is LTS
      series_minor=$(echo "$series" | cut -d'.' -f2)
      if [ "$series_minor" = "1" ]; then
        lts_marker=" [LTS]"
      else
        lts_marker=""
      fi
      
      # Check if this is the current latest series
      if [ "$series" = "$current_series" ]; then
        support_status=" - latest"
      else
        support_status=""
      fi
      
      echo "  $series: $latest_patch$lts_marker$support_status"
    fi
  done
}

packages_update() {
  if [[ "$NAME" == @(Ubuntu|Debian)* ]]; then
    run_cmd apt update
    run_cmd apt-get install $APT_FLAGS curl gnupg2
  elif [[ "$NAME" == @(Amazon|Red Hat)* ]]; then
    run_cmd yum install $YUM_QUIET_CMD_PARAM hostname iputils
  fi
}

setup_install() {
  SCYLLA_GPG_KEY="d0a112e067426ab2 491c93b9de7496a7 a43e06657bac99e3 c503c686b007f39e"
  if [[ -n "$SCYLLA_REPO_FILE_URL" ]]; then
    SCYLLA_URL=${SCYLLA_REPO_FILE_URL}
    SCYLLA_PRODUCT_VERSION="${SCYLLA_PRODUCT}"
  else
    if [[ $SCYLLA_VERSION == *"nightly"* ]]; then
      SCYLLA_RELEASE=$(echo $SCYLLA_VERSION | cut -d'-' -f 2)
      if [ $SCYLLA_RELEASE == "master" ] || [ $SCYLLA_RELEASE == "enterprise" ]; then
        BRANCH_WORD=""
      fi
      if [ $1 == "debian" ]; then
        SCYLLA_URL="https://downloads.scylladb.com/unstable/$SCYLLA_PRODUCT/$BRANCH_WORD$SCYLLA_RELEASE/deb/unified/latest/scylladb-$SCYLLA_RELEASE/scylla.list"
        SCYLLA_PRODUCT_VERSION=$SCYLLA_PRODUCT
      elif [ $1 == "ubuntu" ]; then
        SCYLLA_URL="https://downloads.scylladb.com/unstable/$SCYLLA_PRODUCT/$BRANCH_WORD$SCYLLA_RELEASE/deb/unified/latest/scylladb-$SCYLLA_RELEASE/scylla.list"
        SCYLLA_PRODUCT_VERSION=$SCYLLA_PRODUCT
      else
        set_rpm_install_tool
        SCYLLA_URL="https://downloads.scylladb.com/unstable/$SCYLLA_PRODUCT/$BRANCH_WORD$SCYLLA_RELEASE/rpm/centos/latest/scylla.repo"
        SCYLLA_PRODUCT_VERSION=$SCYLLA_PRODUCT
      fi
    else
      SCYLLA_RELEASE=$(echo $SCYLLA_VERSION | sed -e "s/\([[:digit:]]\+.[[:digit:]]\+\).*/\1/g")
      if [ $1 == "debian" ]; then
        is_rc_version
        SCYLLA_URL="https://downloads.scylladb.com/deb/debian/scylla-$SCYLLA_RELEASE.list"
        SCYLLA_PRODUCT_VERSION="${SCYLLA_PRODUCT}=$SCYLLA_VERSION*"
      elif [ $1 == "ubuntu" ]; then
        is_rc_version
        SCYLLA_URL="https://downloads.scylladb.com/deb/ubuntu/scylla-$SCYLLA_RELEASE.list"
        SCYLLA_PRODUCT_VERSION="${SCYLLA_PRODUCT}=$SCYLLA_VERSION*"
      else
        set_rpm_install_tool
        SCYLLA_URL="https://downloads.scylladb.com/rpm/centos/scylla-$SCYLLA_RELEASE.repo"
        SCYLLA_PRODUCT_VERSION="$SCYLLA_PRODUCT-$SCYLLA_VERSION"
      fi
    fi
  fi
}

get_full_version() {
  PATCH_VERSION=$(echo $SCYLLA_VERSION | awk -v FS='.' '{print $3}')
  if [ -n "$PATCH_VERSION" ] && [ -z "$DEFAULT_SCYLLA_VERSION" ] || [[ $SCYLLA_VERSION == *rc* ]]; then
    FULL_SCYLLA_VERSION=$(apt-cache madison ${SCYLLA_PRODUCT} | grep -F -w $SCYLLA_VERSION | cut -d'|' -f 2 | sed 's/[[:space:]]//g' | head -n1)
    PACKAGES_LIST=',-server,-cqlsh,-kernel-conf,-node-exporter,-conf,-python3'
    [[ "$SCYLLA_VERSION" == 2025.1* ]] && PACKAGES_LIST+=",-tools,-tools-core"
    [[ "$SCYLLA_VERSION" == 2024.1* ]] && PACKAGES_LIST+=",-tools,-tools-core,-jmx"
    SCYLLA_PRODUCT_VERSION="${SCYLLA_PRODUCT}{$PACKAGES_LIST}=$FULL_SCYLLA_VERSION"
  fi
}

main() {

  DRY_RUN=0
  SCYLLA_REPO_FILE_URL=""

  DEBUG_RUN=0
  VERBOSE=0

  USAGE=0

  DEFAULT_SCYLLA_PRODUCT="scylla"
  SCYLLA_PRODUCT="$DEFAULT_SCYLLA_PRODUCT"

  SCYLLA_VERSION=""
  DEFAULT_SCYLLA_VERSION=""

  while [ $# -gt 0 ]; do
    case "$1" in
      "-h" | "--help")
        USAGE=1
        shift 1
        ;;
      "--scylla-product")
        SCYLLA_PRODUCT="$2"
        shift 2
        ;;
      "--scylla-version")
        SCYLLA_VERSION="$2"
        shift 2
        ;;
      "--list-active-releases")
        list_active_releases
        exit 0
        ;;
      "--dry-run")
        DRY_RUN=1
        shift 1
        ;;
      "--debug-run")
        DEBUG_RUN=1
        shift 1
        ;;
      "--scylla-repo-file-url")
        SCYLLA_REPO_FILE_URL="$2"
        shift 2
        ;;
      "--verbose")
        VERBOSE=1
        shift 1
        ;;
      *)
        USAGE=1
        shift 1
        ;;
    esac
  done

  if [ $USAGE -eq 1 ]; then
    usage
    exit 1
  fi

  RETRY_OPTION="-o Acquire::Retries=3"
  if [ $VERBOSE -eq 1 ]; then
    YUM_QUIET_CMD_PARAM="--assumeyes"
    APT_FLAGS="--assume-yes $RETRY_OPTION"
  else
    YUM_QUIET_CMD_PARAM="--assumeyes --quiet"
    APT_FLAGS="-qq $RETRY_OPTION"
  fi

  check_product
  check_os
  is_supported_version

  # os-release may be missing in container environment by default.
  if [ -f "/etc/os-release" ]; then
      . /etc/os-release
  elif [ -f "/etc/arch-release" ]; then
      export ID=arch
  else
      echo "/etc/os-release missing."
      exit 1
  fi

  packages_update

  if [ -z "$SCYLLA_VERSION" ] && [ -z "$SCYLLA_REPO_FILE_URL" ]; then
    query_default_version

    SCYLLA_VERSION="$DEFAULT_SCYLLA_VERSION"
  fi

  check_arch
  setup_install $ID

  case "$ID" in
    "amzn") amzn_install ;;
    "centos"|"rocky") centos_install ;;
    "debian"|"ubuntu") deb_install ;;
    "fedora") fedora_install ;;
    "almalinux") centos_install ;;
    "ol") ol_install ;;
    "rhel") rhel_install ;;
    *) echo "Operating system '$ID' is not supported by this installer." && exit 1
  esac

  echo_msg "Scylla installation done!"
}

usage() {
  cat 1>&2 <<EOF
scylla-get
The platform agnostic installer for Scylla.

USAGE:
    scylla-get [FLAGS] [OPTIONS]

FLAGS:
    -h, --help              Prints help information
    --list-active-releases  Lists all active ScyllaDB releases with LTS status

OPTIONS:
  [--scylla-product scylla|scylla-enterprise] - Scylla product to install (default: $DEFAULT_SCYLLA_PRODUCT)
  [--scylla-version <version>] - Scylla version to install (default: $DEFAULT_SCYLLA_VERSION)
  Version examples:
    --scylla-version nightly-master will install master latest nightly version
    --scylla-version nightly-5.4 will install 5.4 latest nightly version
    For enterprise product:
    --scylla-version nightly-enterprise will install enterprise latest nightly version
    --scylla-version nightly-202x.1 will install 202x.1 latest nightly version

DEBUG OPTIONS:
  [--scylla-repo-file-url <url>] URL to test installation from a custom URL.

DEBUG FLAGS:
  [--debug-run] - use slim/minimal dockers when needed.
  [--dry-run] -   Prints out commands instead of executing them.
  [--verbose] -   Runs install commands with no quiet option, and more info.

EOF
}

require_cmd() {
  CMD=$1
  if ! command -v "$CMD" &> /dev/null
  then
    echo "Please make sure '$CMD' is installed on this machine."
    exit
  fi
}

set_rpm_install_tool() {
  RPM_INSTALL_TOOL="yum"
  if [ $DEBUG_RUN -eq 1 ]; then
    RPM_INSTALL_TOOL="microdnf"
  fi
  require_cmd $RPM_INSTALL_TOOL
}

install_rpm() {
  ret=0
  run_cmd curl -f -s -L -o /etc/yum.repos.d/scylla.repo "$SCYLLA_URL" || ret=$?
  if [ $ret -ne 0 ]; then
    supported_versions_message
    exit 1
  fi
  run_cmd $RPM_INSTALL_TOOL install $YUM_QUIET_CMD_PARAM $SCYLLA_PRODUCT_VERSION
}

check_amzn_version() {
  case "$VERSION_ID" in
    2*)
      return 1
      ;;
    *)
      return 0
      ;;
  esac
}

amzn_install() {
  if check_amzn_version = 0; then
    echo "Amazon Linux $VERSION_ID is not supported by this installer."
    exit 1
  fi
  echo_msg "Installing Scylla version $SCYLLA_VERSION for Amazon Linux ..."
  set_rpm_install_tool
  install_rpm
}

check_centos_version() {
  case "$VERSION_ID" in
    8*|9*|10*)
      return 1
      ;;
    *)
      return 0
      ;;
  esac
}

centos_install() {
  if check_centos_version = 0; then
    echo "CentOS $VERSION_ID is not supported by this installer."
    exit 1
  fi
  set_rpm_install_tool
  echo_msg "Installing Scylla version $SCYLLA_VERSION for CentOS ..."
  install_rpm
}

fedora_install() {
  echo_msg "Installing Scylla version $SCYLLA_VERSION for Fedora ..."
  set_rpm_install_tool
  install_rpm
}

check_rhel_version() {
  case "$VERSION_ID" in
    7.?|8*|9*|10*)
      return 1
      ;;
    *)
      return 0
      ;;
  esac
}

rhel_install() {
  if check_rhel_version = 0; then
    echo "Red Hat Enterprise Linux $VERSION_ID is not supported by this installer."
    exit 1
  fi
  echo_msg "Installing Scylla version $SCYLLA_VERSION for Red Hat Enterprise Linux ..."
  set_rpm_install_tool
  install_rpm
}

check_ol_version() {
  case "$VERSION_ID" in
    7.?|8*)
      return 1
      ;;
    *)
      return 0
      ;;
  esac
}

ol_install() {
  if check_ol_version = 0; then
    echo "Oracle Linux $VERSION_ID is not supported by this installer."
    exit 1
  fi
  echo_msg "Installing Scylla version $SCYLLA_VERSION for Oracle Linux ..."
  set_rpm_install_tool
  install_rpm
}

check_debian_ubuntu_version() {
  if [[ "$ID" == "debian" ]]; then
    case "$VERSION_ID" in
      "9"|"10"|"11"|"12"|"13")
        return 0
        ;;
      *)
        echo "$ID $VERSION_ID is not supported by this installer."
        exit 1
        ;;
    esac
  elif [[ "$ID" == "ubuntu" ]]; then
    case "$VERSION_ID" in
      "20.04"|"22.04"|"24.04")
        return 0
        ;;
      *)
        echo "$ID $VERSION_ID is not supported by this installer."
        exit 1
        ;;
    esac
  fi
}

deb_install() {
  if check_debian_ubuntu_version; then
    export DEBIAN_FRONTEND=noninteractive
    APT_KEYS_DIR='/etc/apt/keyrings'

    ret=0
    run_cmd curl -f -s -L -o /etc/apt/sources.list.d/scylla.list "$SCYLLA_URL" || ret=$?
    if [ $ret -ne 0 ]; then
      supported_versions_message
      exit 1
    fi
    echo_msg "Installing Scylla version $SCYLLA_VERSION for $NAME ..."
    run_cmd mkdir -p $APT_KEYS_DIR
    run_cmd gpg --homedir /tmp --no-default-keyring --keyring /tmp/temp.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys $SCYLLA_GPG_KEY
    run_cmd gpg --homedir /tmp --no-default-keyring --keyring /tmp/temp.gpg --export --armor $SCYLLA_GPG_KEY | gpg --dearmor > $APT_KEYS_DIR/scylladb.gpg    
    run_cmd apt update $APT_FLAGS
    get_full_version
    run_cmd apt-get install $APT_FLAGS $SCYLLA_PRODUCT_VERSION
  fi
}

main "$@"
