#!/usr/bin/env bash
# gw-audit.sh — gateway / WAN / internet + overall site-health audit (controller-side, read-only).
# Uses the controller's own stat/health (wan/www/wlan/lan/vpn subsystems) + the gateway device's
# per-WAN detail. Reports WAN status + IP + uplink speed, internet latency/drops/last-speedtest,
# gateway CPU/mem/uptime, and the adoption/health rollup (APs/switches adopted vs disconnected vs
# pending, client counts). Flags: WAN/internet not-ok, high latency/drops, disconnected or pending
# devices, gateway resource pressure. No AP cred / VPN needed -> any UOS site. (ROADMAP C)
#
# Sites with a third-party firewall (e.g. pfSense) show num_gw=0 -> "no UniFi gateway" (still reports
# wlan/lan/adoption health).
#
# Usage: bash .claude/skills/unifi-wifi/scripts/gw-audit.sh <site-name|id>
set -uo pipefail
REPO="$(git rev-parse --show-toplevel 2>/dev/null || echo .)"
UOS="$REPO/.claude/scripts/uos-mongo.sh"; VAULT="$REPO/.claude/scripts/vault.sh"
# Mandatory skill error logging (skill-creator rule): log GENUINE functional failures only.
logerr(){ bash "$REPO/.claude/scripts/log-skill-error.sh" "unifi-wifi/gw-audit" "$1" --context "${2:-}" >/dev/null 2>&1 || true; }
HOST="${UOS_HOST:-172.16.3.29}"; PORT="${UOS_HTTPS_PORT:-11443}"
SITEARG="${1:?usage: gw-audit.sh <site-name|id> [--pfsense <slug>]}"; shift || true
PFARG=""   # optional: pfSense client slug (or full vault path) when UOS site name != client slug
while [ $# -gt 0 ]; do case "$1" in --pfsense) PFARG="${2:?--pfsense needs a slug/vault-path}"; shift 2;; *) shift;; esac; done
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
# §D least-privilege: this audit is READ-ONLY, so prefer a read-only controller cred when one is vaulted;
# fall back to the RW admin (which does double duty today). Vault the RO cred at infrastructure/
# uos-server-network-api and it is used automatically — no code change needed (see references/onboarding.md).
CREDP=""
for c in infrastructure/uos-server-network-api infrastructure/uos-server-network-api-rw; do
  if [ -n "$(bash "$VAULT" get-field "$c" credentials.password 2>/dev/null)" ]; then CREDP="$c"; break; fi
done
[ -n "$CREDP" ] || { echo "[ERROR] no controller cred (infrastructure/uos-server-network-api[-rw])"; logerr "no UOS controller cred vaulted" "tried RO+RW"; exit 1; }
case "$CREDP" in *-rw) echo "[INFO] using RW controller cred for read-only audit (vault a read-only cred at infrastructure/uos-server-network-api for least privilege)";; esac
U="$(bash "$VAULT" get-field "$CREDP" credentials.username 2>/dev/null)"
P="$(bash "$VAULT" get-field "$CREDP" credentials.password 2>/dev/null)"
[ -n "$U" ] && [ -n "$P" ] || { echo "[ERROR] incomplete controller cred at vault:$CREDP"; exit 1; }
base="https://$HOST:$PORT"; CJ="$TMP/cj"
code=$(curl -sk -c "$CJ" -o /dev/null -w '%{http_code}' -X POST "$base/api/auth/login" -H 'Content-Type: application/json' \
  --data-binary "$(python -c 'import json,sys;print(json.dumps({"username":sys.argv[1],"password":sys.argv[2]}))' "$U" "$P")")
[ "$code" = "200" ] || { echo "[ERROR] controller login HTTP $code"; logerr "UOS controller login failed (HTTP $code)" "host=$HOST:$PORT site=$SITEARG"; exit 1; }
SHORT="$(curl -sk -b "$CJ" "$base/proxy/network/api/self/sites" | python -c "
import sys,json; d=json.load(sys.stdin).get('data',[]); q='''$SITEARG'''.lower()
for s in d:
  if s.get('_id')=='''$SITEARG''' or s.get('name')=='''$SITEARG''' or q in (s.get('desc','').lower()): print(s.get('name')); break
")"; [ -n "$SHORT" ] || SHORT="$SITEARG"
echo "[INFO] gateway/health audit: site=$SHORT"
curl -sk -b "$CJ" "$base/proxy/network/api/s/$SHORT/stat/health" -o "$TMP/health.json"
curl -sk -b "$CJ" "$base/proxy/network/api/s/$SHORT/stat/device" -o "$TMP/dev.json"
export NGW_FILE="$TMP/ngw"
python - "$TMP/health.json" "$TMP/dev.json" <<'PY'
import sys,json,os
H={h['subsystem']:h for h in json.load(open(sys.argv[1])).get('data',[])}
devs=json.load(open(sys.argv[2])).get('data',[])
flags=[]
def st(x): return (x or 'unknown')
wan=H.get('wan',{}); www=H.get('www',{}); wlan=H.get('wlan',{}); lan=H.get('lan',{}); vpn=H.get('vpn',{})

print("\n== WAN / Internet ==")
ngw=wan.get('num_gw',0)
try: open(os.environ['NGW_FILE'],'w').write(str(ngw))
except Exception: pass
if not ngw:
    print("  no UniFi gateway at this site (third-party firewall, e.g. pfSense)")
else:
    gwdev=next((d for d in devs if d.get('type') in ('ugw','uxg','udm')),None)
    gwname=wan.get('gw_name') or (gwdev and (gwdev.get('name') or gwdev.get('model'))) or '?'
    gwmodel=(gwdev and gwdev.get('model')) or ''
    print(f"  gateway: {gwname} [{gwmodel}] ({wan.get('gw_version')})  status={st(wan.get('status'))}")
    print(f"  WAN IP: {wan.get('wan_ip')}  gateways: {wan.get('gateways')}  nameservers: {wan.get('nameservers')}")
    gs=wan.get('gw_system-stats') or {}
    if gs: print(f"  gw load: cpu={gs.get('cpu')}% mem={gs.get('mem')}% uptime={int(int(gs.get('uptime',0))/86400)}d")
    if st(wan.get('status'))!='ok': flags.append(f"WAN status={wan.get('status')}")
    try:
        if float(gs.get('cpu',0))>85: flags.append(f"gateway CPU {gs.get('cpu')}%")
        if float(gs.get('mem',0))>90: flags.append(f"gateway MEM {gs.get('mem')}%")
    except: pass
# internet (www)
print(f"  internet: status={st(www.get('status'))} latency={www.get('latency')}ms drops={www.get('drops')} "
      f"speedtest={www.get('xput_down')}/{www.get('xput_up')} Mbps ping={www.get('speedtest_ping')}ms")
if ngw and www and st(www.get('status'))!='ok': flags.append(f"internet(www) status={www.get('status')}")
try:
    if www.get('latency') and float(www.get('latency'))>80: flags.append(f"internet latency {www.get('latency')}ms")
    if www.get('drops') and float(www.get('drops'))>5: flags.append(f"internet drops={www.get('drops')}")
except: pass
# per-WAN device detail (multi-WAN)
for d in devs:
    if d.get('type') in ('ugw','uxg','udm'):
        for wk in ('wan1','wan2'):
            w=d.get(wk) or {}
            if w.get('enable'): print(f"  {wk}: ip={w.get('ip')} up={w.get('up')} {w.get('speed')}M{'' if w.get('full_duplex',True) else ' HALF-DUPLEX'}")
            if w.get('enable') and not w.get('up'): flags.append(f"{wk} DOWN")

print("\n== Adoption / device health ==")
for label,sub in (('APs (wlan)',wlan),('switches (lan)',lan),('gateway (wan)',wan)):
    a=sub.get('num_adopted'); dc=sub.get('num_disconnected'); pe=sub.get('num_pending')
    if a is None and dc is None: continue
    print(f"  {label}: adopted={a} disconnected={dc} pending={pe}")
    if dc: flags.append(f"{label}: {dc} disconnected")
    if pe: flags.append(f"{label}: {pe} pending adoption")
print(f"  clients: users={wlan.get('num_user')} guests={wlan.get('num_guest')} iot={wlan.get('num_iot')}")
# outdated firmware (from device list)
outd=[d.get('name') for d in devs if d.get('upgradable')]
if outd: flags.append(f"{len(outd)} device(s) with firmware upgrade available")

print("\n== FLAGS ==")
if flags:
    for f in flags: print(f"  [!] {f}")
else:
    print("  [OK] gateway/WAN/adoption all healthy")
PY

# ---------- pfSense gateway augmentation (ROADMAP §E: dispatch when there's no UniFi gateway) ----------
# A pfSense site shows num_gw=0 above (no UniFi gateway). If a pfSense REST API cred is vaulted, run the
# pfSense gateway/WAN/DHCP audit via the backend so one `gw-audit <site>` covers either gateway vendor.
NGW="$(cat "$TMP/ngw" 2>/dev/null || echo 1)"
if [ "$NGW" = "0" ]; then
  # PREFERRED: SSH backend (Mike 2026-06-16). Target = full vault path if --pfsense contains '/'
  # (option A, Mike 2026-06-21), else a client slug -> clients/<slug>/pfsense-firewall.
  ssh_target=""; ssh_port=""
  # 1. explicit --pfsense full vault path (option A)
  case "$PFARG" in
    */*) if [ -n "$(bash "$VAULT" get-field "$PFARG" credentials.password 2>/dev/null || bash "$VAULT" get-field "$PFARG" password 2>/dev/null)" ]; then ssh_target="$PFARG"; fi ;;
  esac
  # 2. explicit --pfsense client slug
  if [ -z "$ssh_target" ] && [ -n "$PFARG" ]; then case "$PFARG" in
    */*) ;; *) [ -n "$(bash "$VAULT" get-field "clients/$PFARG/pfsense-firewall" credentials.password 2>/dev/null)" ] && ssh_target="$PFARG";; esac; fi
  # 3. AUTO-SELECT from the gateway map (no --pfsense needed): bind by resolved site name/id
  if [ -z "$ssh_target" ] && mapres="$(bash "$REPO/.claude/skills/unifi-wifi/scripts/gateway-map.sh" lookup "$SITEARG" 2>/dev/null)"; then
    IFS=$'\t' read -r ssh_target ssh_port <<< "$mapres"
    [ -n "$ssh_target" ] && echo; [ -n "$ssh_target" ] && echo "[INFO] site in gateway map -> auto-selected pfSense cred '$ssh_target'"
  fi
  # 4. SITEARG itself a client slug
  if [ -z "$ssh_target" ]; then case "$SITEARG" in
    */*) ;; *) [ -n "$(bash "$VAULT" get-field "clients/$SITEARG/pfsense-firewall" credentials.password 2>/dev/null)" ] && ssh_target="$SITEARG";; esac; fi
  if [ -n "$ssh_target" ]; then
    echo; echo "[INFO] pfSense gateway (SSH backend, target '$ssh_target') -> pfSense gateway audit:"
    if [ -n "$ssh_port" ] && [ "$ssh_port" != "-" ]; then
      bash "$REPO/.claude/skills/unifi-wifi/scripts/pfsense-ssh.sh" "$ssh_target" audit --port "$ssh_port" || true
    else
      bash "$REPO/.claude/skills/unifi-wifi/scripts/pfsense-ssh.sh" "$ssh_target" audit || true
    fi
  else
    # REST fallback (dormant): only if a pfSense-api cred is vaulted.
    cands=()
    [ -n "$PFARG" ] && { case "$PFARG" in */*) cands+=("$PFARG");; *) cands+=("clients/$PFARG/pfsense-api");; esac; }
    cands+=("clients/$SITEARG/pfsense-api")
    pf_vp=""
    for cand in "${cands[@]}"; do
      if [ -n "$(bash "$VAULT" get-field "$cand" credentials.apikey 2>/dev/null || bash "$VAULT" get-field "$cand" apikey 2>/dev/null)" ]; then pf_vp="$cand"; break; fi
    done
    if [ -n "$pf_vp" ]; then
      echo; echo "[INFO] pfSense gateway REST cred found (vault:$pf_vp) -> pfSense gateway audit (dormant REST path):"
      bash "$REPO/.claude/skills/unifi-wifi/scripts/pfsense-backend.sh" "$pf_vp" audit || true
    else
      echo; echo "[INFO] gateway is third-party (pfSense?). For pfSense WAN/DHCP/firewall audit, vault an SSH"
      echo "       cred at clients/<slug>/pfsense-firewall (host + credentials.username/password), then re-run"
      echo "       (pass --pfsense <slug> if the UOS site name differs from the client slug)."
    fi
  fi
fi
