#!/usr/bin/env bash
#
# Fixture listen-only — Pi on POG422 bus (no Ethernet bridge / WireGuard).
# Default: pog422_uartd owns UART (same C capture / HW_DEBUG path as bridge).
# Inject is always off (witness only).
# Options from ~/EtherGold/fixture-bridge.env when FIXTURE_POS is unset
# (run_listen_jun11_good.sh / hat_listen.sh). Capture default: ./data ./logs
# under this tree (~/EtherGold/dev) so rclone SYNC_SOURCE=dev lands in logs/N.
#
# Env:
#   FIXTURE_POS=1..99   (optional log/socket routing; default 1; 51 = pos5 witness)
#   PI_LABEL=topbox1    (optional, default topbox${FIXTURE_POS})
#   FIXTURE_DIR=        optional override for data/logs base
#   CHANNEL=machine     default; ois → raw HS window log (no SOH/EOP parse)
#   FIXTURE_PROD=1      errors.jsonl only; no packets.raw (field collector)
#   VERBOSE=1           mirror events to stderr + fixture_listen.log
#   IGNORE_HS=1         continuous UART capture (forces pyserial; no UARTD)
#   NO_PROGRESSIVE_CSV=1  disable Master 0x0A → progressives.csv
#   ETHERGOLD_PROGRESSIVES_CSV=  override CSV path (default ~/EtherGold/progressives.csv)
#   CLEAR_CAPTURE=1     default: wipe data/ and logs/ contents on start (prod uses 0)
#   LISTEN_AUTO_RESTART=1  default: on non-zero exit, restart Python without
#                          wiping capture (CLEAR_CAPTURE only applies to first start)
#   ETHERGOLD_UARTD_PERSIST=1  default: leave pog422_uartd running on listen exit
#   POG422_UARTD=1      default: start pog422_uartd (same as bridge)
#   POG422_UARTD=0      legacy pyserial register-poll
#   POG422_HS_PATH=pi   J3/J4 PI: GPIO HS (BCM8/9) + XR UART RX (forces UARTD=0)
#   POG422_UARTD_SOCK=  override /tmp/pog422_uart_<pos>.sock
#   HW_DEBUG=1          pass through to inject-shim make (bench dense timeline)
#   LOOP_SLEEP_US=10    main Python loop sleep (µs)
#   FIXTURE_EARLY_WIN_PHASE1=1     listen default ON — progwin → fireworks JSON
#   FIXTURE_EARLY_WIN_PHASE2_KENO=1  listen default ON — SuperBall/TouchEasy cash
#   FIXTURE_EARLY_WIN_PHASE2_SLOTS=1 listen default ON — 970C cash
#   FIXTURE_EARLY_WIN_DRY_RUN=0    1 = log hits, do not write fireworks-request.json
#   ETHERGOLD_LOCAL_WIRE_TID=      Phase 2 local TID (else POG_TID / CURRENT_GAME_TID)

set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${ROOT}"

# Preserve explicit CLI; otherwise load ~/EtherGold/fixture-bridge.env
# (listen options live there — not on the command line).
_CLI_POS="${FIXTURE_POS-}"
_CLI_CHANNEL="${CHANNEL-}"
_CLI_VERBOSE="${VERBOSE-}"
_CLI_CLEAR="${CLEAR_CAPTURE-}"
_CLI_UARTD="${POG422_UARTD-}"
_CLI_HS="${POG422_HS_PATH-}"
for _sync in \
  "${ETHERGOLD_SYNC:-}" \
  "${ROOT}/../sync" \
  "/home/topbox/EtherGold/sync"; do
  if [[ -f "${_sync}/source_ethergold_env.sh" ]]; then
    # shellcheck source=/dev/null
    source "${_sync}/source_ethergold_env.sh"
    break
  fi
done
unset _sync
if [[ -n "${_CLI_POS}" ]]; then export FIXTURE_POS="${_CLI_POS}"; fi
if [[ -n "${_CLI_CHANNEL}" ]]; then export CHANNEL="${_CLI_CHANNEL}"; fi
if [[ -n "${_CLI_VERBOSE}" ]]; then export VERBOSE="${_CLI_VERBOSE}"; fi
if [[ -n "${_CLI_CLEAR}" ]]; then export CLEAR_CAPTURE="${_CLI_CLEAR}"; fi
if [[ -n "${_CLI_UARTD}" ]]; then export POG422_UARTD="${_CLI_UARTD}"; fi
if [[ -n "${_CLI_HS}" ]]; then export POG422_HS_PATH="${_CLI_HS}"; fi
unset _CLI_POS _CLI_CHANNEL _CLI_VERBOSE _CLI_CLEAR _CLI_UARTD _CLI_HS

# shellcheck source=uartd_common.sh
source "${ROOT}/uartd_common.sh"
# shellcheck source=hat_status_led.sh
if [[ -f "${ROOT}/hat_status_led.sh" ]]; then
  source "${ROOT}/hat_status_led.sh"
fi

FIXTURE_POS="${FIXTURE_POS:-}"
if [[ -z "${FIXTURE_POS}" ]] || ! [[ "${FIXTURE_POS}" =~ ^[1-9][0-9]?$ ]]; then
  echo "WARN: FIXTURE_POS unset/invalid; using 1 for log/socket routing only (not a run gate)" >&2
  FIXTURE_POS=1
fi

PI_LABEL="${PI_LABEL:-topbox${FIXTURE_POS}}"
CHANNEL="${CHANNEL:-machine}"
# Listen is single-UART witness — bridge's CHANNEL=both is not valid here.
if [[ "${CHANNEL}" == "both" ]]; then
  echo "NOTE: CHANNEL=both (from fixture-bridge.env) → machine for listen" >&2
  CHANNEL=machine
fi
if [[ "${CHANNEL}" != "machine" && "${CHANNEL}" != "ois" ]]; then
  echo "FATAL: listen CHANNEL must be machine or ois (got ${CHANNEL})" >&2
  exit 1
fi
OUTPUT_DIR="${OUTPUT_DIR:-./data}"
LOG_DIR="${LOG_DIR:-./logs}"
VERBOSE="${VERBOSE:-0}"
IGNORE_HS="${IGNORE_HS:-0}"
FIXTURE_DIR="${FIXTURE_DIR:-}"
CLEAR_CAPTURE="${CLEAR_CAPTURE:-1}"
LISTEN_AUTO_RESTART="${LISTEN_AUTO_RESTART:-1}"
FIXTURE_PROD="${FIXTURE_PROD:-0}"
NO_PROGRESSIVE_CSV="${NO_PROGRESSIVE_CSV:-0}"
ETHERGOLD_ROOT="${ETHERGOLD_ROOT:-${HOME}/EtherGold}"
ETHERGOLD_PROGRESSIVES_CSV="${ETHERGOLD_PROGRESSIVES_CSV:-${ETHERGOLD_ROOT}/progressives.csv}"
STATS_INTERVAL="${STATS_INTERVAL:-30}"
POG422_UARTD="${POG422_UARTD:-1}"
LOOP_SLEEP_US="${LOOP_SLEEP_US:-10}"
# Witness never injects — even if shell inherited bridge env.
export POG422_UARTD_INJECT=0

if [[ -n "${FIXTURE_DIR}" ]]; then
  OUTPUT_DIR="${FIXTURE_DIR}/data"
  LOG_DIR="${FIXTURE_DIR}/logs"
fi

mkdir -p "${LOG_DIR}" "${OUTPUT_DIR}"

remove_capture_file() {
  local f="$1"
  [[ -e "${f}" ]] || return 0
  if rm -f "${f}" 2>/dev/null; then
    return 0
  fi
  local owner
  owner="$(stat -c '%U:%G' "${f}" 2>/dev/null || echo unknown)"
  echo "FATAL: cannot remove ${f} (owner ${owner})" >&2
  echo "  A prior sudo run left root-owned capture files. Fix once:" >&2
  echo "    sudo chown -R $(id -un):$(id -gn) ${OUTPUT_DIR} ${LOG_DIR}" >&2
  echo "  Or append without delete: CLEAR_CAPTURE=0" >&2
  return 1
}

if [[ "${CLEAR_CAPTURE}" == "1" ]]; then
  # Full wipe (rotated events.jsonl.* included). Must run before uartd starts
  # writing logs/uartd.log. Python then --append-capture so it does not re-wipe.
  # Auto-restart path below never re-wipes — capture continues across recoveries.
  pog422_wipe_capture_dirs "${OUTPUT_DIR}" "${LOG_DIR}"
  echo "Wiped capture dirs ${OUTPUT_DIR} and ${LOG_DIR}" >&2
fi

# HAT LED1 POST — listen role/env (phase 1), then wait for HAT USB (phase 2).
if type hat_led_phase >/dev/null 2>&1; then
  hat_led_phase 1
fi
if type hat_led_phase >/dev/null 2>&1; then
  hat_led_phase 2
fi
if [[ -x "${ROOT}/ensure_tty_xrusb.sh" ]]; then
  bash "${ROOT}/ensure_tty_xrusb.sh"
elif type hat_led_wait_tty >/dev/null 2>&1; then
  if ! hat_led_wait_tty "${HAT_TTY_WAIT_S:-60}"; then
    exit 1
  fi
elif ! ls /dev/ttyXRUSB* >/dev/null 2>&1; then
  echo "FATAL: /dev/ttyXRUSB* not found" >&2
  exit 1
fi

# Prod collector can dual-open ttyXRUSB and leave tip listen with window_bytes=0
# while HS LEDs still blink — look like "UART deaf" hardware. Refuse early.
if [[ "${FIXTURE_PROD:-0}" != "1" && "${ALLOW_UART_SHARE:-0}" != "1" ]]; then
  if systemctl is-active --quiet ethergold-collector.service 2>/dev/null \
    || systemctl is-active --quiet fixture-collector.service 2>/dev/null; then
    echo "FATAL: ethergold-collector is active — it steals ttyXRUSB RX from tip/witness listen." >&2
    echo "  sudo systemctl stop ethergold-collector.service" >&2
    echo "  # permanent witness: touch /var/lib/ethergold/WITNESS_NO_PROD" >&2
    echo "  Override only if intentional: ALLOW_UART_SHARE=1" >&2
    exit 1
  fi
  if systemctl is-active --quiet ethergold.service 2>/dev/null \
    || systemctl is-active --quiet fixture-prod.service 2>/dev/null; then
    echo "FATAL: ethergold/fixture-prod bridge is active — stop it before tip listen." >&2
    echo "  sudo systemctl stop ethergold.service fixture-prod.service" >&2
    exit 1
  fi
fi

export PYTHONUNBUFFERED=1

# ignore_hs is pyserial-only — do not start uartd (it would hold the port).
if [[ "${IGNORE_HS}" == "1" ]]; then
  POG422_UARTD=0
fi

# Pi HS: J3/J4 PI disconnects XR GPIO1. Sense HS on BCM8/9; RX stays ttyXRUSB.
if [[ -z "${POG422_HS_PATH:-}" && -f /etc/ethergold/ethergold.env ]]; then
  POG422_HS_PATH="$(
    awk -F= '/^POG422_HS_PATH=/ {
      v=$2; gsub(/^[ \t"\047]+|[ \t"\047]+$/, "", v); print v
    }' /etc/ethergold/ethergold.env | tail -1
  )"
fi
case "${POG422_HS_PATH:-}" in
  pi|gpio|j19|pi_hs)
    export POG422_HS_PATH=pi
    if [[ "${POG422_UARTD}" == "1" ]]; then
      echo "NOTE: POG422_HS_PATH=pi → UARTD=0 (GPIO HS + XR UART RX)" >&2
      POG422_UARTD=0
    fi
    ;;
esac

# Production collector restart used to SIGKILL uartd via this EXIT trap and
# wedge ttyXRUSB until a hard power cycle. Persist by default; attach on respawn.
# Pi-HS listen is pyserial (UARTD=0) — do not print the persist line.
_hat_listen_cleanup_led() {
  if type hat_led_stop >/dev/null 2>&1; then
    hat_led_stop
  fi
}
if [[ "${POG422_UARTD}" == "1" && "${ETHERGOLD_UARTD_PERSIST:-1}" == "1" ]]; then
  trap 'echo "hat_listen: leaving pog422_uartd up (ETHERGOLD_UARTD_PERSIST=1)" >&2; _hat_listen_cleanup_led' EXIT
elif [[ "${POG422_UARTD}" == "1" ]]; then
  trap 'pog422_stop_uartd; _hat_listen_cleanup_led' EXIT INT TERM
else
  trap '_hat_listen_cleanup_led' EXIT INT TERM
fi

ARGS=(
  "${ROOT}/pog422_listen.py"
  --fixture-pos "${FIXTURE_POS}"
  --pi-label "${PI_LABEL}"
  --channel "${CHANNEL}"
  --output-dir "${OUTPUT_DIR}"
  --logs-dir "${LOG_DIR}"
  --stats-interval "${STATS_INTERVAL}"
  --progressives-csv "${ETHERGOLD_PROGRESSIVES_CSV}"
  --loop-sleep-us "${LOOP_SLEEP_US}"
)

if [[ "${POG422_UARTD}" == "1" ]]; then
  ARGS+=(--uartd)
else
  ARGS+=(--no-uartd)
fi
if [[ "${VERBOSE}" == "1" ]]; then
  ARGS+=(--echo-events)
fi
# Shell owns wipe when CLEAR_CAPTURE=1 (before uartd). Always append afterward so
# Python does not empty logs/ while uartd is already writing uartd.log.
ARGS+=(--append-capture)
if [[ "${IGNORE_HS}" == "1" ]]; then
  ARGS+=(--ignore-hs)
fi
if [[ "${FIXTURE_PROD}" == "1" ]]; then
  ARGS+=(--prod)
fi
if [[ "${FIXTURE_HS_WITNESS:-1}" == "0" ]]; then
  ARGS+=(--no-hs-witness)
fi
if [[ "${NO_PROGRESSIVE_CSV}" == "1" ]]; then
  ARGS+=(--no-progressive-csv)
fi

echo "Fixture listen pos=${FIXTURE_POS} label=${PI_LABEL} prod=${FIXTURE_PROD} capture=$([[ "${IGNORE_HS}" == "1" ]] && echo ignore_hs || echo handshake)" >&2
echo "UART mode: uartd=${POG422_UARTD} inject=0 hs_path=${POG422_HS_PATH:-uart} socket=${POG422_UARTD_SOCK:-/tmp/pog422_uart_${FIXTURE_POS}.sock} HW_DEBUG=${HW_DEBUG:-0}" >&2
echo "Auto-restart: ${LISTEN_AUTO_RESTART} (restart keeps capture; wipe only on first CLEAR_CAPTURE=1)" >&2
echo "Progressives: ${ETHERGOLD_PROGRESSIVES_CSV}" >&2
echo "Log dir: ${OUTPUT_DIR}" >&2

LISTEN_RESTARTS=0
while true; do
  if [[ "${POG422_UARTD}" == "1" ]]; then
    if [[ "${EUID}" -eq 0 ]]; then
      echo "WARN: do not run hat_listen.sh with sudo — use topbox" >&2
    fi
    # Phase 4 — UARTD ready (retries inside uartd_common / here).
    if type hat_led_phase >/dev/null 2>&1; then
      hat_led_phase 4
    fi
    _UARTD_TRIES=0
    _UARTD_OK=0
    while ((_UARTD_TRIES < 3)); do
      _UARTD_TRIES=$((_UARTD_TRIES + 1))
      if pog422_start_uartd "${ROOT}" "${FIXTURE_POS}" "${CHANNEL}" "${LOG_DIR}"; then
        _UARTD_OK=1
        break
      fi
      echo "WARN: pog422_start_uartd failed (try ${_UARTD_TRIES}/3) — retrying" >&2
      pog422_stop_uartd || true
      sleep 2
    done
    if [[ "${_UARTD_OK}" != "1" ]]; then
      exit 1
    fi
  fi

  # Python owns LED1 after spawn (release shell helper).
  if type hat_led_stop >/dev/null 2>&1; then
    hat_led_stop
  fi

  # Always tee Python traceback into fixture_listen.log (append across restarts).
  set +e
  python3 "${ARGS[@]}" 2>> "${LOG_DIR}/fixture_listen.log"
  LISTEN_RC=$?
  set -e

  if [[ "${LISTEN_RC}" -eq 0 ]]; then
    exit 0
  fi

  # 130/143 = Ctrl-C / SIGTERM — do not auto-restart.
  if [[ "${LISTEN_RC}" -eq 130 || "${LISTEN_RC}" -eq 143 ]]; then
    echo "Listen stopped (signal exit ${LISTEN_RC})" >&2
    exit "${LISTEN_RC}"
  fi

  echo "FATAL: listen exited ${LISTEN_RC} — see ${LOG_DIR}/fixture_listen.log" >&2
  if [[ -f "${LOG_DIR}/fixture_listen.log" ]]; then
    tail -n 30 "${LOG_DIR}/fixture_listen.log" >&2
  fi
  if [[ -f "${LOG_DIR}/uartd.log" ]]; then
    echo "--- ${LOG_DIR}/uartd.log (last 20 lines) ---" >&2
    tail -n 20 "${LOG_DIR}/uartd.log" >&2
  fi

  # GPIO EBUSY is another process (often ethergold-collector), not a USB blip.
  if [[ -f "${LOG_DIR}/fixture_listen.log" ]] \
    && grep -q 'already claimed' "${LOG_DIR}/fixture_listen.log"; then
    echo "FATAL: GPIO HS line already claimed — not auto-restarting." >&2
    echo "  If systemd collector owns BCM9: sudo systemctl stop ethergold-collector.service" >&2
    exit "${LISTEN_RC}"
  fi

  if [[ "${LISTEN_AUTO_RESTART}" != "1" ]]; then
    exit "${LISTEN_RC}"
  fi

  LISTEN_RESTARTS=$((LISTEN_RESTARTS + 1))
  {
    echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) listen_auto_restart n=${LISTEN_RESTARTS} exit=${LISTEN_RC} clear_capture=0"
  } >> "${LOG_DIR}/fixture_listen.log"
  echo "WARN: listen auto-restart #${LISTEN_RESTARTS} without clearing capture (exit ${LISTEN_RC})" >&2
  if [[ "${ETHERGOLD_UARTD_PERSIST:-1}" != "1" ]]; then
    pog422_stop_uartd
  fi
  sleep 2
done
