#!/usr/bin/env bash

SCRIPT_NAME="ipregion.sh"
SCRIPT_URL="https://github.com/vladon/ipregion"
# Version metadata - injected during build/deploy
# Format: VERSION_TYPE|VERSION_VALUE|BUILD_DATE|COMMIT_HASH
# Examples:
#   tag|v2.1.0|2025-01-15T10:30:00Z|1a2b3c4d
#   commit|1a2b3c4d|2025-01-15T10:30:00Z|
SCRIPT_VERSION_METADATA="commit|6af55ff8|2026-06-10T00:57:14+03:00|"
USER_AGENT="Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
SPINNER_SERVICE_FILE=$(mktemp "${TMPDIR:-/tmp}/ipregion_spinner_XXXXXX")
DEBUG_LOG_FILE=""

VERBOSE=false
JSON_OUTPUT=false
GROUPS_TO_SHOW="all"
CURL_TIMEOUT=5
CURL_RETRIES=1
IPV4_ONLY=false
IPV6_ONLY=false
PROXY_ADDR=""
INTERFACE_NAME=""
DEBUG=false
IPV4_SUPPORTED=0
IPV6_SUPPORTED=0
EXTERNAL_IPV4=""
EXTERNAL_IPV6=""
EXTERNAL_IPV4_SOURCE=""
EXTERNAL_IPV6_SOURCE=""
EXTERNAL_IPV4_CACHE_HIT=false
EXTERNAL_IPV6_CACHE_HIT=false
EXTERNAL_IPV4_TIMESTAMP=""
EXTERNAL_IPV6_TIMESTAMP=""
PARALLEL_JOBS=0
PARALLEL_PIDS=()
FORCE_SPINNER=false
PROGRESS_LOG=false
LAST_PROGRESS_MSG=""
HEADER_PRINTED=false
SHOW_METRICS=false
OUTPUT_FILE=""

# Cache configuration
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/ipregion"
CACHE_FILE="$CACHE_DIR/ip_cache.json"
CACHE_TTL=3600
CACHE_ENABLED=true
IP_FETCH_MAX_RETRIES=3
IP_FETCH_RETRY_DELAY=1
CACHE_CLEARED=false
CACHE_SHOWN=false

RESULT_JSON=""
ARR_PRIMARY=()
ARR_CUSTOM=()
REGISTERED_COUNTRY_IPV4=""
REGISTERED_COUNTRY_IPV6=""
ASN_NUMBER=""
ASN_NAME=""
MAXMIND_PROFILE_IPV4=""
MAXMIND_PROFILE_IPV6=""
CURL_META_FILE=""
CACHE_LAST_SOURCE=""
CACHE_LAST_TIMESTAMP=""

COLOR_HEADER="1;36"
COLOR_SERVICE="1;32"
COLOR_HEART="1;31"
COLOR_URL="1;90"
COLOR_ASN="1;33"
COLOR_TABLE_HEADER="1;97"
COLOR_TABLE_VALUE="1"
COLOR_NULL="0;90"
COLOR_ERROR="1;31"
COLOR_WARN="1;33"
COLOR_INFO="1;36"
COLOR_RESET="0"

LOG_INFO="INFO"
LOG_WARN="WARNING"
LOG_ERROR="ERROR"
LOG_DEBUG="DEBUG"

STATUS_NA="N/A"
STATUS_DENIED="Denied"
STATUS_RATE_LIMIT="Rate-limit"
STATUS_SERVER_ERROR="Server error"

declare -A DEPENDENCIES=(
  [jq]="jq"
  [curl]="curl"
  [column]="util-linux"
  [nslookup]="bind-utils"
)

declare -A PACKAGE_MAPPING=(
  ["apt:nslookup"]="dnsutils"
  ["apt:column"]="bsdmainutils"
  ["pacman:nslookup"]="bind"
  ["dnf:nslookup"]="bind-utils"
  ["yum:nslookup"]="bind-utils"
  ["termux:column"]="util-linux"
)

declare -A PRIMARY_SERVICES=(
  [MAXMIND]="maxmind.com|geoip.maxmind.com|/geoip/v2.1/city/me"
  [RIPE]="rdap.db.ripe.net|rdap.db.ripe.net|/ip/{ip}"
  [IPINFO_IO]="ipinfo.io|ipinfo.io|/widget/demo/{ip}"
  [IPAPI_CO]="ipapi.co|ipapi.co|/{ip}/json"
  [CLOUDFLARE]="cloudflare.com|speed.cloudflare.com|/meta"
  [IFCONFIG_CO]="ifconfig.co|ifconfig.co|/country-iso?ip={ip}|plain"
  [IP2LOCATION_IO]="ip2location.io|api.ip2location.io|/?ip={ip}"
  [IPLOCATION_COM]="iplocation.com|iplocation.com"
  [COUNTRY_IS]="country.is|api.country.is|/{ip}"
  [GEOJS_IO]="geojs.io|get.geojs.io|/v1/ip/country.json?ip={ip}"
  [IPAPI_IS]="ipapi.is|api.ipapi.is|/?q={ip}"
  [IPBASE_COM]="ipbase.com|api.ipbase.com|/v2/info?ip={ip}"
  [IPQUERY_IO]="ipquery.io|api.ipquery.io|/{ip}"
  [IPWHOIS_IO]="ipwhois.io|ipwho.is|/{ip}"
  [IPWHO_IS]="ipwho.is|ipwho.is|/{ip}"
  [IPAPI_COM]="ip-api.com|ip-api.com|/json/{ip}?fields=countryCode"
  [DETECTOR404]="Detector404|geoip.detector404.ru|/api/v1/ip/{ip}"
)

PRIMARY_SERVICES_ORDER=(
  "MAXMIND"
  "RIPE"
  "IPINFO_IO"
  "CLOUDFLARE"
  "IPAPI_CO"
  "IFCONFIG_CO"
  "IP2LOCATION_IO"
  "IPLOCATION_COM"
  "COUNTRY_IS"
  "GEOJS_IO"
  "IPAPI_IS"
  "IPBASE_COM"
  "IPQUERY_IO"
  "IPWHOIS_IO"
  "IPWHO_IS"
  "IPAPI_COM"
  "DETECTOR404"
)

declare -A PRIMARY_SERVICES_CUSTOM_HANDLERS=(
  [IPLOCATION_COM]="lookup_iplocation_com"
)

declare -A SERVICE_HEADERS=(
  [MAXMIND]="Referer: https://www.maxmind.com"
  [IPAPI_COM]="Origin: https://ip-api.com"
  [CLOUDFLARE]="Referer: https://speed.cloudflare.com"
)

declare -A CUSTOM_SERVICES=(
  [GOOGLE]="Google"
  [YOUTUBE]="YouTube"
  [YOUTUBE_PREMIUM]="YouTube Premium"
  [GOOGLE_SEARCH_CAPTCHA]="Google Search Captcha"
  [GEMINI]="Google Gemini"
  [APPLE]="Apple"
  [STEAM]="Steam"
  [TIKTOK]="Tiktok"
  [OOKLA_SPEEDTEST]="Ookla Speedtest"
  [JETBRAINS]="JetBrains"
  [PLAYSTATION]="PlayStation"
  [MICROSOFT]="Microsoft"
)

CUSTOM_SERVICES_ORDER=(
  "GOOGLE"
  "YOUTUBE"
  "YOUTUBE_PREMIUM"
  "GOOGLE_SEARCH_CAPTCHA"
  "GEMINI"
  "APPLE"
  "STEAM"
  "TIKTOK"
  "OOKLA_SPEEDTEST"
  "JETBRAINS"
  "PLAYSTATION"
  "MICROSOFT"
)

declare -A CUSTOM_SERVICES_HANDLERS=(
  [GOOGLE]="lookup_google"
  [YOUTUBE]="lookup_youtube"
  [YOUTUBE_PREMIUM]="lookup_youtube_premium"
  [GOOGLE_SEARCH_CAPTCHA]="lookup_google_search_captcha"
  [GEMINI]="lookup_gemini"
  [APPLE]="lookup_apple"
  [STEAM]="lookup_steam"
  [TIKTOK]="lookup_tiktok"
  [OOKLA_SPEEDTEST]="lookup_ookla_speedtest"
  [JETBRAINS]="lookup_jetbrains"
  [PLAYSTATION]="lookup_playstation"
  [MICROSOFT]="lookup_microsoft"
)

declare -A SERVICE_GROUPS=(
  [primary]="${PRIMARY_SERVICES_ORDER[*]}"
  [custom]="${CUSTOM_SERVICES_ORDER[*]}"
)

EXCLUDED_SERVICES=(
  # "IPINFO_IO"
  # "IPAPI_CO"
  "GOOGLE_SEARCH_CAPTCHA"
)
INCLUDED_SERVICES=()
EXCLUDED_SERVICES_CLI=()

IDENTITY_SERVICES=(
  # Primary services
  "ident.me"
  "ifconfig.me"
  "api64.ipify.org"
  "ifconfig.co"
  # Secondary services (fallback pool)
  "icanhazip.com"
  "checkip.amazonaws.com"
  "ipinfo.io/ip"
  "api.ipify.org"
  "myip.dnsomatic.com"
  "ipecho.net/plain"
  "whatismyipaddress.com/plain"
)

IPV6_OVER_IPV4_SERVICES=(
  "IPINFO_IO"
  "IPAPI_IS"
  "IPLOCATION_COM"
  "IPWHO_IS"
  "IPAPI_COM"
)

color() {
  local color_name="$1"
  local text="$2"
  local code

  case "$color_name" in
  HEADER) code="$COLOR_HEADER" ;;
  SERVICE) code="$COLOR_SERVICE" ;;
  HEART) code="$COLOR_HEART" ;;
  URL) code="$COLOR_URL" ;;
  ASN) code="$COLOR_ASN" ;;
  TABLE_HEADER) code="$COLOR_TABLE_HEADER" ;;
  TABLE_VALUE) code="$COLOR_TABLE_VALUE" ;;
  NULL) code="$COLOR_NULL" ;;
  ERROR) code="$COLOR_ERROR" ;;
  WARN) code="$COLOR_WARN" ;;
  INFO) code="$COLOR_INFO" ;;
  RESET) code="$COLOR_RESET" ;;
  *) code="$color_name" ;;
  esac

  printf "\033[%sm%s\033[0m" "$code" "$text"
}

bold() {
  local text="$1"
  printf "\033[1m%s\033[0m" "$text"
}

get_timestamp() {
  local format="$1"
  date +"$format"
}

log() {
  local log_level="$1"
  local message="${*:2}"
  local timestamp

  if [[ "$VERBOSE" == true ]]; then
    local color_code

    timestamp=$(get_timestamp "%d.%m.%Y %H:%M:%S")

    case "$log_level" in
    "$LOG_ERROR") color_code=ERROR ;;
    "$LOG_WARN") color_code=WARN ;;
    "$LOG_INFO") color_code=INFO ;;
    "$LOG_DEBUG") color_code=NULL ;;
    *) color_code=RESET ;;
    esac

    printf "[%s] [%s]: %s\n" "$timestamp" "$(color $color_code "$log_level")" "$message" >&2
  fi
}

error_exit() {
  local message="$1"
  local exit_code="${2:-1}"
  printf "%s %s\n" "$(color ERROR '[ERROR]')" "$(color TABLE_HEADER "$message")" >&2
  display_help
  exit "$exit_code"
}

display_help() {
  cat <<EOF

Usage: $SCRIPT_NAME [OPTIONS]

IPRegion — determines your IP geolocation using various GeoIP services and popular websites

Options:
  -h, --help           Show this help message and exit
  -v, --verbose        Enable verbose logging
  -d, --debug          Enable debug trace; log may contain sensitive data (uploads use redacted copy)
  -j, --json           Output results in JSON format
  -g, --group GROUP    Run only one group: 'primary', 'custom', or 'all' (default: all)
  -t, --timeout SEC    Set curl request timeout in seconds (default: $CURL_TIMEOUT)
  -P, --parallel N     Run up to N services in parallel (default: auto)
  -S, --force-spinner  Always show spinner (even in parallel mode)
  --force-spinner      Always show spinner (even in parallel mode)
  --progress-log       Print progress lines instead of spinner
  -4, --ipv4           Test only IPv4
  -6, --ipv6           Test only IPv6
  -p, --proxy ADDR     Use SOCKS5 proxy (format: host:port)
  -i, --interface IF   Use specified network interface (e.g. eth1)
  --no-cache           Disable IP address caching
  --cache-ttl SEC      Set cache TTL in seconds (default: $CACHE_TTL)
  --clear-cache        Clear the IP cache
  --show-cache         Show cached IP addresses
  --include-service S  Run only selected services (repeatable; case-insensitive)
  --exclude-service S  Skip selected services (repeatable; case-insensitive)
  --metrics            Show per-service HTTP/latency metrics in table output
  -o, --output FILE    Save results to file (.json or .csv)

Examples:
  $SCRIPT_NAME                       # Check all services with default settings
  $SCRIPT_NAME -g primary            # Check only GeoIP services
  $SCRIPT_NAME -g custom             # Check only popular websites
  $SCRIPT_NAME -4                    # Test only IPv4
  $SCRIPT_NAME -6                    # Test only IPv6
  $SCRIPT_NAME -p 127.0.0.1:1080     # Use SOCKS5 proxy
  $SCRIPT_NAME -i eth1               # Use network interface eth1
  $SCRIPT_NAME -j                    # Output result as JSON
  $SCRIPT_NAME -v                    # Enable verbose logging
  $SCRIPT_NAME -d                    # Enable debug and save full trace to file (upload on consent)
  $SCRIPT_NAME -P 6                  # Check services using 6 parallel jobs
  $SCRIPT_NAME --force-spinner       # Force spinner even with parallel checks
  $SCRIPT_NAME --progress-log        # Print progress lines instead of spinner
  $SCRIPT_NAME --show-cache         # Show cached IP addresses
  $SCRIPT_NAME --clear-cache         # Clear the IP cache
  $SCRIPT_NAME --no-cache            # Disable IP address caching
  $SCRIPT_NAME --cache-ttl 1800      # Set cache TTL to 30 minutes
  $SCRIPT_NAME --include-service MAXMIND --include-service GOOGLE
  $SCRIPT_NAME --exclude-service YOUTUBE
  $SCRIPT_NAME --metrics
  $SCRIPT_NAME --output result.json
  $SCRIPT_NAME --output result.csv

EOF
}

print_startup_message() {
  local parallel_display="$PARALLEL_JOBS"
  local ipv4="auto" ipv6="auto"
  local proxy="${PROXY_ADDR:-none}"
  local iface="${INTERFACE_NAME:-auto}"
  local cache_status="${CACHE_ENABLED:-false}"
  local cache_ttl_display="${CACHE_TTL}s"

  if ((PARALLEL_JOBS <= 0)); then
    parallel_display="unknown"
  fi

  if [[ "$IPV4_ONLY" == true ]]; then
    ipv4="only"
    ipv6="off"
  elif [[ "$IPV6_ONLY" == true ]]; then
    ipv4="off"
    ipv6="only"
  fi

  if [[ "$CACHE_ENABLED" == true ]]; then
    cache_status="enabled (TTL: ${CACHE_TTL}s)"
  else
    cache_status="disabled"
  fi

  if [[ "$JSON_OUTPUT" == true ]]; then
    return
  fi

  printf "%s %s: %s\n" \
    "$(color INFO '[INFO]')" \
    "$(color HEADER 'Version')" \
    "$(get_script_version)" >&2

  printf "%s %s\n" \
    "$(color INFO '[INFO]')" \
    "Starting with group=$GROUPS_TO_SHOW timeout=${CURL_TIMEOUT}s parallel=$parallel_display ipv4=$ipv4 ipv6=$ipv6 proxy=$proxy interface=$iface cache=$cache_status metrics=$SHOW_METRICS output=${OUTPUT_FILE:-stdout} verbose=$VERBOSE debug=$DEBUG" >&2
}

setup_debug() {
  if [[ "$DEBUG" != true ]]; then
    return 1
  fi

  umask 077
  DEBUG_LOG_FILE=$(mktemp "${TMPDIR:-/tmp}/ipregion_debug_XXXXXX.log")

  exec 3>&1 4>&2

  exec 1> >(tee -a "$DEBUG_LOG_FILE" >&3)
  exec 2> >(tee -a "$DEBUG_LOG_FILE" >&4)

  set -x
  return 0
}

grep_wrapper() {
  local grep_args=()

  if [[ "$1" == "--perl" ]]; then
    grep_args+=("-oP")
    shift
  fi

  grep "${grep_args[@]}" "$@"
}

normalize_ascii() {
  local value="$1"

  if command -v iconv >/dev/null 2>&1; then
    printf "%s" "$value" | iconv -f UTF-8 -t ASCII//TRANSLIT 2>/dev/null
    return
  fi

  printf "%s" "$value"
}

html_decode() {
  local value="$1"

  if command -v python3 >/dev/null 2>&1; then
    python3 -c 'import html,sys; sys.stdout.write(html.unescape(sys.stdin.read()))' <<<"$value"
    return
  fi

  if command -v perl >/dev/null 2>&1; then
    perl -MHTML::Entities -pe 'decode_entities($_)' <<<"$value"
    return
  fi

  if command -v php >/dev/null 2>&1; then
    php -r 'echo html_entity_decode(stream_get_contents(STDIN), ENT_QUOTES | ENT_HTML5);' <<<"$value"
    return
  fi

  if command -v sed >/dev/null 2>&1; then
    printf "%s" "$value" | sed -E \
      -e 's/&amp;/\&/g' \
      -e 's/&lt;/</g' \
      -e 's/&gt;/>/g' \
      -e 's/&quot;/"/g' \
      -e "s/&#39;/'/g"
    return
  fi

  printf "%s" "$value"
}

redact_debug_log() {
  local source_file="$1"
  local redacted_file
  redacted_file=$(mktemp "${TMPDIR:-/tmp}/ipregion_debug_redacted_XXXXXX.log")

  sed -E \
    -e 's/([A-Fa-f0-9]{0,4}:){2,}[A-Fa-f0-9]{0,4}/[REDACTED_IPV6]/g' \
    -e 's/\b([0-9]{1,3}\.){3}[0-9]{1,3}\b/[REDACTED_IPV4]/g' \
    -e 's/(Authorization:)[^[:cntrl:]]*/\1 [REDACTED]/I' \
    -e 's/(Proxy-Authorization:)[^[:cntrl:]]*/\1 [REDACTED]/I' \
    -e 's/(X-Api-Key:)[^[:cntrl:]]*/\1 [REDACTED]/I' \
    -e 's/(X-Auth-Token:)[^[:cntrl:]]*/\1 [REDACTED]/I' \
    -e 's/(Cookie:)[^[:cntrl:]]*/\1 [REDACTED]/I' \
    -e 's/(Set-Cookie:)[^[:cntrl:]]*/\1 [REDACTED]/I' \
    -e 's/([?&](token|access_token|refresh_token|api_key|apikey|password)=)[^&#[:space:]]+/\1[REDACTED]/Ig' \
    "$source_file" >"$redacted_file"

  printf "%s" "$redacted_file"
}

upload_debug() {
  local ip_version
  local user_agent="ipregion-script/1.0 (github.com/vernette/ipregion)"
  local upload_file

  ip_version=$(preferred_ip_version)
  upload_file=$(redact_debug_log "$DEBUG_LOG_FILE")

  curl_wrapper POST "https://0x0.st" \
    --user-agent "$user_agent" \
    --form "file=@$upload_file" \
    --form "secret=" \
    --form "expires=24" \
    --ip-version "$ip_version"

  rm -f "$upload_file"
}

cleanup_debug() {
  local debug_url

  if [[ ! -f "$DEBUG_LOG_FILE" ]]; then
    return 1
  fi

  set +x
  exec 1>&3 2>&4 3>&- 4>&-

  printf "\n%s\n  %s\n" \
    "$(color WARN 'Debug information:')" \
    "Local file: $DEBUG_LOG_FILE"
  printf "%s\n" \
    "$(color WARN 'Privacy notice: the debug log may contain sensitive data. Upload uses a redacted copy, but review it before sharing.')"

  if [[ -t 0 ]] && prompt_for_debug_upload </dev/tty; then
    debug_url="$(upload_debug)"
    printf "  %s\n\n%s\n%s\n\n%s\n" \
      "Remote URL: $debug_url" \
      "$(color INFO 'PRIVACY NOTICE: This file is uploaded to 0x0.st - a public file hoster.')" \
      "$(color INFO 'The file will be automatically deleted in 24 hours.')" \
      "$(color INFO 'If you open a GitHub Issue, please download the log and attach it')"
  else
    printf "\n%s\n" \
      "$(color INFO 'Upload skipped. You can share the local file manually if needed.')"
  fi
}

is_command_available() {
  local cmd="$1"
  command -v "$cmd" >/dev/null 2>&1
}

supports_wait_n() {
  local major minor
  major=${BASH_VERSINFO[0]:-0}
  minor=${BASH_VERSINFO[1]:-0}

  ((major > 4 || (major == 4 && minor >= 3)))
}

prune_parallel_pids() {
  local pid
  local -a remaining=()

  for pid in "${PARALLEL_PIDS[@]}"; do
    if kill -0 "$pid" 2>/dev/null; then
      remaining+=("$pid")
    else
      wait "$pid" 2>/dev/null
    fi
  done

  PARALLEL_PIDS=("${remaining[@]}")
}

wait_for_parallel_slot() {
  while ((${#PARALLEL_PIDS[@]} >= PARALLEL_JOBS)); do
    if supports_wait_n; then
      wait -n 2>/dev/null || true
      prune_parallel_pids
    else
      log "$LOG_INFO" "Bash < 4.3 detected; using legacy parallel wait loop"
      prune_parallel_pids
      if ((${#PARALLEL_PIDS[@]} < PARALLEL_JOBS)); then
        break
      fi
      sleep 0.1
    fi
  done
}

register_parallel_pid() {
  local pid="$1"
  PARALLEL_PIDS+=("$pid")
}

detect_parallel_jobs() {
  local cpus=""

  if is_command_available "nproc"; then
    cpus=$(nproc 2>/dev/null)
  elif is_command_available "getconf"; then
    cpus=$(getconf _NPROCESSORS_ONLN 2>/dev/null)
  fi

  if [[ "$cpus" =~ ^[0-9]+$ ]] && ((10#$cpus >= 1)); then
    echo "$cpus"
  else
    echo 4
  fi
}

detect_distro() {
  if [[ -f /etc/os-release ]]; then
    distro=$(sed -n 's/^ID=//p' /etc/os-release | head -n 1 | tr -d '"')
  elif [[ -f /etc/redhat-release ]]; then
    distro="rhel"
  elif [[ -d /data/data/com.termux ]]; then
    distro="termux"
  fi
}

detect_package_manager() {
  local pkg_manager

  case "$distro" in
  ubuntu | debian | termux)
    pkg_manager="apt"
    ;;
  arch | manjaro)
    pkg_manager="pacman"
    ;;
  fedora)
    pkg_manager="dnf"
    ;;
  centos | rhel)
    if is_command_available "dnf"; then
      pkg_manager="dnf"
    else
      pkg_manager="yum"
    fi
    ;;
  opensuse*)
    pkg_manager="zypper"
    ;;
  alpine)
    pkg_manager="apk"
    ;;
  *)
    error_exit "Unknown distro: $distro"
    ;;
  esac

  echo "$pkg_manager"
}

get_missing_commands() {
  local missing=()

  for cmd in "${!DEPENDENCIES[@]}"; do
    if ! is_command_available "$cmd"; then
      missing+=("$cmd")
    fi
  done

  printf '%s\n' "${missing[@]}"
}

get_package_name() {
  local pkg_manager="$1"
  local command="$2"
  local mapping_key="${pkg_manager}:${command}"

  if [[ -n "${PACKAGE_MAPPING[$mapping_key]}" ]]; then
    echo "${PACKAGE_MAPPING[$mapping_key]}"
    return
  fi

  echo "${DEPENDENCIES[$command]:-$command}"
}

is_sudo_required() {
  if [[ "${EUID:-$(id -u)}" -eq 0 || "$distro" == "termux" ]]; then
    return 1
  fi

  return 0
}

get_install_args() {
  local pkg_manager="$1"
  local install_args

  case "$pkg_manager" in
  apt)
    install_args=("install" "-y")
    ;;
  pacman)
    install_args=("-Sy" "--noconfirm")
    ;;
  dnf | yum | zypper)
    install_args=("install" "-y")
    ;;
  apk)
    install_args=("add" "--no-cache")
    ;;
  esac

  echo "${install_args[@]}"
}

install_packages() {
  local pkg_manager="$1"
  shift
  local packages=("$@")
  local cmd_prefix=()
  local install_cmd=()

  if is_sudo_required; then
    cmd_prefix=("sudo")
    log "$LOG_INFO" "Running as non-root user, using sudo"
  fi

  cmd_prefix+=("$pkg_manager")

  if [[ "$pkg_manager" == "apt" ]]; then
    log "$LOG_INFO" "Updating package lists"
    if ! "${cmd_prefix[@]}" update; then
      error_exit "Error occurred while updating package lists"
    fi
  fi

  for pkg in "${packages[@]}"; do
    if ! is_valid_package_name "$pkg"; then
      error_exit "Invalid package name: $pkg"
    fi
  done

  read -ra install_args <<<"$(get_install_args "$pkg_manager")"
  if ((${#install_args[@]} == 0)); then
    error_exit "No install arguments available for package manager: $pkg_manager"
  fi
  install_cmd+=("${cmd_prefix[@]}" "${install_args[@]}" "${packages[@]}")

  log "$LOG_INFO" "Running: ${install_cmd[*]}"

  if ! "${install_cmd[@]}"; then
    error_exit "Error occurred while installing packages"
  fi
}

prompt_for_installation() {
  local missing=("$@")
  local response
  local formatted_deps=""

  for dep in "${missing[@]}"; do
    formatted_deps+="  $dep\n"
  done

  printf "\n%s\n%b\n%s " \
    "$(color WARN 'Missing dependencies:')" \
    "$formatted_deps" \
    "$(color INFO 'Do you want to install them? [y/N]:')"

  read -r response
  response=${response,,}

  case "$response" in
  y | yes)
    return 0
    ;;
  *)
    return 1
    ;;
  esac
}

prompt_for_debug_upload() {
  local response

  printf "\n%s %s " \
    "$(color WARN 'Debug log upload?')" \
    "$(color INFO 'This will upload to 0x0.st (public). Proceed? [y/N]:')"

  read -r response
  response=${response,,}

  case "$response" in
  y | yes)
    return 0
    ;;
  *)
    return 1
    ;;
  esac
}

install_dependencies() {
  local missing_dependencies=()
  local missing_commands pkg_manager package_name

  log "$LOG_INFO" "Checking dependencies"

  mapfile -t missing_commands < <(get_missing_commands)

  if [[ "${missing_commands[*]}" =~ ^[[:space:]]*$ ]]; then
    log "$LOG_INFO" "All dependencies are installed"
    return 0
  fi

  log "$LOG_INFO" "Missing commands: ${missing_commands[*]}"

  pkg_manager=$(detect_package_manager)

  log "$LOG_INFO" "Detected package manager: $pkg_manager"

  for cmd in "${missing_commands[@]}"; do
    package_name=$(get_package_name "$pkg_manager" "$cmd")
    missing_dependencies+=("$package_name")
  done

  log "$LOG_INFO" "Missing dependencies: ${missing_dependencies[*]}"

  if ! prompt_for_installation "${missing_dependencies[@]}" </dev/tty; then
    printf "%s\n" "$(color WARN 'Installation canceled by user')"
    exit 1
  fi

  log "$LOG_INFO" "Installing missing dependencies"
  install_packages "$pkg_manager" "${missing_dependencies[@]}"
}

is_valid_json() {
  local json="$1"
  jq -e . >/dev/null 2>&1 <<<"$json"
}

process_json() {
  local json="$1"
  local jq_filter="$2"

  if is_status_string "$json"; then
    echo "$json"
    return
  fi

  if [[ -z "$json" ]]; then
    echo ""
    return
  fi

  if ! is_valid_json "$json"; then
    log "$LOG_WARN" "Invalid JSON input for filter: $jq_filter"
    echo ""
    return
  fi

  jq -r "$jq_filter" <<<"$json"
}

format_value() {
  local value="$1"

  case "$value" in
  "$STATUS_NA")
    color NULL "$value"
    ;;
  "$STATUS_DENIED" | "$STATUS_SERVER_ERROR")
    color ERROR "$value"
    ;;
  "$STATUS_RATE_LIMIT")
    color WARN "$value"
    ;;
  *)
    bold "$value"
    ;;
  esac
}

print_value_or_colored() {
  local value="$1"
  local color_name="$2"

  if [[ "$JSON_OUTPUT" == true ]]; then
    echo "$value"
    return
  fi

  color "$color_name" "$value"
}

mask_ipv4() {
  local ip="$1"
  echo "${ip%.*.*}.*.*"
}

mask_ipv6() {
  local ip="$1"
  local left_part right_part
  local -a left_arr right_arr expanded
  local h total missing=0

  if [[ -z "$ip" || "$ip" == "null" ]]; then
    echo "$ip"
    return
  fi

  if [[ "$ip" == *"."* || ! "$ip" =~ ^[0-9A-Fa-f:]+$ ]]; then
    echo "$ip"
    return
  fi

  if [[ "$ip" == *"::"* ]]; then
    if [[ "${ip#*::}" == *"::"* ]]; then
      echo "$ip"
      return
    fi
    left_part="${ip%%::*}"
    right_part="${ip##*::}"
  else
    left_part="$ip"
    right_part=""
  fi

  if [[ -n "$left_part" ]]; then
    IFS=':' read -r -a left_arr <<<"$left_part"
  fi

  if [[ -n "$right_part" ]]; then
    IFS=':' read -r -a right_arr <<<"$right_part"
  fi

  for h in "${left_arr[@]}" "${right_arr[@]}"; do
    if [[ -z "$h" || ! "$h" =~ ^[0-9A-Fa-f]{1,4}$ ]]; then
      echo "$ip"
      return
    fi
  done

  total=$((${#left_arr[@]} + ${#right_arr[@]}))
  if [[ "$ip" == *"::"* ]]; then
    missing=$((8 - total))
    if ((missing < 1)); then
      echo "$ip"
      return
    fi
  elif ((total != 8)); then
    echo "$ip"
    return
  fi

  expanded=("${left_arr[@]}")
  for ((h = 0; h < missing; h++)); do
    expanded+=("0")
  done
  expanded+=("${right_arr[@]}")

  if ((${#expanded[@]} < 3)); then
    echo "$ip"
    return
  fi

  printf "%s:%s:%s::\n" "${expanded[0]}" "${expanded[1]}" "${expanded[2]}"
}

is_valid_port() {
  local port="$1"

  if [[ ! "$port" =~ ^[0-9]+$ ]]; then
    return 1
  fi

  ((10#$port >= 1 && 10#$port <= 65535))
}

is_valid_proxy_addr() {
  local addr="$1"
  local host port

  if [[ "$addr" =~ ^\[(.+)\]:([0-9]+)$ ]]; then
    host="${BASH_REMATCH[1]}"
    port="${BASH_REMATCH[2]}"
    [[ "$host" =~ ^[0-9A-Fa-f:]+$ ]] || return 1
  elif [[ "$addr" =~ ^([^:]+):([0-9]+)$ ]]; then
    host="${BASH_REMATCH[1]}"
    port="${BASH_REMATCH[2]}"
    [[ "$host" =~ ^[A-Za-z0-9.-]+$ ]] || return 1
  else
    return 1
  fi

  is_valid_port "$port"
}

# Parse version metadata and return formatted version string
get_script_version() {
  IFS='|' read -r version_type version_value build_date commit_hash <<<"$SCRIPT_VERSION_METADATA"

  case "$version_type" in
  tag)
    # Release build: v2.1.0 (1a2b3c4d)
    if [[ -n "$commit_hash" ]]; then
      echo "${version_value} (${commit_hash})"
    else
      echo "$version_value"
    fi
    ;;
  commit)
    # Development build: 1a2b3c4d (2025-01-15 10:30 UTC)
    if [[ -n "$build_date" ]]; then
      local formatted_date=$(echo "$build_date" | sed 's/T/ /; s/Z/ UTC/')
      echo "${version_value} (${formatted_date})"
    else
      echo "$version_value"
    fi
    ;;
  *)
    # Fallback for unknown version
    echo "unknown"
    ;;
  esac
}

is_valid_interface_name() {
  local name="$1"
  [[ "$name" =~ ^[A-Za-z0-9._:@-]+$ ]]
}

is_valid_ipv4() {
  local ip="$1"
  local IFS=.
  local -a parts
  local part

  [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
  read -r -a parts <<<"$ip"
  for part in "${parts[@]}"; do
    [[ "$part" =~ ^[0-9]+$ ]] || return 1
    ((10#$part >= 0 && 10#$part <= 255)) || return 1
  done
  return 0
}

is_valid_ipv6() {
  local ip="$1"
  local part
  local -a parts
  local group_count=0
  local double_colon_count=0
  local tmp

  [[ -n "$ip" && "$ip" == *:* ]] || return 1

  if command -v python3 >/dev/null 2>&1; then
    python3 - "$ip" <<'PY'
import ipaddress
import sys
try:
    ipaddress.IPv6Address(sys.argv[1])
    sys.exit(0)
except Exception:
    sys.exit(1)
PY
    return $?
  fi

  [[ "$ip" =~ ^[0-9A-Fa-f:]+$ ]] || return 1

  tmp="$ip"
  while [[ "$tmp" == *"::"* ]]; do
    double_colon_count=$((double_colon_count + 1))
    tmp="${tmp#*::}"
  done

  ((double_colon_count <= 1)) || return 1

  if [[ "$ip" == *"::"* ]]; then
    ip="${ip#::}"
    ip="${ip%::}"
  fi

  IFS=':' read -r -a parts <<<"$ip"
  for part in "${parts[@]}"; do
    [[ -z "$part" ]] && continue
    [[ "$part" =~ ^[0-9A-Fa-f]{1,4}$ ]] || return 1
    group_count=$((group_count + 1))
  done

  if ((double_colon_count == 1)); then
    ((group_count <= 8)) || return 1
  else
    ((group_count == 8)) || return 1
  fi

  return 0
}

is_valid_package_name() {
  local name="$1"
  [[ "$name" =~ ^[A-Za-z0-9][A-Za-z0-9+._-]*$ ]]
}

normalize_service_name() {
  local name="$1"
  name="${name^^}"
  name="${name//-/_}"
  echo "$name"
}

is_service_in_list() {
  local needle="$1"
  shift
  local item

  for item in "$@"; do
    if [[ "$item" == "$needle" ]]; then
      return 0
    fi
  done

  return 1
}

validate_known_service() {
  local service_name="$1"

  if [[ -n "${PRIMARY_SERVICES[$service_name]}" || -n "${CUSTOM_SERVICES[$service_name]}" ]]; then
    return 0
  fi

  return 1
}

should_skip_service() {
  local service_name="$1"

  if ((${#INCLUDED_SERVICES[@]} > 0)) && ! is_service_in_list "$service_name" "${INCLUDED_SERVICES[@]}"; then
    return 0
  fi

  if is_service_in_list "$service_name" "${EXCLUDED_SERVICES[@]}" || is_service_in_list "$service_name" "${EXCLUDED_SERVICES_CLI[@]}"; then
    return 0
  fi

  return 1
}

parse_arguments() {
  while [[ $# -gt 0 ]]; do
    case $1 in
    -h | --help)
      display_help
      exit 0
      ;;
    -v | --verbose)
      VERBOSE=true
      shift
      ;;
    -d | --debug)
      DEBUG=true
      shift
      ;;
    -j | --json)
      JSON_OUTPUT=true
      shift
      ;;
    -g | --group)
      case "$2" in
      primary | custom | all)
        GROUPS_TO_SHOW="$2"
        ;;
      "")
        error_exit "Missing value for --group. Expected: primary, custom, or all"
        ;;
      *)
        error_exit "Invalid group: $2. Expected: primary, custom, or all"
        ;;
      esac
      shift 2
      ;;
    -t | --timeout)
      if [[ "$2" =~ ^[0-9]+$ ]]; then
        CURL_TIMEOUT="$2"
      else
        error_exit "Invalid timeout value: $2. Timeout must be a positive integer"
      fi
      shift 2
      ;;
    -P | --parallel)
      if [[ "$2" =~ ^[0-9]+$ ]] && ((10#$2 >= 1)); then
        PARALLEL_JOBS="$2"
      else
        error_exit "Invalid parallel value: $2. Parallel jobs must be a positive integer"
      fi
      shift 2
      ;;
    -S | --force-spinner)
      FORCE_SPINNER=true
      shift
      ;;
    --progress-log)
      PROGRESS_LOG=true
      shift
      ;;
    -4 | --ipv4)
      IPV4_ONLY=true
      shift
      ;;
    -6 | --ipv6)
      IPV6_ONLY=true
      shift
      ;;
    -p | --proxy)
      if ! is_valid_proxy_addr "$2"; then
        error_exit "Invalid proxy address: $2. Expected host:port or [ipv6]:port"
      fi
      PROXY_ADDR="$2"
      log "$LOG_INFO" "Using SOCKS5 proxy: $PROXY_ADDR"
      shift 2
      ;;
    -i | --interface)
      if ! is_valid_interface_name "$2"; then
        error_exit "Invalid interface name: $2"
      fi
      INTERFACE_NAME="$2"
      log "$LOG_INFO" "Using interface: $INTERFACE_NAME"
      shift 2
      ;;
    --no-cache)
      CACHE_ENABLED=false
      log "$LOG_INFO" "Cache disabled"
      shift
      ;;
    --cache-ttl)
      if [[ "$2" =~ ^[0-9]+$ ]] && ((10#$2 >= 0)); then
        CACHE_TTL="$2"
        log "$LOG_INFO" "Cache TTL set to ${CACHE_TTL}s"
      else
        error_exit "Invalid cache TTL value: $2. TTL must be a non-negative integer"
      fi
      shift 2
      ;;
    --clear-cache)
      CACHE_CLEARED=true
      shift
      ;;
    --show-cache)
      CACHE_SHOWN=true
      shift
      ;;
    --include-service)
      if [[ -z "$2" ]]; then
        error_exit "Missing value for --include-service"
      fi
      local include_service
      include_service=$(normalize_service_name "$2")
      if ! validate_known_service "$include_service"; then
        error_exit "Unknown service for --include-service: $2"
      fi
      INCLUDED_SERVICES+=("$include_service")
      shift 2
      ;;
    --exclude-service)
      if [[ -z "$2" ]]; then
        error_exit "Missing value for --exclude-service"
      fi
      local exclude_service
      exclude_service=$(normalize_service_name "$2")
      if ! validate_known_service "$exclude_service"; then
        error_exit "Unknown service for --exclude-service: $2"
      fi
      EXCLUDED_SERVICES_CLI+=("$exclude_service")
      shift 2
      ;;
    --metrics)
      SHOW_METRICS=true
      shift
      ;;
    -o | --output)
      if [[ -z "$2" ]]; then
        error_exit "Missing value for --output"
      fi
      OUTPUT_FILE="$2"
      shift 2
      ;;
    *)
      error_exit "Unknown option: $1"
      ;;
    esac
  done
}

is_status_string() {
  local value="$1"

  case "$value" in
  "$STATUS_DENIED" | "$STATUS_SERVER_ERROR" | "$STATUS_RATE_LIMIT" | "$STATUS_NA")
    return 0
    ;;
  *)
    return 1
    ;;
  esac
}

status_from_http_code() {
  local code="$1"

  case "$code" in
  403)
    echo "$STATUS_DENIED"
    ;;
  429)
    echo "$STATUS_RATE_LIMIT"
    ;;
  5*)
    echo "$STATUS_SERVER_ERROR"
    ;;
  4*)
    echo "$STATUS_NA"
    ;;
  *)
    echo ""
    ;;
  esac
}

get_ping_command() {
  local version="$1"
  local ping_cmd

  if [[ "$version" == "4" ]]; then
    if is_command_available "ping"; then
      ping_cmd="ping"
    fi
  else
    if is_command_available "ping6"; then
      ping_cmd="ping6"
    elif is_command_available "ping"; then
      ping_cmd="ping -6"
    fi
  fi

  if [[ -n "$ping_cmd" ]]; then
    echo "$ping_cmd"
    return 0
  else
    return 1
  fi
}

check_ip_interfaces() {
  local version="$1"
  local ifconfig_output

  log "$LOG_INFO" "Checking for IPv${version} interfaces"

  if is_command_available "ip"; then
    if [[ -n $(ip -"${version}" addr show scope global 2>/dev/null) ]]; then
      log "$LOG_INFO" "IPv${version} global interfaces found"
      return 0
    fi
  elif is_command_available "ifconfig"; then
    ifconfig_output=$(ifconfig -a 2>/dev/null)
    if [[ "$version" == "4" ]]; then
      if echo "$ifconfig_output" | grep -E '\binet [0-9]+' | grep -Ev '\binet 127\.' >/dev/null; then
        log "$LOG_INFO" "IPv${version} global interfaces found (ifconfig)"
        return 0
      fi
    else
      if echo "$ifconfig_output" | grep -E '\binet6 [0-9A-Fa-f:]+' | grep -Ev '\binet6 (fe80:|::1)' >/dev/null; then
        log "$LOG_INFO" "IPv${version} global interfaces found (ifconfig)"
        return 0
      fi
    fi
  else
    log "$LOG_WARN" "Neither ip nor ifconfig is available to check IPv${version} interfaces"
    return 1
  fi

  log "$LOG_ERROR" "No global IPv${version} addresses found on interfaces"
  return 1
}

check_ip_connectivity() {
  local version="$1"
  local test_hosts_v4=("8.8.8.8" "8.8.4.4" "1.1.1.1" "1.0.0.1" "9.9.9.9")
  local test_hosts_v6=("2001:4860:4860::8888" "2001:4860:4860::8844" "2606:4700:4700::1111" "2606:4700:4700::1001" "2620:fe::9")
  local timeout=3
  local count=1
  local test_hosts ping_cmd

  log "$LOG_INFO" "Checking IPv${version} connectivity"

  ping_cmd=($(get_ping_command "$version"))

  if [[ ${#ping_cmd[@]} -eq 0 ]]; then
    log "$LOG_ERROR" "Ping command for IPv${version} is not available"
    return 1
  fi

  if [[ "$version" == "4" ]]; then
    test_hosts=("${test_hosts_v4[@]}")
  else
    test_hosts=("${test_hosts_v6[@]}")
  fi

  for host in "${test_hosts[@]}"; do
    if "${ping_cmd[@]}" -c "$count" -W "$timeout" "$host" >/dev/null 2>&1; then
      log "$LOG_INFO" "IPv${version} connectivity confirmed via $host"
      return 0
    fi
  done

  log "$LOG_ERROR" "IPv${version} connectivity test failed"
  return 1
}

check_ip_dns() {
  local version="$1"
  local test_domain="google.com"
  local record_type
  local dig_result

  log "$LOG_INFO" "Checking IPv${version} DNS resolution"

  if [[ "$version" == "4" ]]; then
    record_type="A"
  else
    record_type="AAAA"
  fi

  if is_command_available "nslookup"; then
    if nslookup -type="$record_type" "$test_domain" >/dev/null 2>&1; then
      log "$LOG_INFO" "IPv${version} DNS resolution works via nslookup"
      return 0
    fi
  elif is_command_available "dig"; then
    dig_result=$(dig +short -t "$record_type" "$test_domain" 2>/dev/null)
    if [[ -n "$dig_result" ]]; then
      log "$LOG_INFO" "IPv${version} DNS resolution works via dig"
      return 0
    fi
  elif is_command_available "host"; then
    if host -t "$record_type" "$test_domain" >/dev/null 2>&1; then
      log "$LOG_INFO" "IPv${version} DNS resolution works via host"
      return 0
    fi
  else
    log "$LOG_WARN" "Neither nslookup, dig, nor host is available to check IPv${version} DNS resolution"
    return 1
  fi

  log "$LOG_ERROR" "IPv${version} DNS resolution failed"
  return 1
}

check_ip_support() {
  local version="$1"
  local -a checks=("interfaces" "connectivity" "dns")
  local -a failed=()

  spinner_update "IPv$version support"
  log "$LOG_INFO" "Starting comprehensive IPv${version} support check"

  for check in "${checks[@]}"; do
    if ! "check_ip_${check}" "$version"; then
      failed+=("$check")
    fi
  done

  if [[ ${#failed[@]} -eq 0 ]]; then
    log "$LOG_INFO" "IPv${version} is fully supported (${checks[*]})"
    return 0
  else
    log "$LOG_ERROR" "IPv${version} is not fully supported. Failed checks: ${failed[*]}"
    return 1
  fi
}

ipv4_enabled() {
  [[ "$IPV6_ONLY" != true ]] && [[ "$IPV4_SUPPORTED" -eq 0 ]]
}

ipv6_enabled() {
  [[ "$IPV4_ONLY" != true ]] && [[ "$IPV6_SUPPORTED" -eq 0 ]]
}

can_use_ipv4() {
  ipv4_enabled && [[ -n "$EXTERNAL_IPV4" ]]
}

can_use_ipv6() {
  ipv6_enabled && [[ "$IPV6_SUPPORTED" -eq 0 ]] && [[ -n "$EXTERNAL_IPV6" ]]
}

preferred_ip_version() {
  can_use_ipv4 && echo 4 || echo 6
}

preferred_ip() {
  can_use_ipv4 && echo "$EXTERNAL_IPV4" || echo "$EXTERNAL_IPV6"
}

# Cache Management Functions
load_ip_cache() {
  local ip_version="$1"
  local cache_content cached_ip cached_timestamp cached_source current_timestamp cache_age

  CACHE_LAST_SOURCE=""
  CACHE_LAST_TIMESTAMP=""

  if [[ "$CACHE_ENABLED" != true ]]; then
    return 1
  fi

  if [[ ! -f "$CACHE_FILE" ]]; then
    log "$LOG_DEBUG" "Cache file not found: $CACHE_FILE"
    return 1
  fi

  cache_content=$(cat "$CACHE_FILE" 2>/dev/null)

  if [[ -z "$cache_content" ]]; then
    log "$LOG_WARN" "Cache file is empty"
    return 1
  fi

  if ! is_valid_json "$cache_content"; then
    log "$LOG_WARN" "Cache file contains invalid JSON"
    return 1
  fi

  cached_ip=$(echo "$cache_content" | jq -r --arg v "ipv$ip_version" '.[$v].address // empty' 2>/dev/null)
  cached_timestamp=$(echo "$cache_content" | jq -r --arg v "ipv$ip_version" '.[$v].timestamp // empty' 2>/dev/null)
  cached_source=$(echo "$cache_content" | jq -r --arg v "ipv$ip_version" '.[$v].source // empty' 2>/dev/null)

  if [[ -z "$cached_ip" ]] || [[ -z "$cached_timestamp" ]]; then
    log "$LOG_WARN" "Cache missing IPv${ip_version} data"
    return 1
  fi

  current_timestamp=$(date +%s)
  cache_age=$((current_timestamp - cached_timestamp))

  if [[ $cache_age -gt $CACHE_TTL ]]; then
    log "$LOG_INFO" "Cached IPv${ip_version} expired (age: ${cache_age}s, TTL: ${CACHE_TTL}s)"
    return 1
  fi

  if [[ "$ip_version" == "4" ]]; then
    if ! is_valid_ipv4 "$cached_ip"; then
      log "$LOG_WARN" "Cached IPv4 address is invalid: $cached_ip"
      return 1
    fi
  else
    if ! is_valid_ipv6 "$cached_ip"; then
      log "$LOG_WARN" "Cached IPv6 address is invalid: $cached_ip"
      return 1
    fi
  fi

  log "$LOG_INFO" "Using cached IPv${ip_version} address: $cached_ip (age: ${cache_age}s)"
  CACHE_LAST_SOURCE="$cached_source"
  CACHE_LAST_TIMESTAMP="$cached_timestamp"
  echo "$cached_ip"
  return 0
}

save_ip_cache() {
  local ip_version="$1"
  local ip_address="$2"
  local service="$3"
  local cache_content current_timestamp
  local temp_file

  if [[ "$CACHE_ENABLED" != true ]]; then
    return 0
  fi

  if [[ -z "$ip_address" ]]; then
    log "$LOG_WARN" "Cannot save empty IP address to cache"
    return 1
  fi

  current_timestamp=$(date +%s)

  if [[ -f "$CACHE_FILE" ]]; then
    cache_content=$(cat "$CACHE_FILE" 2>/dev/null)
  fi

  if [[ -n "$cache_content" ]] && is_valid_json "$cache_content"; then
    cache_content=$(echo "$cache_content" | jq \
      --arg v "ipv$ip_version" \
      --arg ip "$ip_address" \
      --arg ts "$current_timestamp" \
      --arg src "$service" \
      '.[$v] = {address: $ip, timestamp: ($ts | tonumber), source: $src}')
  else
    cache_content=$(jq -n \
      --arg v "ipv$ip_version" \
      --arg ip "$ip_address" \
      --arg ts "$current_timestamp" \
      --arg src "$service" \
      '{version: 1, ($v): {address: $ip, timestamp: ($ts | tonumber), source: $src}}')
  fi

  mkdir -p "$CACHE_DIR"
  temp_file=$(mktemp "$CACHE_FILE.XXXXXX")
  echo "$cache_content" >"$temp_file"
  chmod 0600 "$temp_file"
  mv "$temp_file" "$CACHE_FILE"

  log "$LOG_INFO" "Saved IPv${ip_version} address to cache: $ip_address"
}

clear_ip_cache() {
  if [[ -f "$CACHE_FILE" ]]; then
    rm -f "$CACHE_FILE"
    log "$LOG_INFO" "Cache file cleared: $CACHE_FILE"
  fi
}

show_ip_cache() {
  local cache_content cached_ipv4 cached_ipv6 ipv4_ts ipv6_ts ipv4_src ipv6_src
  local current_timestamp ipv4_age ipv6_age ipv4_status ipv6_status

  if [[ ! -f "$CACHE_FILE" ]]; then
    echo "No cache file found at $CACHE_FILE"
    return 0
  fi

  cache_content=$(cat "$CACHE_FILE" 2>/dev/null)

  if [[ -z "$cache_content" ]] || ! is_valid_json "$cache_content"; then
    echo "Cache file exists but contains invalid data"
    return 1
  fi

  current_timestamp=$(date +%s)

  cached_ipv4=$(echo "$cache_content" | jq -r '.ipv4.address // empty' 2>/dev/null)
  cached_ipv6=$(echo "$cache_content" | jq -r '.ipv6.address // empty' 2>/dev/null)
  ipv4_ts=$(echo "$cache_content" | jq -r '.ipv4.timestamp // empty' 2>/dev/null)
  ipv6_ts=$(echo "$cache_content" | jq -r '.ipv6.timestamp // empty' 2>/dev/null)
  ipv4_src=$(echo "$cache_content" | jq -r '.ipv4.source // empty' 2>/dev/null)
  ipv6_src=$(echo "$cache_content" | jq -r '.ipv6.source // empty' 2>/dev/null)

  echo "IP Cache Contents"
  echo "================="
  echo "Cache file: $CACHE_FILE"
  echo "Cache TTL: ${CACHE_TTL}s"
  echo ""

  if [[ -n "$cached_ipv4" ]] && [[ -n "$ipv4_ts" ]]; then
    ipv4_age=$((current_timestamp - ipv4_ts))
    if [[ $ipv4_age -le $CACHE_TTL ]]; then
      ipv4_status="Valid"
    else
      ipv4_status="Expired"
    fi
    echo "IPv4: $cached_ipv4"
    echo "  Status: $ipv4_status"
    echo "  Age: ${ipv4_age}s"
    echo "  Source: $ipv4_src"
  else
    echo "IPv4: Not cached"
  fi
  echo ""

  if [[ -n "$cached_ipv6" ]] && [[ -n "$ipv6_ts" ]]; then
    ipv6_age=$((current_timestamp - ipv6_ts))
    if [[ $ipv6_age -le $CACHE_TTL ]]; then
      ipv6_status="Valid"
    else
      ipv6_status="Expired"
    fi
    echo "IPv6: $cached_ipv6"
    echo "  Status: $ipv6_status"
    echo "  Age: ${ipv6_age}s"
    echo "  Source: $ipv6_src"
  else
    echo "IPv6: Not cached"
  fi
}

shuffle_identity_services() {
  local i tmp size rand_idx
  size=${#IDENTITY_SERVICES[@]}

  for ((i = size - 1; i > 0; i--)); do
    rand_idx=$((RANDOM % (i + 1)))

    if ((rand_idx != i)); then
      tmp=${IDENTITY_SERVICES[i]}
      IDENTITY_SERVICES[i]=${IDENTITY_SERVICES[rand_idx]}
      IDENTITY_SERVICES[rand_idx]=$tmp
    fi
  done
}

fetch_ip_from_service_with_retry() {
  local service="$1"
  local ip_version="$2"
  local response attempt delay
  local max_retries=$IP_FETCH_MAX_RETRIES

  for ((attempt = 1; attempt <= max_retries; attempt++)); do
    response=$(curl_wrapper GET "https://$service" --ip-version "$ip_version")

    if [[ -n "$response" ]]; then
      echo "$response"
      return 0
    fi

    if ((attempt < max_retries)); then
      delay=$((IP_FETCH_RETRY_DELAY * (2 ** (attempt - 1))))
      log "$LOG_DEBUG" "Retry $attempt/$max_retries for $service after ${delay}s delay"
      sleep "$delay"
    fi
  done

  log "$LOG_WARN" "Failed to fetch IP from $service after $max_retries retries"
  return 1
}

fetch_external_ip() {
  local ip_version="$1"
  local service ip
  local now

  spinner_update "External IPv$ip_version address"
  log "$LOG_INFO" "Getting external IPv${ip_version} address"

  shuffle_identity_services

  for service in "${IDENTITY_SERVICES[@]}"; do
    ip=$(fetch_ip_from_service_with_retry "$service" "$ip_version")
    ip=${ip//$'\r'/}
    ip=${ip//$'\n'/}
    ip=${ip//[[:space:]]/}

    if [[ "$ip_version" == "4" ]]; then
      if ! is_valid_ipv4 "$ip"; then
        log "$LOG_WARN" "Invalid IPv4 from $service: $ip"
        continue
      fi
    else
      if ! is_valid_ipv6 "$ip"; then
        log "$LOG_WARN" "Invalid IPv6 from $service: $ip"
        continue
      fi
    fi

    if [[ -n "$ip" ]]; then
      log "$LOG_INFO" "Successfully obtained IPv${ip_version} address from $service: $ip"
      save_ip_cache "$ip_version" "$ip" "$service"
      now=$(date +%s)
      if [[ "$ip_version" == "4" ]]; then
        EXTERNAL_IPV4_SOURCE="$service"
        EXTERNAL_IPV4_CACHE_HIT=false
        EXTERNAL_IPV4_TIMESTAMP="$now"
      else
        EXTERNAL_IPV6_SOURCE="$service"
        EXTERNAL_IPV6_CACHE_HIT=false
        EXTERNAL_IPV6_TIMESTAMP="$now"
      fi
      echo "$ip"
      return 0
    else
      log "$LOG_WARN" "No response from $service for IPv${ip_version}"
    fi
  done

  log "$LOG_WARN" "Failed to obtain IPv${ip_version} address from any service, trying cache"
  ip=$(load_ip_cache "$ip_version")
  if [[ -n "$ip" ]]; then
    if [[ "$ip_version" == "4" ]]; then
      EXTERNAL_IPV4_SOURCE="${CACHE_LAST_SOURCE:-cache}"
      EXTERNAL_IPV4_CACHE_HIT=true
      EXTERNAL_IPV4_TIMESTAMP="$CACHE_LAST_TIMESTAMP"
    else
      EXTERNAL_IPV6_SOURCE="${CACHE_LAST_SOURCE:-cache}"
      EXTERNAL_IPV6_CACHE_HIT=true
      EXTERNAL_IPV6_TIMESTAMP="$CACHE_LAST_TIMESTAMP"
    fi
    echo "$ip"
    return 0
  fi

  log "$LOG_ERROR" "Failed to obtain IPv${ip_version} address from any service or cache"
  return 1
}

discover_external_ips() {
  local ipv4_failed=false ipv6_failed=false

  if ipv4_enabled; then
    EXTERNAL_IPV4=$(fetch_external_ip 4)
    if [[ -z "$EXTERNAL_IPV4" ]]; then
      ipv4_failed=true
    fi
  fi

  if ipv6_enabled; then
    EXTERNAL_IPV6=$(fetch_external_ip 6)
    if [[ -z "$EXTERNAL_IPV6" ]]; then
      ipv6_failed=true
    fi
  fi

  if [[ "$ipv4_failed" == true ]] && [[ "$ipv6_failed" == true ]]; then
    error_exit "Failed to obtain external IPv4 and IPv6 address. Check network connectivity, DNS settings, proxy configuration, and firewall rules."
  fi

  if [[ "$ipv4_failed" == true ]]; then
    log "$LOG_WARN" "IPv4 address not available, continuing with IPv6 only"
  fi

  if [[ "$ipv6_failed" == true ]]; then
    log "$LOG_WARN" "IPv6 address not available, continuing with IPv4 only"
  fi
}

load_maxmind_profile() {
  local ip_version="$1"
  local response

  if [[ "$ip_version" == "4" && -n "$MAXMIND_PROFILE_IPV4" ]]; then
    return 0
  fi

  if [[ "$ip_version" == "6" && -n "$MAXMIND_PROFILE_IPV6" ]]; then
    return 0
  fi

  response=$(curl_wrapper GET "https://geoip.maxmind.com/geoip/v2.1/city/me" \
    --header "Referer: https://www.maxmind.com" \
    --ip-version "$ip_version")

  if [[ -z "$response" ]] || ! is_valid_json "$response"; then
    log "$LOG_WARN" "Failed to load MaxMind profile for IPv${ip_version}"
    return 1
  fi

  if [[ "$ip_version" == "4" ]]; then
    MAXMIND_PROFILE_IPV4="$response"
  else
    MAXMIND_PROFILE_IPV6="$response"
  fi

  return 0
}

get_registered_country() {
  local ip_version="$1"
  local profile

  if ! load_maxmind_profile "$ip_version"; then
    echo ""
    return 1
  fi

  if [[ "$ip_version" == "4" ]]; then
    profile="$MAXMIND_PROFILE_IPV4"
  else
    profile="$MAXMIND_PROFILE_IPV6"
  fi

  process_json "$profile" ".registered_country.names.en"
}

get_asn() {
  local ip_version ip profile traits

  ip_version=$(preferred_ip_version)
  ip=$(preferred_ip)

  spinner_update "ASN info"
  log "$LOG_INFO" "Getting ASN info for IP $ip"
  ASN_NUMBER=""
  ASN_NAME=""

  if ! load_maxmind_profile "$ip_version"; then
    return 1
  fi

  if [[ "$ip_version" == "4" ]]; then
    profile="$MAXMIND_PROFILE_IPV4"
  else
    profile="$MAXMIND_PROFILE_IPV6"
  fi

  traits=$(process_json "$profile" ".traits")
  ASN_NUMBER=$(process_json "$traits" ".autonomous_system_number")
  ASN_NAME=$(process_json "$traits" ".autonomous_system_organization")

  log "$LOG_INFO" "ASN info: AS$ASN_NUMBER $ASN_NAME"
}

load_registered_countries() {
  if can_use_ipv4; then
    REGISTERED_COUNTRY_IPV4=$(get_registered_country 4)
  fi

  if can_use_ipv6; then
    REGISTERED_COUNTRY_IPV6=$(get_registered_country 6)
  fi
}

is_ipv6_over_ipv4_service() {
  local service="$1"
  for s in "${IPV6_OVER_IPV4_SERVICES[@]}"; do
    [[ "$s" == "$service" ]] && return 0
  done
  return 1
}

spinner_start() {
  local delay=0.1
  # shellcheck disable=SC1003
  local spinstr='|/-\\'
  local current_service

  spinner_running=true

  (
    while $spinner_running; do
      for ((i = 0; i < ${#spinstr}; i++)); do
        current_service=""

        if [[ -f "$SPINNER_SERVICE_FILE" ]]; then
          current_service="$(cat "$SPINNER_SERVICE_FILE")"
        fi

        printf "\r\033[K%s %s %s" \
          "$(color HEADER "${spinstr:$i:1}")" \
          "$(color HEADER "Checking:")" \
          "$(color SERVICE "$current_service")"

        sleep $delay
      done
    done
  ) &

  spinner_pid=$!
}

spinner_stop() {
  spinner_running=false

  if [[ -n "$spinner_pid" ]]; then
    if kill -0 "$spinner_pid" 2>/dev/null; then
      kill "$spinner_pid" 2>/dev/null
    fi
    wait "$spinner_pid" 2>/dev/null
    spinner_pid=""
    printf "\\r%*s\\r" 40 " "
  fi

  if [[ -f "$SPINNER_SERVICE_FILE" ]]; then
    rm -f "$SPINNER_SERVICE_FILE"
    unset SPINNER_SERVICE_FILE
  fi
}

spinner_update() {
  local value="$1"

  if [[ -n "$SPINNER_SERVICE_FILE" ]]; then
    echo "$value" >"$SPINNER_SERVICE_FILE"
  fi

  if [[ "$PROGRESS_LOG" == true && "$JSON_OUTPUT" != "true" ]]; then
    if [[ "$value" != "$LAST_PROGRESS_MSG" ]]; then
      LAST_PROGRESS_MSG="$value"
      printf "%s %s\n" \
        "$(color INFO '[INFO]')" \
        "Checking: $value" >&2
    fi
  fi
}

spinner_cleanup() {
  spinner_stop
  if [[ "$DEBUG" == true ]]; then
    cleanup_debug
  fi
  exit 130
}

curl_wrapper() {
  local method="$1"
  local url="$2"
  shift 2
  local ip_version user_agent json data data_urlencode file forms headers
  local response_with_meta response http_code latency_seconds latency_ms curl_exit
  local error_type=""
  local curl_args=(
    --silent
    --compressed
    --location
    --retry-connrefused
    --retry-all-errors
    --retry "$CURL_RETRIES"
    --max-time "$CURL_TIMEOUT"
    -w '\n%{http_code}\n%{time_total}'
  )

  case "$method" in
  HEAD)
    curl_args+=(--head)
    ;;
  *)
    curl_args+=(--request "$method")
    ;;
  esac

  while (($#)); do
    case "$1" in
    --ip-version)
      ip_version="$2"
      shift 2
      ;;
    --user-agent)
      user_agent="$2"
      shift 2
      ;;
    --header)
      headers+=("$2")
      shift 2
      ;;
    --json)
      json="$2"
      shift 2
      ;;
    --data)
      data="$2"
      shift 2
      ;;
    --data-urlencode)
      data_urlencode="$2"
      shift 2
      ;;
    --file)
      file="$2"
      shift 2
      ;;
    --form)
      forms+=("$2")
      shift 2
      ;;
    esac
  done

  if [[ "$ip_version" == "4" ]]; then
    curl_args+=(-4)
  else
    curl_args+=(-6)
  fi

  for h in "${headers[@]}"; do
    curl_args+=(-H "$h")
  done

  if [[ -n "$user_agent" ]]; then
    curl_args+=(-A "$user_agent")
  fi

  if [[ -n "$json" ]]; then
    curl_args+=(--json "$json")
  fi

  if [[ -n "$data" ]]; then
    curl_args+=(--data "$data")
  fi

  if [[ -n "$data_urlencode" ]]; then
    curl_args+=(--data-urlencode "$data_urlencode")
  fi

  if [[ -n "$file" ]]; then
    curl_args+=(--upload-file "$file")
  fi

  for f in "${forms[@]}"; do
    curl_args+=(-F "$f")
  done

  if [[ -n "$PROXY_ADDR" ]]; then
    curl_args+=(--proxy "socks5://$PROXY_ADDR")
  fi

  if [[ -n "$INTERFACE_NAME" ]]; then
    curl_args+=(--interface "$INTERFACE_NAME")
  fi

  curl_args+=("$url")

  response_with_meta=$(curl "${curl_args[@]}")
  curl_exit=$?
  if [[ $curl_exit -ne 0 || -z "$response_with_meta" ]]; then
    error_type="curl_error"
    if [[ -n "$CURL_META_FILE" ]]; then
      printf "http_code=;latency_ms=;error_type=%s\n" "$error_type" >"$CURL_META_FILE"
    fi
    log "$LOG_WARN" "curl request failed for $url"
    echo ""
    return
  fi

  latency_seconds=$(printf '%s\n' "$response_with_meta" | tail -n 1)
  http_code=$(printf '%s\n' "$response_with_meta" | tail -n 2 | head -n 1)
  response=$(printf '%s\n' "$response_with_meta" | sed '$d' | sed '$d')

  if [[ "$latency_seconds" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
    latency_ms=$(awk -v t="$latency_seconds" 'BEGIN { printf "%d", t * 1000 }')
  else
    latency_ms=""
  fi

  if [[ ! "$http_code" =~ ^[0-9]{3}$ ]]; then
    error_type="invalid_http_code"
    if [[ -n "$CURL_META_FILE" ]]; then
      printf "http_code=;latency_ms=%s;error_type=%s\n" "$latency_ms" "$error_type" >"$CURL_META_FILE"
    fi
    log "$LOG_WARN" "Unexpected HTTP code for $url"
    echo "$response"
    return
  fi

  if [[ "$http_code" =~ ^[45][0-9]{2}$ ]]; then
    error_type="http_${http_code}"
    if [[ -n "$CURL_META_FILE" ]]; then
      printf "http_code=%s;latency_ms=%s;error_type=%s\n" "$http_code" "$latency_ms" "$error_type" >"$CURL_META_FILE"
    fi
    status_from_http_code "$http_code"
    return 0
  fi

  if [[ -n "$CURL_META_FILE" ]]; then
    printf "http_code=%s;latency_ms=%s;error_type=none\n" "$http_code" "$latency_ms" >"$CURL_META_FILE"
  fi

  echo "$response"
}

service_build_request() {
  local service="$1" ip="$2" ip_version="$3"
  local cfg="${PRIMARY_SERVICES[$service]}"
  local display_name domain url_template url headers_str response_format

  IFS='|' read -r display_name domain url_template response_format <<<"$cfg"

  if [[ -z "$display_name" ]]; then
    display_name="$service"
  fi

  # ip-api.com free tier only supports HTTP, not HTTPS
  if [[ "$service" == "IPAPI_COM" ]]; then
    url="http://$domain${url_template//\{ip\}/$ip}"
  else
    url="https://$domain${url_template//\{ip\}/$ip}"
  fi

  if [[ -n "${SERVICE_HEADERS[$service]}" ]]; then
    headers_str="${SERVICE_HEADERS[$service]}"
  fi

  printf "%s\n%s\n%s\n%s" "$display_name" "$url" "${response_format:-json}" "$headers_str"
}

probe_service() {
  local service="$1"
  local ip_version="$2"
  local ip="$3"
  local built display_name url response_format headers_line request_params response

  mapfile -t built < <(service_build_request "$service" "$ip" "$ip_version")
  display_name="${built[0]}"
  url="${built[1]}"
  response_format="${built[2]}"
  headers_line="${built[3]}"

  if [[ -n "$headers_line" ]]; then
    IFS='||' read -ra hs <<<"$headers_line"
    for h in "${hs[@]}"; do
      if [[ -n "$h" ]]; then
        request_params+=(--header "$h")
      fi
    done
  fi

  if [[ "$ip_version" == "6" ]] && is_ipv6_over_ipv4_service "$service"; then
    ip_version="4"
  fi

  response=$(curl_wrapper GET "$url" "${request_params[@]}" --ip-version "$ip_version")

  process_response "$service" "$response" "$display_name" "$response_format"
}

process_response() {
  local service="$1"
  local response="$2"
  local display_name="$3"
  local response_format="${4:-json}"
  local jq_filter

  if is_status_string "$response"; then
    echo "$response"
    return
  fi

  if [[ -z "$response" || "$response" == *"<html"* ]]; then
    echo "$STATUS_NA"
    return
  fi

  if [[ "$response_format" == "plain" ]]; then
    echo "$response" | tr -d '\r\n '
    return
  fi

  if ! is_valid_json "$response"; then
    log "$LOG_ERROR" "Invalid JSON response from $display_name: $response"
    return 1
  fi

  case "$service" in
  MAXMIND)
    jq_filter='.country.iso_code'
    ;;
  RIPE)
    jq_filter='.country'
    ;;
  IP2LOCATION_IO)
    jq_filter='.country_code'
    ;;
  IPINFO_IO)
    jq_filter='.data.country'
    ;;
  IPAPI_CO)
    jq_filter='.country'
    ;;
  CLOUDFLARE)
    jq_filter='.country'
    ;;
  COUNTRY_IS)
    jq_filter='.country'
    ;;
  GEOJS_IO)
    jq_filter='.[0].country'
    ;;
  IPAPI_IS)
    jq_filter='.location.country_code'
    ;;
  IPBASE_COM)
    jq_filter='.data.location.country.alpha2'
    ;;
  IPQUERY_IO)
    jq_filter='.location.country_code'
    ;;
  IPWHOIS_IO)
    jq_filter='.country_code'
    ;;
  IPWHO_IS)
    jq_filter='.country_code'
    ;;
  IPAPI_COM)
    jq_filter='.countryCode'
    ;;
  DETECTOR404)
    jq_filter='.data.country.ccode'
    ;;
  *)
    echo "$response"
    ;;
  esac

  process_json "$response" "$jq_filter"
}

read_curl_metric_field() {
  local file="$1"
  local key="$2"

  if [[ ! -f "$file" ]]; then
    echo ""
    return
  fi

  sed -n "s/.*${key}=\\([^;]*\\).*/\\1/p" "$file" | head -n1
}

build_service_metric() {
  local metric_file="$1"
  local transport_version="$2"
  local http_code latency_ms error_type

  http_code=$(read_curl_metric_field "$metric_file" "http_code")
  latency_ms=$(read_curl_metric_field "$metric_file" "latency_ms")
  error_type=$(read_curl_metric_field "$metric_file" "error_type")

  if [[ -z "$error_type" ]]; then
    error_type="none"
  fi

  printf "http_code=%s;latency_ms=%s;transport_ip_version=%s;error_type=%s" \
    "$http_code" "$latency_ms" "$transport_version" "$error_type"
}

process_with_custom_handler() {
  local service="$1"
  local display_name="$2"
  local handler_func="${PRIMARY_SERVICES_CUSTOM_HANDLERS[$service]}"
  local ipv4_result=""
  local ipv6_result=""
  local ipv4_metric=""
  local ipv6_metric=""
  local metric_file

  if can_use_ipv4; then
    log "$LOG_INFO" "Checking $display_name via IPv4 (custom handler)"
    metric_file=$(mktemp "${TMPDIR:-/tmp}/ipregion_metric_XXXXXX")
    CURL_META_FILE="$metric_file"
    ipv4_result=$("$handler_func" 4 4)
    ipv4_metric=$(build_service_metric "$metric_file" 4)
    rm -f "$metric_file"
    CURL_META_FILE=""
  fi

  if can_use_ipv6; then
    local transport=6
    local log_msg="Checking $display_name via IPv6 (custom handler)"

    if is_ipv6_over_ipv4_service "$service"; then
      transport=4
      log_msg="Checking $display_name (IPv6 address, IPv4 transport) (custom handler)"
    fi

    log "$LOG_INFO" "$log_msg"
    metric_file=$(mktemp "${TMPDIR:-/tmp}/ipregion_metric_XXXXXX")
    CURL_META_FILE="$metric_file"
    ipv6_result=$("$handler_func" "$transport" 6)
    ipv6_metric=$(build_service_metric "$metric_file" "$transport")
    rm -f "$metric_file"
    CURL_META_FILE=""
  fi

  add_result "primary" "$display_name" "$ipv4_result" "$ipv6_result" "$ipv4_metric" "$ipv6_metric"
}

process_with_probe() {
  local service="$1"
  local display_name="$2"
  local ipv4_result=""
  local ipv6_result=""
  local ipv4_metric=""
  local ipv6_metric=""
  local metric_file
  local transport

  if can_use_ipv4; then
    log "$LOG_INFO" "Checking $display_name via IPv4"
    metric_file=$(mktemp "${TMPDIR:-/tmp}/ipregion_metric_XXXXXX")
    CURL_META_FILE="$metric_file"
    ipv4_result=$(probe_service "$service" 4 "$EXTERNAL_IPV4")
    ipv4_metric=$(build_service_metric "$metric_file" 4)
    rm -f "$metric_file"
    CURL_META_FILE=""
  fi

  if can_use_ipv6; then
    transport=6
    local log_msg="Checking $display_name via IPv6"

    if is_ipv6_over_ipv4_service "$service"; then
      transport=4
      log_msg="Checking $display_name (IPv6 address, IPv4 transport)"
    fi

    log "$LOG_INFO" "$log_msg"
    metric_file=$(mktemp "${TMPDIR:-/tmp}/ipregion_metric_XXXXXX")
    CURL_META_FILE="$metric_file"
    ipv6_result=$(probe_service "$service" 6 "$EXTERNAL_IPV6")
    ipv6_metric=$(build_service_metric "$metric_file" "$transport")
    rm -f "$metric_file"
    CURL_META_FILE=""
  fi

  add_result "primary" "$display_name" "$ipv4_result" "$ipv6_result" "$ipv4_metric" "$ipv6_metric"
}

process_service() {
  local service="$1"
  local custom="${2:-false}"
  local service_config="${PRIMARY_SERVICES[$service]}"
  local display_name domain url_template response_format handler_func

  IFS='|' read -r display_name domain url_template response_format <<<"$service_config"
  display_name="${display_name:-$service}"

  if [[ "$custom" == true ]]; then
    process_custom_service "$service"
    return
  fi

  spinner_update "$display_name"

  if [[ -n "${PRIMARY_SERVICES_CUSTOM_HANDLERS[$service]}" ]]; then
    process_with_custom_handler "$service" "$display_name"
    return
  fi

  process_with_probe "$service" "$display_name"
}

process_custom_service() {
  local service="$1"
  local ipv4_result=""
  local ipv6_result=""
  local ipv4_metric=""
  local ipv6_metric=""
  local display_name handler_func group
  local metric_file

  if [[ -n "${CUSTOM_SERVICES[$service]}" ]]; then
    display_name="${CUSTOM_SERVICES[$service]}"
    handler_func="${CUSTOM_SERVICES_HANDLERS[$service]}"
    group="custom"
  else
    display_name="$service"
    handler_func="${CUSTOM_SERVICES_HANDLERS[$service]}"
    group="custom"
  fi

  spinner_update "$display_name"

  if [[ -z "$handler_func" ]]; then
    log "$LOG_WARN" "Unknown service handler: $service"
    return
  fi

  if can_use_ipv4; then
    log "$LOG_INFO" "Checking $display_name via IPv4"
    metric_file=$(mktemp "${TMPDIR:-/tmp}/ipregion_metric_XXXXXX")
    CURL_META_FILE="$metric_file"
    ipv4_result=$("$handler_func" 4)
    ipv4_metric=$(build_service_metric "$metric_file" 4)
    rm -f "$metric_file"
    CURL_META_FILE=""
  fi

  if can_use_ipv6; then
    log "$LOG_INFO" "Checking $display_name via IPv6"
    metric_file=$(mktemp "${TMPDIR:-/tmp}/ipregion_metric_XXXXXX")
    CURL_META_FILE="$metric_file"
    ipv6_result=$("$handler_func" 6)
    ipv6_metric=$(build_service_metric "$metric_file" 6)
    rm -f "$metric_file"
    CURL_META_FILE=""
  fi

  add_result "$group" "$display_name" "$ipv4_result" "$ipv6_result" "$ipv4_metric" "$ipv6_metric"
}

run_service_group() {
  local group="$1"
  local services_string="${SERVICE_GROUPS[$group]}"
  local is_custom=false
  local services_array service_name handler_func display_name result

  read -ra services_array <<<"$services_string"

  log "$LOG_INFO" "Running $group group services"

  for service_name in "${services_array[@]}"; do
    if should_skip_service "$service_name"; then
      log "$LOG_INFO" "Skipping service: $service_name"
      continue
    fi

    case "$group" in
    custom)
      is_custom=true
      ;;
    esac

    if [[ "$is_custom" == true ]]; then
      process_service "$service_name" true
    else
      process_service "$service_name"
    fi
  done
}

run_service_group_parallel() {
  local group="$1"
  local services_string="${SERVICE_GROUPS[$group]}"
  local is_custom=false
  local services_array service_name temp_dir result_file

  read -ra services_array <<<"$services_string"

  if [[ "$group" == "custom" ]]; then
    is_custom=true
  fi

  temp_dir=$(mktemp -d "${TMPDIR:-/tmp}/ipregion_parallel_XXXXXX")

  for service_name in "${services_array[@]}"; do
    if should_skip_service "$service_name"; then
      log "$LOG_INFO" "Skipping service: $service_name"
      continue
    fi

    result_file="$temp_dir/$service_name"
    (
      RESULT_FILE="$result_file"
      if [[ "$is_custom" == true ]]; then
        process_service "$service_name" true
      else
        process_service "$service_name"
      fi
    ) &

    register_parallel_pid "$!"
    wait_for_parallel_slot
  done

  if [[ "$PROGRESS_LOG" == true && "$JSON_OUTPUT" != "true" ]]; then
    printf "%s %s\n" \
      "$(color INFO '[INFO]')" \
      "Waiting for remaining $group checks..." >&2
  fi

  local pid
  local -a remaining_group_pids=("${PARALLEL_PIDS[@]}")
  for pid in "${remaining_group_pids[@]}"; do
    wait "$pid" 2>/dev/null || true
  done
  PARALLEL_PIDS=()

  for service_name in "${services_array[@]}"; do
    if should_skip_service "$service_name"; then
      continue
    fi

    result_file="$temp_dir/$service_name"
    if [[ -f "$result_file" ]]; then
      while IFS= read -r line; do
        [[ -n "$line" ]] && add_result_line "$line"
      done <"$result_file"
    fi
  done

  rm -rf "$temp_dir"
}

run_all_services() {
  local service_name

  for func in $(declare -F | awk '{print $3}' | grep_wrapper '^lookup_'); do
    service_name=${func#lookup_}
    service_name_uppercase=${service_name^^}

    if should_skip_service "$service_name_uppercase"; then
      log "$LOG_INFO" "Skipping service: $service_name_uppercase"
      continue
    fi

    if [[ -n "${CUSTOM_SERVICES[$service_name_uppercase]}" ]]; then
      process_service "$service_name_uppercase" true
      continue
    fi

    "$func"
  done
}

finalize_json() {
  local t_primary t_custom
  local IFS=$'\n'

  if ((${#ARR_PRIMARY[@]} > 0)); then
    t_primary=$(printf '%s\n' "${ARR_PRIMARY[@]//|||/$'\t'}")
  fi

  if ((${#ARR_CUSTOM[@]} > 0)); then
    t_custom=$(printf '%s\n' "${ARR_CUSTOM[@]//|||/$'\t'}")
  fi

  RESULT_JSON=$(
    jq -n \
      --rawfile p <(printf "%s" "$t_primary") \
      --rawfile c <(printf "%s" "$t_custom") \
      --arg ipv4 "$EXTERNAL_IPV4" \
      --arg ipv6 "$EXTERNAL_IPV6" \
      --arg ipv4_source "$EXTERNAL_IPV4_SOURCE" \
      --arg ipv6_source "$EXTERNAL_IPV6_SOURCE" \
      --arg ipv4_cache_hit "$EXTERNAL_IPV4_CACHE_HIT" \
      --arg ipv6_cache_hit "$EXTERNAL_IPV6_CACHE_HIT" \
      --arg ipv4_ts "$EXTERNAL_IPV4_TIMESTAMP" \
      --arg ipv6_ts "$EXTERNAL_IPV6_TIMESTAMP" \
      --arg registered_country_ipv4 "$REGISTERED_COUNTRY_IPV4" \
      --arg registered_country_ipv6 "$REGISTERED_COUNTRY_IPV6" \
      --arg asn_number "$ASN_NUMBER" \
      --arg asn_name "$ASN_NAME" \
      --arg version "1" '
        def parse_metric($raw):
          if ($raw | length) == 0 then null
          else
            ($raw | split(";") | map(select(length > 0) | split("="))) as $pairs
            | ($pairs | map({key: .[0], value: (.[1] // "")}) | from_entries) as $m
            | {
                http_code: ($m.http_code | if . == null or . == "" then null else (tonumber?) end),
                latency_ms: ($m.latency_ms | if . == null or . == "" then null else (tonumber?) end),
                transport_ip_version: ($m.transport_ip_version | if . == null or . == "" then null else (tonumber?) end),
                error_type: ($m.error_type | if . == null or . == "" or . == "none" then null else . end)
              }
          end;

        def lines_to_array($raw):
          if ($raw | length) == 0 then [] else
          ($raw | split("\n"))
          | map(select(length > 0))
          | map(
              (split("\t")) as $f
              | {
                  service: $f[0],
                  ipv4: ( ($f[1] // "") | if length>0 then . else null end ),
                  ipv6: ( ($f[2] // "") | if length>0 then . else null end ),
                  metrics: {
                    ipv4: parse_metric($f[3] // ""),
                    ipv6: parse_metric($f[4] // "")
                  }
                }
            )
          end;

        def country_consensus($rows; $field):
          ($rows
           | map(if $field == "ipv4" then .ipv4 else .ipv6 end)
           | map(select(. != null and (. | test("^[A-Z]{2}$"))))
          ) as $codes
          | ($codes | length) as $total
          | if $total == 0 then null
            else
              ($codes | group_by(.) | map({country: .[0], count: length}) | sort_by(-.count, .country) | .[0]) as $top
              | {
                  country: $top.country,
                  count: $top.count,
                  total: $total,
                  confidence: (($top.count / $total) * 100 | floor)
                }
            end;

        (lines_to_array($p)) as $primary
        | (lines_to_array($c)) as $custom
        | {
          version: ($version|tonumber),
          ip: {
            ipv4: {
              address: ($ipv4 | select(length > 0) // null),
              source: ($ipv4_source | select(length > 0) // null),
              cache_hit: ($ipv4_cache_hit == "true"),
              timestamp: ($ipv4_ts | if length > 0 then (tonumber?) else null end),
              registered_country: ($registered_country_ipv4 | select(length > 0) // null)
            },
            ipv6: {
              address: ($ipv6 | select(length > 0) // null),
              source: ($ipv6_source | select(length > 0) // null),
              cache_hit: ($ipv6_cache_hit == "true"),
              timestamp: ($ipv6_ts | if length > 0 then (tonumber?) else null end),
              registered_country: ($registered_country_ipv6 | select(length > 0) // null)
            }
          },
          asn: {
            number: ($asn_number | if length > 0 then (tonumber?) else null end),
            name: ($asn_name | select(length > 0) // null)
          },
          consensus: {
            ipv4: country_consensus($primary; "ipv4"),
            ipv6: country_consensus($primary; "ipv6")
          },
          results: {
            primary: $primary,
            custom:  $custom
          }
        }
      '
  )
}

add_result() {
  local group="$1"
  local service="$2"
  local ipv4="$3"
  local ipv6="$4"
  local ipv4_metric="$5"
  local ipv6_metric="$6"

  ipv4=${ipv4//$'\n'/}
  ipv4=${ipv4//$'\t'/ }
  ipv6=${ipv6//$'\n'/}
  ipv6=${ipv6//$'\t'/ }
  ipv4_metric=${ipv4_metric//$'\n'/}
  ipv4_metric=${ipv4_metric//$'\t'/ }
  ipv6_metric=${ipv6_metric//$'\n'/}
  ipv6_metric=${ipv6_metric//$'\t'/ }

  if [[ "$PROGRESS_LOG" == true && "$JSON_OUTPUT" != "true" ]]; then
    printf "%s %s\n" \
      "$(color INFO '[INFO]')" \
      "Done: $service" >&2
  fi

  if [[ -n "${RESULT_FILE:-}" ]]; then
    printf "%s|||%s|||%s|||%s|||%s|||%s\n" "$group" "$service" "$ipv4" "$ipv6" "$ipv4_metric" "$ipv6_metric" >>"$RESULT_FILE"
    return
  fi

  case "$group" in
  primary) ARR_PRIMARY+=("$service|||$ipv4|||$ipv6|||$ipv4_metric|||$ipv6_metric") ;;
  custom) ARR_CUSTOM+=("$service|||$ipv4|||$ipv6|||$ipv4_metric|||$ipv6_metric") ;;
  esac
}

add_result_line() {
  local line="$1"
  local group rest service ipv4 ipv6 ipv4_metric ipv6_metric

  group="${line%%|||*}"
  rest="${line#*|||}"
  service="${rest%%|||*}"
  rest="${rest#*|||}"
  ipv4="${rest%%|||*}"
  rest="${rest#*|||}"
  ipv6="${rest%%|||*}"
  rest="${rest#*|||}"
  ipv4_metric="${rest%%|||*}"
  ipv6_metric="${rest#*|||}"

  case "$group" in
  primary) ARR_PRIMARY+=("$service|||$ipv4|||$ipv6|||$ipv4_metric|||$ipv6_metric") ;;
  custom) ARR_CUSTOM+=("$service|||$ipv4|||$ipv6|||$ipv4_metric|||$ipv6_metric") ;;
  esac
}

print_table_group() {
  local group="$1"
  local group_title="$2"
  local na="N/A"
  local show_ipv4=0
  local show_ipv6=0
  local separator=$'\t'

  if can_use_ipv4; then
    show_ipv4=1
  fi

  if can_use_ipv6; then
    show_ipv6=1
  fi

  printf "%s\n\n" "$(color HEADER "$group_title")"

  if is_command_available "column"; then
    {
      printf "%s" "$(color TABLE_HEADER 'Service')"

      if [[ $show_ipv4 -eq 1 ]]; then
        printf "%s%s" "$separator" "$(color TABLE_HEADER 'IPv4')"
      fi

      if [[ $show_ipv6 -eq 1 ]]; then
        printf "%s%s" "$separator" "$(color TABLE_HEADER 'IPv6')"
      fi

      printf "\n"

      jq -r --arg group "$group" '
        (.results // {}) as $r
        | ($r[$group] // [])
        | .[]
        | [ .service, (.ipv4 // "N/A"), (.ipv6 // "N/A") ]
        | @tsv
      ' <<<"$RESULT_JSON" | while IFS=$'\t' read -r s v4 v6; do

        printf "%s" "$(color SERVICE "$s")"

        if [[ $show_ipv4 -eq 1 ]]; then
          if [[ "$v4" == "null" || -z "$v4" ]]; then
            v4="$na"
          fi
          printf "%s%s" "$separator" "$(format_value "$v4")"
        fi

        if [[ $show_ipv6 -eq 1 ]]; then
          if [[ "$v6" == "null" || -z "$v6" ]]; then
            v6="$na"
          fi
          printf "%s%s" "$separator" "$(format_value "$v6")"
        fi

        printf "\n"
      done
    } | column -t -s "$separator"
  else
    log "$LOG_WARN" "column is not available; displaying unaligned output"
    {
      printf "%s" "$(color TABLE_HEADER 'Service')"

      if [[ $show_ipv4 -eq 1 ]]; then
        printf "%s%s" "$separator" "$(color TABLE_HEADER 'IPv4')"
      fi

      if [[ $show_ipv6 -eq 1 ]]; then
        printf "%s%s" "$separator" "$(color TABLE_HEADER 'IPv6')"
      fi

      printf "\n"

      jq -r --arg group "$group" '
        (.results // {}) as $r
        | ($r[$group] // [])
        | .[]
        | [ .service, (.ipv4 // "N/A"), (.ipv6 // "N/A") ]
        | @tsv
      ' <<<"$RESULT_JSON" | while IFS=$'\t' read -r s v4 v6; do

        printf "%s" "$(color SERVICE "$s")"

        if [[ $show_ipv4 -eq 1 ]]; then
          if [[ "$v4" == "null" || -z "$v4" ]]; then
            v4="$na"
          fi
          printf "%s%s" "$separator" "$(format_value "$v4")"
        fi

        if [[ $show_ipv6 -eq 1 ]]; then
          if [[ "$v6" == "null" || -z "$v6" ]]; then
            v6="$na"
          fi
          printf "%s%s" "$separator" "$(format_value "$v6")"
        fi

        printf "\n"
      done
    }
  fi
}

print_metrics_group() {
  local group="$1"
  local title="$2"
  local separator=$'\t'
  local show_ipv4=0
  local show_ipv6=0

  if [[ "$SHOW_METRICS" != true ]]; then
    return
  fi

  if can_use_ipv4; then
    show_ipv4=1
  fi

  if can_use_ipv6; then
    show_ipv6=1
  fi

  printf "%s\n\n" "$(color HEADER "$title")"
  if is_command_available "column"; then
    {
      printf "%s" "$(color TABLE_HEADER 'Service')"
      if [[ $show_ipv4 -eq 1 ]]; then
        printf "%s%s" "$separator" "$(color TABLE_HEADER 'IPv4 metric')"
      fi
      if [[ $show_ipv6 -eq 1 ]]; then
        printf "%s%s" "$separator" "$(color TABLE_HEADER 'IPv6 metric')"
      fi
      printf "\n"

      jq -r --arg group "$group" '
        (.results // {}) as $r
        | ($r[$group] // [])
        | .[]
        | [
            .service,
            (
              .metrics.ipv4
              | if . == null then "N/A" else
                  ((.http_code // "n/a")|tostring) + " / " +
                  ((.latency_ms // "n/a")|tostring) + "ms / " +
                  ((.error_type // "ok")|tostring)
                end
            ),
            (
              .metrics.ipv6
              | if . == null then "N/A" else
                  ((.http_code // "n/a")|tostring) + " / " +
                  ((.latency_ms // "n/a")|tostring) + "ms / " +
                  ((.error_type // "ok")|tostring)
                end
            )
          ]
        | @tsv
      ' <<<"$RESULT_JSON" | while IFS=$'\t' read -r s m4 m6; do
        printf "%s" "$(color SERVICE "$s")"
        if [[ $show_ipv4 -eq 1 ]]; then
          printf "%s%s" "$separator" "$m4"
        fi
        if [[ $show_ipv6 -eq 1 ]]; then
          printf "%s%s" "$separator" "$m6"
        fi
        printf "\n"
      done
    } | column -t -s "$separator"
    return
  fi

  log "$LOG_WARN" "column is not available; displaying unaligned metrics output"
  {
    printf "%s" "$(color TABLE_HEADER 'Service')"
    if [[ $show_ipv4 -eq 1 ]]; then
      printf "%s%s" "$separator" "$(color TABLE_HEADER 'IPv4 metric')"
    fi
    if [[ $show_ipv6 -eq 1 ]]; then
      printf "%s%s" "$separator" "$(color TABLE_HEADER 'IPv6 metric')"
    fi
    printf "\n"

    jq -r --arg group "$group" '
      (.results // {}) as $r
      | ($r[$group] // [])
      | .[]
      | [
          .service,
          (
            .metrics.ipv4
            | if . == null then "N/A" else
                ((.http_code // "n/a")|tostring) + " / " +
                ((.latency_ms // "n/a")|tostring) + "ms / " +
                ((.error_type // "ok")|tostring)
              end
          ),
          (
            .metrics.ipv6
            | if . == null then "N/A" else
                ((.http_code // "n/a")|tostring) + " / " +
                ((.latency_ms // "n/a")|tostring) + "ms / " +
                ((.error_type // "ok")|tostring)
              end
          )
        ]
      | @tsv
    ' <<<"$RESULT_JSON" | while IFS=$'\t' read -r s m4 m6; do
      printf "%s" "$(color SERVICE "$s")"
      if [[ $show_ipv4 -eq 1 ]]; then
        printf "%s%s" "$separator" "$m4"
      fi
      if [[ $show_ipv6 -eq 1 ]]; then
        printf "%s%s" "$separator" "$m6"
      fi
      printf "\n"
    done
  }
}

print_header() {
  local ipv4 ipv6
  local na="N/A"
  local asn_value asn_name_value asn_display
  local consensus_ipv4 consensus_ipv6

  ipv4=$(process_json "$RESULT_JSON" ".ip.ipv4.address")
  ipv6=$(process_json "$RESULT_JSON" ".ip.ipv6.address")
  consensus_ipv4=$(process_json "$RESULT_JSON" ".consensus.ipv4.country")
  consensus_ipv6=$(process_json "$RESULT_JSON" ".consensus.ipv6.country")

  if [[ "$ipv4" != "null" ]]; then
    printf "%s: %s, %s %s\n" "$(color HEADER 'IPv4')" "$(bold "$(mask_ipv4 "$ipv4")")" "registered in" "$(bold "${REGISTERED_COUNTRY_IPV4:-$STATUS_NA}")"
  fi

  if [[ "$ipv6" != "null" ]]; then
    printf "%s: %s, %s %s\n" "$(color HEADER 'IPv6')" "$(bold "$(mask_ipv6 "$ipv6")")" "registered in" "$(bold "${REGISTERED_COUNTRY_IPV6:-$STATUS_NA}")"
  fi

  asn_value="$ASN_NUMBER"
  asn_name_value="$ASN_NAME"

  if [[ -z "$asn_value" || "$asn_value" == "null" ]]; then
    asn_display="$na"
  elif [[ -z "$asn_name_value" || "$asn_name_value" == "null" ]]; then
    asn_display="AS$asn_value"
  else
    asn_display="AS$asn_value $asn_name_value"
  fi

  printf "%s: %s\n\n" "$(color HEADER 'ASN')" "$(bold "$asn_display")"

  if [[ "$consensus_ipv4" != "null" && -n "$consensus_ipv4" ]]; then
    printf "%s: %s\n" "$(color HEADER 'IPv4 consensus')" "$(bold "$consensus_ipv4")"
  fi

  if [[ "$consensus_ipv6" != "null" && -n "$consensus_ipv6" ]]; then
    printf "%s: %s\n" "$(color HEADER 'IPv6 consensus')" "$(bold "$consensus_ipv6")"
  fi

  if [[ "$consensus_ipv4" != "null" || "$consensus_ipv6" != "null" ]]; then
    printf "\n"
  fi
}

detect_output_format() {
  local output_file="$1"
  local lower_file
  lower_file="${output_file,,}"

  if [[ "$lower_file" == *.csv ]]; then
    echo "csv"
    return 0
  fi

  if [[ "$lower_file" == *.json ]]; then
    echo "json"
    return 0
  fi

  return 1
}

write_output_file() {
  local output_format

  if [[ -z "$OUTPUT_FILE" ]]; then
    return 0
  fi

  output_format=$(detect_output_format "$OUTPUT_FILE")
  if [[ -z "$output_format" ]]; then
    error_exit "Unsupported output format for $OUTPUT_FILE. Use .json or .csv"
  fi

  mkdir -p "$(dirname "$OUTPUT_FILE")"

  if [[ "$output_format" == "json" ]]; then
    printf "%s\n" "$RESULT_JSON" | jq >"$OUTPUT_FILE"
  else
    jq -r '
      ["group","service","ipv4","ipv6","ipv4_http_code","ipv4_latency_ms","ipv4_transport_ip_version","ipv4_error_type","ipv6_http_code","ipv6_latency_ms","ipv6_transport_ip_version","ipv6_error_type"],
      (.results.primary[] | [
        "primary",
        .service,
        (.ipv4 // ""),
        (.ipv6 // ""),
        (.metrics.ipv4.http_code // ""),
        (.metrics.ipv4.latency_ms // ""),
        (.metrics.ipv4.transport_ip_version // ""),
        (.metrics.ipv4.error_type // ""),
        (.metrics.ipv6.http_code // ""),
        (.metrics.ipv6.latency_ms // ""),
        (.metrics.ipv6.transport_ip_version // ""),
        (.metrics.ipv6.error_type // "")
      ]),
      (.results.custom[] | [
        "custom",
        .service,
        (.ipv4 // ""),
        (.ipv6 // ""),
        (.metrics.ipv4.http_code // ""),
        (.metrics.ipv4.latency_ms // ""),
        (.metrics.ipv4.transport_ip_version // ""),
        (.metrics.ipv4.error_type // ""),
        (.metrics.ipv6.http_code // ""),
        (.metrics.ipv6.latency_ms // ""),
        (.metrics.ipv6.transport_ip_version // ""),
        (.metrics.ipv6.error_type // "")
      ])
      | @csv
    ' <<<"$RESULT_JSON" >"$OUTPUT_FILE"
  fi

  if [[ "$JSON_OUTPUT" != true ]]; then
    printf "%s %s\n" \
      "$(color INFO '[INFO]')" \
      "Saved output to $OUTPUT_FILE" >&2
  fi
}

print_results() {
  finalize_json
  write_output_file

  if [[ "$JSON_OUTPUT" == true ]]; then
    echo "$RESULT_JSON" | jq
    return
  fi

  local restart_spinner=false
  if [[ "$spinner_running" == true ]]; then
    restart_spinner=true
    spinner_stop
  fi

  if [[ "$HEADER_PRINTED" != true ]]; then
    print_header
  fi

  case "$GROUPS_TO_SHOW" in
  primary)
    print_table_group "primary" "GeoIP services"
    print_metrics_group "primary" "GeoIP service metrics"
    ;;
  custom)
    print_table_group "custom" "Popular services"
    print_metrics_group "custom" "Popular service metrics"
    ;;
  *)
    print_table_group "custom" "Popular services"
    print_metrics_group "custom" "Popular service metrics"
    printf "\n"
    print_table_group "primary" "GeoIP services"
    print_metrics_group "primary" "GeoIP service metrics"
    ;;
  esac

  if [[ "$restart_spinner" == true ]]; then
    spinner_start
  fi
}

lookup_maxmind() {
  process_service "MAXMIND"
}

lookup_ripe() {
  process_service "RIPE"
}

lookup_ip2location_io() {
  process_service "IP2LOCATION_IO"
}

lookup_ipinfo_io() {
  process_service "IPINFO_IO"
}

lookup_ipapi_co() {
  process_service "IPAPI_CO"
}

lookup_cloudflare() {
  process_service "CLOUDFLARE"
}

lookup_ifconfig_co() {
  process_service "IFCONFIG_CO"
}

lookup_detector404() {
  process_service "DETECTOR404"
}

lookup_iplocation_com() {
  local ip_version="$1"
  local response ip

  if [[ "$ip_version" -eq 4 ]]; then
    ip="$EXTERNAL_IPV4"
  else
    ip="$EXTERNAL_IPV6"
  fi

  if [[ -z "$ip" ]]; then
    log "$LOG_WARN" "Skipping iplocation.com lookup: empty IP for IPv${ip_version}"
    echo ""
    return
  fi

  if [[ "$ip_version" -eq 4 ]]; then
    if ! is_valid_ipv4 "$ip"; then
      log "$LOG_WARN" "Skipping iplocation.com lookup: invalid IPv4 address"
      echo ""
      return
    fi
  else
    if ! is_valid_ipv6 "$ip"; then
      log "$LOG_WARN" "Skipping iplocation.com lookup: invalid IPv6 address"
      echo ""
      return
    fi
  fi

  response=$(curl_wrapper POST "https://iplocation.com" --ip-version "$ip_version" --user-agent "$USER_AGENT" --data-urlencode "ip=$ip")
  process_json "$response" ".country_code"
}

lookup_google() {
  local ip_version="$1"
  local response

  response=$(curl_wrapper GET "https://www.google.com" \
    --user-agent "$USER_AGENT" \
    --ip-version "$ip_version")

  grep_wrapper --perl '"MgUcDb":"\K[^"]*' <<<"$response"
}

lookup_youtube() {
  local ip_version="$1"
  local response json_result

  response=$(curl_wrapper GET "https://www.youtube.com/sw.js_data" --ip-version "$ip_version")

  json_result=$(tail -n +3 <<<"$response")
  process_json "$json_result" ".[0][2][0][0][1]"
}

lookup_youtube_premium() {
  local ip_version="$1"
  local response is_available

  response=$(curl_wrapper GET "https://www.youtube.com/premium" \
    --ip-version "$ip_version" \
    --user-agent "$USER_AGENT" \
    --header "Accept-Language: en-US,en;q=0.9")

  if [[ -z "$response" ]]; then
    echo ""
    return
  fi

  is_available=$(grep_wrapper -io "youtube premium is not available in your country" <<<"$response")

  if [[ -z "$is_available" ]]; then
    is_available="Yes"
    color_name="SERVICE"
  else
    is_available="No"
    color_name="HEART"
  fi

  print_value_or_colored "$is_available" "$color_name"
}

lookup_google_search_captcha() {
  local ip_version="$1"
  local response is_captcha color_name

  response=$(curl_wrapper GET "https://www.google.com/search?q=cats" --ip-version "$ip_version" \
    --user-agent "$USER_AGENT" \
    --header "Accept-Language: en-US,en;q=0.9")

  if [[ -z "$response" ]]; then
    echo ""
    return
  fi

  is_captcha=$(grep_wrapper -iE "unusual traffic from|is blocked|unaddressed abuse" <<<"$response")

  if [[ -z "$is_captcha" ]]; then
    is_captcha="No"
    color_name="SERVICE"
  else
    is_captcha="Yes"
    color_name="HEART"
  fi

  print_value_or_colored "$is_captcha" "$color_name"
}

parse_gemini_user_status_code() {
  local batch_response="$1"
  local line inner_json status

  if [[ -z "$batch_response" ]]; then
    echo ""
    return
  fi

  line=$(grep_wrapper '^\[\["wrb.fr","otAQ7b"' <<<"$batch_response" | head -n1)

  if [[ -z "$line" ]]; then
    echo ""
    return
  fi

  inner_json=$(process_json "$line" '.[0][2]')

  if [[ -z "$inner_json" ]]; then
    echo ""
    return
  fi

  status=$(process_json "$inner_json" '.[14]')
  echo "$status"
}

gemini_batch_needs_stream_probe() {
  local batch_response="$1"
  local line inner_json status session_ok enabled_models

  if [[ -z "$batch_response" ]]; then
    echo ""
    return
  fi

  line=$(grep_wrapper '^\[\["wrb.fr","otAQ7b"' <<<"$batch_response" | head -n1)

  if [[ -z "$line" ]]; then
    echo ""
    return
  fi

  inner_json=$(process_json "$line" '.[0][2]')

  if [[ -z "$inner_json" ]]; then
    echo ""
    return
  fi

  status=$(process_json "$inner_json" '.[14]')

  case "$status" in
  1060 | 1014 | 1033)
    echo "no"
    ;;
  1000 | 1040)
    echo "stream"
    ;;
  1016)
    session_ok=$(process_json "$inner_json" '.[2]')
    enabled_models=$(process_json "$inner_json" '
      [.[15][]? | select(type == "array" and (.[7] == true or .[7] == "true"))] | length
    ')
    if [[ "$session_ok" == "true" && "$enabled_models" =~ ^[0-9]+$ && "$enabled_models" -gt 0 ]]; then
      echo "stream"
    else
      echo "no"
    fi
    ;;
  *)
    echo ""
    ;;
  esac
}

gemini_build_stream_generate_freq() {
  local uuid

  if [[ -r /proc/sys/kernel/random/uuid ]]; then
    uuid=$(</proc/sys/kernel/random/uuid)
  else
    uuid=$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n' | sed -E 's/(.{8})(.{4})(.{4})(.{4})(.{12})/\1-\2-\3-\4-\5/')
  fi

  jq -nc --arg uuid "$uuid" '
    (
      [range(80)] | map(null)
      | .[0] = ["hi", 0, null, null, null, null, 0]
      | .[1] = ["en"]
      | .[2] = ["", "", "", null, null, null, null, null, null, ""]
      | .[6] = [0]
      | .[7] = 1
      | .[10] = 1
      | .[11] = 0
      | .[17] = [[4]]
      | .[18] = 0
      | .[27] = 1
      | .[30] = [4]
      | .[41] = [2]
      | .[53] = 0
      | .[59] = $uuid
      | .[61] = []
      | .[68] = 1
      | .[79] = 1
    ) as $inner
    | [null, ($inner | tojson)]
  '
}

gemini_stream_generate_has_reply() {
  local response="$1"
  local line inner_json reply blocked_code

  if [[ -z "$response" ]]; then
    echo "no"
    return
  fi

  if grep_wrapper -qiE 'not available in your|isn.t available|not supported in your|unsupported country|location.rejected' <<<"$response"; then
    echo "no"
    return
  fi

  while IFS= read -r line; do
    [[ ! "$line" =~ ^\[\[\"wrb\.fr\" ]] && continue

    inner_json=$(process_json "$line" '.[0][2]')

    if [[ -z "$inner_json" || ${#inner_json} -lt 50 ]]; then
      continue
    fi

    blocked_code=$(process_json "$inner_json" '
      [.. | numbers | select(. == 1060 or . == 1014 or . == 1033)] | first // empty
    ')

    if [[ -n "$blocked_code" ]]; then
      echo "no"
      return
    fi

    reply=$(process_json "$inner_json" '
      [
        .. | arrays
        | select(
            length >= 2
            and (.[0] | type) == "string"
            and (.[0] | startswith("rc_"))
            and (.[1] | type) == "array"
          )
        | .[1][]
        | select(type == "string" and length > 1)
      ] | last // empty
    ')

    if [[ -n "$reply" ]]; then
      echo "yes"
      return
    fi
  done < <(grep_wrapper '^\[\["wrb.fr"' <<<"$response")

  echo "no"
}

lookup_gemini() {
  local ip_version="$1"
  local response batch_response sid bl encoded_bl availability color_name reqid batch_url
  local probe_status stream_freq stream_url stream_response stream_reply

  response=$(curl_wrapper GET "https://gemini.google.com" \
    --user-agent "$USER_AGENT" \
    --ip-version "$ip_version")

  if [[ -z "$response" ]]; then
    echo ""
    return
  fi

  sid=$(grep_wrapper --perl '"FdrFJe":"\K[^"]*' <<<"$response")
  bl=$(grep_wrapper --perl '"cfb2h":"\K[^"]*' <<<"$response")

  if [[ -z "$sid" || -z "$bl" ]]; then
    echo ""
    return
  fi

  encoded_bl=$(jq -nr --arg bl "$bl" '$bl|@uri')
  reqid=$((RANDOM % 900000 + 100000))
  batch_url="https://gemini.google.com/_/BardChatUi/data/batchexecute?rpcids=otAQ7b&source-path=%2Fapp&bl=${encoded_bl}&f.sid=${sid}&hl=en&_reqid=${reqid}&rt=c"

  batch_response=$(curl_wrapper POST "$batch_url" \
    --ip-version "$ip_version" \
    --user-agent "$USER_AGENT" \
    --header "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" \
    --header "Origin: https://gemini.google.com" \
    --header "Referer: https://gemini.google.com/" \
    --header "X-Same-Domain: 1" \
    --data 'f.req=%5B%5B%5B%22otAQ7b%22%2C%22%5B%5D%22%2Cnull%2C%22generic%22%5D%5D%5D')

  probe_status=$(gemini_batch_needs_stream_probe "$batch_response")

  case "$probe_status" in
  stream)
    stream_freq=$(gemini_build_stream_generate_freq)
    if [[ -z "$stream_freq" ]]; then
      echo ""
      return
    fi

    reqid=$((reqid + 1))
    stream_url="https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate?bl=${encoded_bl}&hl=en&_reqid=${reqid}&rt=c"
    stream_response=$(curl_wrapper POST "$stream_url" \
      --ip-version "$ip_version" \
      --user-agent "$USER_AGENT" \
      --header "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" \
      --header "Origin: https://gemini.google.com" \
      --header "Referer: https://gemini.google.com/app" \
      --header "X-Same-Domain: 1" \
      --data-urlencode "f.req=$stream_freq")

    stream_reply=$(gemini_stream_generate_has_reply "$stream_response")
    if [[ "$stream_reply" == "yes" ]]; then
      availability="Yes"
    else
      availability="No"
    fi
    ;;
  no)
    availability="No"
    ;;
  *)
    echo ""
    return
    ;;
  esac

  if [[ "$availability" == "Yes" ]]; then
    color_name="SERVICE"
  else
    color_name="HEART"
  fi

  print_value_or_colored "$availability" "$color_name"
}

lookup_apple() {
  local ip_version="$1"
  curl_wrapper GET "https://gspe1-ssl.ls.apple.com/pep/gcc" --ip-version "$ip_version"
}

lookup_steam() {
  local ip_version="$1"
  local response

  response=$(curl_wrapper HEAD "https://store.steampowered.com" --ip-version "$ip_version")
  grep_wrapper --perl 'steamCountry=\K[^%;]*' <<<"$response"
}

lookup_tiktok() {
  local ip_version="$1"
  local response

  response=$(curl_wrapper GET "https://www.tiktok.com/api/v1/web-cookie-privacy/config?appId=1988" --ip-version "$ip_version")
  process_json "$response" ".body.appProps.region"
}

lookup_ookla_speedtest() {
  local ip_version="$1"
  local response

  response=$(curl_wrapper GET "https://www.speedtest.net/api/js/config-sdk" --ip-version "$ip_version")
  process_json "$response" ".location.countryCode"
}

lookup_jetbrains() {
  local ip_version="$1"
  local response

  response=$(curl_wrapper GET "https://data.services.jetbrains.com/geo" --ip-version "$ip_version")
  process_json "$response" ".code"
}

lookup_playstation() {
  local ip_version="$1"
  local response

  response=$(curl_wrapper HEAD "https://www.playstation.com" --ip-version "$ip_version")
  grep_wrapper --perl 'country=\K[^;]*' <<<"$response" | head -n1
}

lookup_microsoft() {
  local ip_version="$1"
  local response

  response=$(curl_wrapper GET "https://login.live.com" --ip-version "$ip_version")
  grep_wrapper --perl '"sRequestCountry":"\K[^"]*' <<<"$response"
}

main() {
  parse_arguments "$@"

  # Handle cache management flags early
  if [[ "$CACHE_SHOWN" == true ]]; then
    show_ip_cache
    exit 0
  fi

  if [[ "$CACHE_CLEARED" == true ]]; then
    clear_ip_cache
    exit 0
  fi

  setup_debug

  trap spinner_cleanup EXIT INT TERM

  if ((PARALLEL_JOBS <= 0)); then
    PARALLEL_JOBS=$(detect_parallel_jobs)
  fi

  print_startup_message

  if [[ "$JSON_OUTPUT" != true ]]; then
    printf "%s %s\n" \
      "$(color INFO '[INFO]')" \
      "Checking dependencies..." >&2
  fi

  detect_distro
  install_dependencies

  if [[ "$JSON_OUTPUT" != "true" && "$VERBOSE" != "true" && "$PROGRESS_LOG" != true && ("$PARALLEL_JOBS" -le 1 || "$FORCE_SPINNER" == true) ]]; then
    spinner_start
  fi

  if ipv4_enabled; then
    check_ip_support 4
    IPV4_SUPPORTED=$?
  fi

  if ipv6_enabled; then
    check_ip_support 6
    IPV6_SUPPORTED=$?
  fi

  if [[ "$IPV6_ONLY" == true && "$IPV6_SUPPORTED" -ne 0 ]]; then
    error_exit "IPv6 is not supported on this system"
  fi

  discover_external_ips
  load_registered_countries
  get_asn

  if [[ "$JSON_OUTPUT" != "true" ]]; then
    local restart_spinner=false
    if [[ "$spinner_running" == true ]]; then
      restart_spinner=true
      spinner_stop
    fi

    finalize_json
    print_header
    HEADER_PRINTED=true

    if [[ "$restart_spinner" == true ]]; then
      spinner_start
    fi
  fi

  case "$GROUPS_TO_SHOW" in
  primary)
    if ((PARALLEL_JOBS > 1)); then
      run_service_group_parallel "primary"
    else
      run_service_group "primary"
    fi
    ;;
  custom)
    if ((PARALLEL_JOBS > 1)); then
      run_service_group_parallel "custom"
    else
      run_service_group "custom"
    fi
    ;;
  *)
    if ((PARALLEL_JOBS > 1)); then
      run_service_group_parallel "primary"
      run_service_group_parallel "custom"
    else
      run_service_group "primary"
      run_service_group "custom"
    fi
    ;;
  esac

  if [[ "$PROGRESS_LOG" == true && "$JSON_OUTPUT" != "true" ]]; then
    printf "%s %s\n" \
      "$(color INFO '[INFO]')" \
      "Rendering results..." >&2
  fi

  if [[ "$JSON_OUTPUT" != "true" && "$VERBOSE" != "true" && "$PROGRESS_LOG" != true && ("$PARALLEL_JOBS" -le 1 || "$FORCE_SPINNER" == true) ]]; then
    spinner_stop
  fi

  print_results

  cleanup_debug

  trap - EXIT INT TERM
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  main "$@"
fi
