#!/usr/bin/env bash
#
# usenet-stack-check.sh — a READ-ONLY health check for a Usenet download stack.
#
# What it does:
#   Checks the things that actually cause "my Usenet is slow/broken" complaints:
#   client API reachability, provider connectivity on port 563, DNS, disk
#   headroom, RAM headroom and the default download paths.
#
# What it does NOT do:
#   It changes nothing. No files are created, no settings are touched, no
#   services are restarted. It only reads and reports. Safe to run on any box,
#   including one you actually use for downloads.
#
# Usage:
#   ./usenet-stack-check.sh
#   SAB_SERVER=news.example.com ./usenet-stack-check.sh
#
# Optional environment overrides:
#   SAB_URL      e.g. http://localhost:8080      (SABnzbd web UI)
#   SAB_KEY      SABnzbd API key                  (enables API check)
#   NZB_URL      e.g. http://localhost:6789      (NZBGet web UI)
#   NZB_USER     NZBGet username
#   NZB_PASS     NZBGet password
#   SAB_SERVER   your provider's news server hostname (port 563 checked)
#   DL_PATH      your downloads/completed directory
#   INCOMPLETE   your incomplete directory
#
# Exit codes: 0 = all checks passed, 1 = one or more warnings/failures.

set -uo pipefail

WARN=0
OK=0

c_ok()   { printf '  \033[32m[ ok ]\033[0m %s\n' "$1"; OK=$((OK+1)); }
c_warn() { printf '  \033[33m[warn]\033[0m %s\n' "$1"; WARN=$((WARN+1)); }
c_fail() { printf '  \033[31m[fail]\033[0m %s\n' "$1"; WARN=$((WARN+1)); }
hdr()    { printf '\n\033[1m%s\033[0m\n' "$1"; }

have() { command -v "$1" >/dev/null 2>&1; }

SAB_URL="${SAB_URL:-http://localhost:8080}"
SAB_KEY="${SAB_KEY:-}"
NZB_URL="${NZB_URL:-http://localhost:6789}"
NZB_USER="${NZB_USER:-}"
NZB_PASS="${NZB_PASS:-}"
SAB_SERVER="${SAB_SERVER:-news.newsgroupdirect.com}"
DL_PATH="${DL_PATH:-}"
INCOMPLETE="${INCOMPLETE:-}"

printf '\033[1mUsenet stack check\033[0m — read-only, changes nothing.\n'
printf 'Run at %s\n' "$(date -u '+%Y-%m-%d %H:%M:%SZ')"

# ---------------------------------------------------------------- tools
hdr "Required tools"
if have curl; then c_ok "curl present"; else c_warn "curl not found — HTTP checks will be skipped"; fi
if have nc; then c_ok "nc present"; else c_warn "nc not found — falling back to bash /dev/tcp for port checks"; fi
if have dig; then c_ok "dig present"; else c_warn "dig not found — DNS check will use getent"; fi

# ---------------------------------------------------------------- client API
hdr "Download client"

if have curl; then
  if [ -n "$SAB_KEY" ]; then
    body="$(curl -fsS --max-time 8 "${SAB_URL}/api?mode=version&output=json&apikey=${SAB_KEY}" 2>/dev/null)"
    if [ -n "$body" ]; then
      c_ok "SABnzbd API reachable at ${SAB_URL} (${body})"
    else
      c_warn "SABnzbd API not answering at ${SAB_URL} (check URL / API key)"
    fi
  else
    code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 "${SAB_URL}/" 2>/dev/null)"
    if [ "$code" != "000" ] && [ -n "$code" ]; then
      c_ok "SABnzbd web UI responds at ${SAB_URL} (HTTP ${code}); set SAB_KEY for a deeper check"
    else
      c_warn "nothing answering at ${SAB_URL} — is SABnzbd running?"
    fi
  fi

  if [ -n "$NZB_USER" ] && [ -n "$NZB_PASS" ]; then
    code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 -u "${NZB_USER}:${NZB_PASS}" "${NZB_URL}/" 2>/dev/null)"
    if [ "$code" != "000" ] && [ -n "$code" ]; then
      c_ok "NZBGet web UI responds at ${NZB_URL} (HTTP ${code})"
    else
      c_warn "NZBGet not answering at ${NZB_URL}"
    fi
  else
    code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 "${NZB_URL}/" 2>/dev/null)"
    if [ "$code" != "000" ] && [ -n "$code" ]; then
      c_ok "NZBGet web UI responds at ${NZB_URL} (HTTP ${code})"
    else
      c_warn "nothing answering at ${NZB_URL} — is NZBGet running? (set NZB_USER/NZB_PASS if it needs auth)"
    fi
  fi
fi

# ---------------------------------------------------------------- provider
hdr "Provider connectivity"

if have dig; then
  ip="$(dig +short +time=3 +tries=1 "$SAB_SERVER" A 2>/dev/null | head -n1)"
else
  ip="$(getent hosts "$SAB_SERVER" 2>/dev/null | awk '{print $1}' | head -n1)"
fi
if [ -n "$ip" ]; then
  c_ok "DNS resolves ${SAB_SERVER} -> ${ip}"
else
  c_fail "DNS could not resolve ${SAB_SERVER}"
fi

port_open() { # host port timeout
  if have nc; then
    nc -z -w "$3" "$1" "$2" >/dev/null 2>&1
  else
    (exec 3<>"/dev/tcp/$1/$2") >/dev/null 2>&1 && exec 3<&- 3>&-
  fi
}

if port_open "$SAB_SERVER" 563 8; then
  c_ok "port 563 (SSL) reachable on ${SAB_SERVER}"
else
  c_fail "port 563 NOT reachable on ${SAB_SERVER} — wrong server, blocked port, or no route"
fi

if port_open "$SAB_SERVER" 119 5; then
  c_warn "port 119 (plaintext) is also open — you should use 563, not 119"
else
  c_ok "port 119 (plaintext) closed — good, use 563"
fi

# ---------------------------------------------------------------- disk
hdr "Disk and memory"

check_path() { # path label
  local p="$1" label="$2"
  if [ -z "$p" ]; then return; fi
  if [ -d "$p" ]; then
    local avail
    avail="$(df -Pk "$p" 2>/dev/null | awk 'NR==2 {print $4}')"
    if [ -n "$avail" ]; then
      local gb=$((avail / 1024 / 1024))
      if [ "$gb" -lt 10 ]; then
        c_warn "${label} (${p}) has only ${gb} GB free — downloads will fail"
      else
        c_ok "${label} (${p}) has ${gb} GB free"
      fi
    fi
  else
    c_warn "${label} (${p}) does not exist"
  fi
}

if [ -n "$DL_PATH" ] || [ -n "$INCOMPLETE" ]; then
  check_path "$DL_PATH" "completed/downloads dir"
  check_path "$INCOMPLETE" "incomplete dir"
else
  for p in "$HOME/Downloads" "$HOME/downloads" "/downloads" "/data/downloads"; do
    if [ -d "$p" ]; then check_path "$p" "found download dir"; break; fi
  done
  [ -n "$DL_PATH" ] || printf '  \033[33m[warn]\033[0m no DL_PATH set and no obvious download dir found (set DL_PATH=...)\n'
fi

if [ -r /proc/meminfo ]; then
  memavail="$(awk '/MemAvailable/ {print int($2/1024)}' /proc/meminfo)"
  if [ -n "$memavail" ]; then
    if [ "$memavail" -lt 1024 ]; then
      c_warn "only ${memavail} MB RAM available — a large article cache will not fit"
    else
      c_ok "${memavail} MB RAM available for the article cache"
    fi
  fi
fi

# ---------------------------------------------------------------- summary
hdr "Summary"
printf '  passed: %d   warnings/failures: %d\n' "$OK" "$WARN"
if [ "$WARN" -eq 0 ]; then
  printf '\033[32mAll checks passed.\033[0m Nothing was changed.\n'
  exit 0
else
  printf '\033[33m%d item(s) need attention.\033[0m Nothing was changed.\n' "$WARN"
  exit 1
fi