#!/usr/bin/env bash
# =============================================================================
# preflight.sh — run this ON A NEW ROBOT before installing the hand stack.
#
# Answers one question: can this robot run the Inspire hand stack, and if not,
# what exactly is missing? Checks hardware, Docker, the build prerequisites and
# the per-robot settings that silently misbehave rather than fail loudly.
#
#   ./tools/preflight.sh
#
# Exit 0 = at least one install path is open (load a prebuilt image, or build
# here). Exit 1 = blocked; every blocker is printed with its fix.
#
# It changes NOTHING. Safe to run on a live robot.
# =============================================================================
set -uo pipefail                      # not -e: a failing probe is a result, not a crash

HERE="$(cd "$(dirname "$0")/.." && pwd)"
IFACE="${IFACE:-eth0}"
IMG="${IMG:-inspire-hand:latest}"
PORT="${PORT:-8088}"

g=$'\033[32m'; r=$'\033[31m'; y=$'\033[33m'; d=$'\033[2m'; n=$'\033[0m'
FAIL=0; WARN=0
pass() { printf '  %sPASS%s  %-34s %s\n' "$g" "$n" "$1" "${2:-}"; }
fail() { printf '  %sMISS%s  %-34s %s\n' "$r" "$n" "$1" "${2:-}"; FAIL=$((FAIL+1)); }
warn() { printf '  %sWARN%s  %-34s %s\n' "$y" "$n" "$1" "${2:-}"; WARN=$((WARN+1)); }
hint() { printf '        %s%s%s\n' "$d" "$1" "$n"; }
head_() { printf '\n%s\n' "$1"; }

head_ "A. Platform"
arch="$(uname -m)"
[ "$arch" = "aarch64" ] && pass "architecture" "$arch" \
  || warn "architecture" "$arch — the image is arm64; this is not the robot"
pass "kernel / os" "$(uname -r) · $( . /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-unknown}")"
free_gb="$(df -Pk / | awk 'NR==2{printf "%.1f", $4/1048576}')"
awk -v f="$free_gb" 'BEGIN{exit !(f>4)}' && pass "disk free on /" "${free_gb} GB" \
  || warn "disk free on /" "${free_gb} GB — a build wants ~4 GB"

head_ "B. Docker"
HAVE_DOCKER=0
if command -v docker >/dev/null 2>&1; then
  if docker info >/dev/null 2>&1; then
    HAVE_DOCKER=1; pass "docker" "$(docker version --format '{{.Server.Version}}' 2>/dev/null)"
  else
    fail "docker" "installed but not usable by $(id -un)"
    hint "sudo usermod -aG docker $(id -un)  # then log out and back in"
  fi
else
  fail "docker" "not installed"
  hint "sudo apt install docker.io"
fi

head_ "C. Hands hardware"
ch340="$(lsusb 2>/dev/null | grep -c '1a86:7523')"
[ "$ch340" -ge 2 ] && pass "CH340 RS-485 adapters" "$ch340 found" \
  || fail "CH340 RS-485 adapters" "$ch340 found, need 2 — check USB + 24V"
ttys="$(ls /dev/ttyUSB* 2>/dev/null | tr '\n' ' ')"
[ -n "$ttys" ] && pass "serial nodes" "$ttys" || fail "serial nodes" "no /dev/ttyUSB*"
# The container runs privileged with /dev mounted, so host permissions do not
# block it. They only matter for the native manage.sh path.
if [ -n "$ttys" ] && [ ! -r "${ttys%% *}" ]; then
  warn "tty permissions" "not readable as $(id -un) — fine for Docker, blocks native use"
  hint "sudo cp udev/99-inspire-hands.rules /etc/udev/rules.d/ && sudo udevadm control --reload"
fi
if lsusb 2>/dev/null | grep -q '05e3:0749'; then
  warn "card reader on the hub" "05e3:0749 present — re-enumerates the hands' branch"
  hint "./start.sh usbfix   # installs the udev rule + deauthorizes it"
else
  pass "card reader on the hub" "absent"
fi

head_ "D. Install path 1 — prebuilt image"
IMAGE_OK=0
if [ "$HAVE_DOCKER" = 1 ] && docker image inspect "$IMG" >/dev/null 2>&1; then
  IMAGE_OK=1
  pass "$IMG" "already loaded — no build prerequisites needed"
else
  warn "$IMG" "not loaded on this robot"
  hint "on a robot that has it:  docker save $IMG | gzip > inspire-hand.tar.gz"
  hint "here:                    gunzip -c inspire-hand.tar.gz | docker load"
fi

head_ "E. Install path 2 — build the image here"
BUILD_OK=1
need_cmd() { command -v "$1" >/dev/null 2>&1 && pass "$1" "$(command -v "$1")" \
             || { fail "$1" "not installed"; BUILD_OK=0; }; }
need_cmd git; need_cmd cmake; need_cmd rsync
if cc_path="$(command -v gcc || command -v cc)"; then
  pass "c compiler" "$cc_path"
else
  fail "c compiler" "not installed"; BUILD_OK=0
  hint "sudo apt install build-essential"
fi

SDK=""
for c in "${UNITREE_SDK2_DIR:-}" "$HOME/unitree_sdk2" "$HERE/deps/unitree_sdk2"; do
  [ -n "$c" ] && [ -f "$c/lib/aarch64/libunitree_sdk2.a" ] && { SDK="$c"; break; }
done
if [ -n "$SDK" ]; then
  pass "unitree_sdk2 (C++)" "$SDK"
else
  fail "unitree_sdk2 (C++)" "no lib/aarch64/libunitree_sdk2.a anywhere"; BUILD_OK=0
  hint "git clone --depth 1 https://github.com/unitreerobotics/unitree_sdk2 ~/unitree_sdk2"
  hint "or ship it in the project at deps/unitree_sdk2 (build_image.sh looks there)"
fi

CDDS_INC=""
for c in /usr/local/include "$HERE/deps/cyclonedds/install/include"; do
  [ -d "$c/ddscxx" ] && { CDDS_INC="$c"; break; }
done
if [ -n "$CDDS_INC" ]; then
  miss=""
  for h in dds ddsc ddscxx iceoryx; do [ -d "$CDDS_INC/$h" ] || miss="$miss $h"; done
  [ -z "$miss" ] && pass "CycloneDDS headers" "$CDDS_INC" \
    || { fail "CycloneDDS headers" "missing:$miss"; BUILD_OK=0; }
else
  fail "CycloneDDS headers" "not in /usr/local/include nor deps/cyclonedds"; BUILD_OK=0
  hint "build CycloneDDS 0.10.x with -DCMAKE_INSTALL_PREFIX=/usr/local,"
  hint "or stage its install/ prefix at deps/cyclonedds (build_image.sh looks there)"
fi

# The idl/ headers are the ones a normal C++ install leaves out, and without
# them the image build dies late — inside the pip cyclonedds compile.
CDDS_SRC="${CYCLONEDDS_SRC:-$HOME/cyclonedds}"
IDL_INC=""
for c in "${CYCLONEDDS_IDL_INCLUDE:-}" /usr/local/include "$CDDS_SRC/install/include" \
         "$HERE/deps/cyclonedds/install/include" "$HOME/cyclonedds_ws/install/cyclonedds/include"; do
  [ -n "$c" ] && [ -f "$c/idl/retcode.h" ] && { IDL_INC="$c/idl"; break; }
done
if [ -n "$IDL_INC" ]; then
  pass "CycloneDDS idl headers" "$IDL_INC"
else
  fail "CycloneDDS idl headers" "no idl/retcode.h in any known prefix"; BUILD_OK=0
  hint "a ROS2 colcon workspace keeps them at ~/cyclonedds_ws/install/cyclonedds/include/idl,"
  hint "a source build at <src>/install/include/idl; or set CYCLONEDDS_IDL_INCLUDE"
fi

ls /usr/local/lib/libddsc.so* >/dev/null 2>&1 && pass "CycloneDDS runtime libs" "/usr/local/lib" \
  || { fail "CycloneDDS runtime libs" "no libddsc.so"; BUILD_OK=0; }

if curl -fsS -m 6 -o /dev/null https://pypi.org/simple/ 2>/dev/null; then
  pass "internet (pypi)" "reachable — needed by the in-image pip build"
else
  warn "internet (pypi)" "unreachable — a build here will fail at the python DDS step"
fi

head_ "F. This project"
for f in docker/build_image.sh docker/run.sh web/hand_web.py \
         recorder/g1_record_replay.py vendor/unitree_sdk2_python/setup.py; do
  [ -e "$HERE/$f" ] && pass "$f" || { fail "$f" "missing from the copy"; }
done

head_ "G. Per-robot settings"
ip -brief addr show "$IFACE" >/dev/null 2>&1 \
  && pass "DDS interface $IFACE" "$(ip -brief addr show "$IFACE" | awk '{print $2, $3}')" \
  || warn "DDS interface $IFACE" "not present — arm actions need it (fingers do not)"

# The USB pin is this-robot-specific and fails SILENTLY on different hardware:
# a wrong pin swaps left and right with no error at all.
RIGHT_PATH="${INSPIRE_RIGHT_PATH:-$(sed -n 's/^RIGHT_PATH=.*:-\([^}]*\)}.*/\1/p' "$HERE/docker/run.sh" 2>/dev/null | head -1)}"
if [ -n "${RIGHT_PATH:-}" ]; then
  if [ -d "/sys/bus/usb/devices/$RIGHT_PATH" ]; then
    warn "INSPIRE_RIGHT_PATH=$RIGHT_PATH" "exists here — but verify it IS the right hand"
  else
    warn "INSPIRE_RIGHT_PATH=$RIGHT_PATH" "does NOT exist on this robot"
  fi
  hint "best fix, once per robot: give the LEFT hand RS-485 id 2 —"
  hint "  docker exec inspire-hand ./build/hand_setid <left-port> 1 2"
  hint "  then id (not USB path) decides left/right, permanently."
fi

if command -v ss >/dev/null 2>&1 && ss -ltn 2>/dev/null | grep -q ":$PORT "; then
  warn "port $PORT" "already in use — something else is serving it"
else
  pass "port $PORT" "free"
fi

printf '\n%s\n' "Verdict"
if [ "$IMAGE_OK" = 1 ] && [ "$HAVE_DOCKER" = 1 ]; then
  printf '  %sREADY%s — image is loaded. Run: ./start.sh\n' "$g" "$n"; exit 0
elif [ "$BUILD_OK" = 1 ] && [ "$HAVE_DOCKER" = 1 ]; then
  printf '  %sREADY%s — no image yet, but this robot can build one. Run: ./start.sh\n' "$g" "$n"; exit 0
else
  printf '  %sBLOCKED%s — %d missing, %d warnings.\n' "$r" "$n" "$FAIL" "$WARN"
  printf '  Either load a prebuilt image (section D) or install the section E items.\n'
  exit 1
fi
