#!/bin/sh
# telemetry.sh - GPS read and telemetry payload builder.
#
# Config (GPS):
#   MODEM_BUS            gl_modem -B arg (default: 1-1.2)
#   MODEM_AT_CMD         AT command binary (default: gl_modem)
#
# Config (telemetry POST):
#   TELEMETRY_HUB        POST endpoint (default: http://10.88.0.1:8080/api/telemetry)
#   TELEMETRY_INTERVAL   seconds between sends when used from daemon (default: 60)
#   TELEMETRY_BUFFER_DIR disk buffer for failed POSTs (default: /tmp/busrouter/telemetry-buf)
#
# Public API:
#   gps_fix              echo 'lat lon fix_quality'; exit 1 if no fix
#   telemetry_collect    emit JSON payload to stdout from state files
#   telemetry_flush      retry all buffered payloads; returns 1 if hub still down
#   telemetry_send       collect + post; buffer on failure; never blocks caller

LIB_DIR="${LIB_DIR:-/usr/lib/busrouter}"
: "${MODEM_BUS:=1-1.2}"
: "${MODEM_AT_CMD:=gl_modem}"
: "${TELEMETRY_HUB:=http://10.88.0.1:8080/api/telemetry}"
: "${TELEMETRY_INTERVAL:=60}"
: "${TELEMETRY_BUFFER_DIR:=/tmp/busrouter/telemetry-buf}"
_TELEM_SEQ=0

# Parse AT+QGPSLOC=2 response -> 'lat lon fix_quality'.
# Format: +QGPSLOC: <utc>,<lat>,<lon>,<hdop>,<alt>,<fix>,...
# fix: 2=2D, 3=3D (1 or absent = no fix)
_gps_parse() {
	awk '
		/^\+QGPSLOC:/ {
			sub(/^\+QGPSLOC: [^,]+,/, "")
			n = split($0, a, ",")
			lat = a[1]; lon = a[2]; fix = a[5]+0
			if (fix >= 2) { printf "%s %s %d\n", lat, lon, fix; found=1; exit 0 }
		}
		END { if (!found) exit 1 }
	'
}

# Read GPS position from modem via AT+QGPSLOC=2.
# Emits 'lat lon fix_quality' on success; exit 1 on no-fix or modem error.
gps_fix() {
	local result
	result=$(timeout 5 "$MODEM_AT_CMD" -B "$MODEM_BUS" AT AT+QGPSLOC=2 2>/dev/null | _gps_parse)
	if [ -n "$result" ]; then
		logger -t busrouter -p daemon.debug "gps_fix: $result"
		echo "$result"; return 0
	fi
	logger -t busrouter "gps_fix: no fix"
	return 1
}

# Build telemetry JSON from state files written by the control loop.
# Escape a string value for JSON (backslash and double-quote only).
_json_str() { printf '%s' "$1" | sed 's/\/\\/g; s/"/\\"/g'; }

# State files (in /tmp/busrouter/):
#   gps_last            "lat lon fix"          (written by gps_fix caller)
#   metric_modem_0001   "lat_ms dl ul jitter loss rsrp"
#   metric_wan          "lat_ms dl ul jitter loss"
#   score_modem_0001    integer 0-100
#   score_wan           integer 0-100
#   starlink_last       "lat_ms dl_bps obstructed outage"
#   sim_slot            "1" or "2"
telemetry_collect() {
	local now device_id uptime_s version
	now=$(date +%s)
	device_id=$(cat /etc/busrouter/device-id 2>/dev/null || hostname)
	uptime_s=$(awk '{printf "%d", $1}' /proc/uptime 2>/dev/null)
	version=$(cat /etc/busrouter/version 2>/dev/null || echo "dev")

	local gps_lat gps_lon gps_fix
	[ -f /tmp/busrouter/gps_last ] && read -r gps_lat gps_lon gps_fix < /tmp/busrouter/gps_last || true

	local cell_score cell_lat cell_dl cell_ul cell_jitter cell_loss cell_rsrp
	cell_score=$(cat /tmp/busrouter/score_modem_0001 2>/dev/null)
	[ -f /tmp/busrouter/metric_modem_0001 ] && read -r cell_lat cell_dl cell_ul cell_jitter cell_loss cell_rsrp < /tmp/busrouter/metric_modem_0001 || true

	local wan_score wan_lat wan_dl wan_ul wan_jitter wan_loss
	wan_score=$(cat /tmp/busrouter/score_wan 2>/dev/null)
	[ -f /tmp/busrouter/metric_wan ] && read -r wan_lat wan_dl wan_ul wan_jitter wan_loss < /tmp/busrouter/metric_wan || true

	local sl_lat sl_dl sl_obs sl_outage
	[ -f /tmp/busrouter/starlink_last ] && read -r sl_lat sl_dl sl_obs sl_outage < /tmp/busrouter/starlink_last || true

	local sim_slot
	sim_slot=$(cat /tmp/busrouter/sim_slot 2>/dev/null)

	# sel_primary: written by the kit-balance engine to indicate the active WAN member.
	local sel_primary
	sel_primary=$(cat /tmp/busrouter/sel_primary 2>/dev/null)

	local esc_id esc_ver esc_sel
	esc_id=$(_json_str "$device_id")
	esc_ver=$(_json_str "$version")
	esc_sel=$(_json_str "$sel_primary")
	printf '{"device_id":"%s","timestamp":%d,"uptime":%d,"version":"%s","platform":"openwrt","sel_primary":"%s","gps":{"lat":%s,"lon":%s,"fix":%s},"wan":{"modem_0001":{"score":%s,"latency_ms":%s,"dl_mbps":%s,"ul_mbps":%s,"jitter_ms":%s,"loss_pct":%s,"rsrp_dbm":%s},"wan":{"score":%s,"latency_ms":%s,"dl_mbps":%s,"ul_mbps":%s,"jitter_ms":%s,"loss_pct":%s}},"sim_slot":%s,"starlink":{"latency_ms":%s,"dl_bps":%s,"obstructed":%s,"outage":%s}}\n' \
		"${esc_id}" "${now:-0}" "${uptime_s:-0}" "${esc_ver}" "${esc_sel}" \
		"${gps_lat:-null}" "${gps_lon:-null}" "${gps_fix:-null}" \
		"${cell_score:-0}" "${cell_lat:-null}" "${cell_dl:-null}" "${cell_ul:-null}" "${cell_jitter:-null}" "${cell_loss:-null}" "${cell_rsrp:-null}" \
		"${wan_score:-0}" "${wan_lat:-null}" "${wan_dl:-null}" "${wan_ul:-null}" "${wan_jitter:-null}" "${wan_loss:-null}" \
		"${sim_slot:-null}" "${sl_lat:-null}" "${sl_dl:-null}" "${sl_obs:-0}" "${sl_outage:-0}"
}

# POST a JSON file to the hub. Returns 0 on HTTP 2xx, 1 on any failure.
_telemetry_try_post() {
	local file="$1"
	local code
	code=$(curl -sf -X POST -H "Content-Type: application/json" \
		--data-binary @"$file" -o /dev/null -w "%{http_code}" \
		--max-time 10 "$TELEMETRY_HUB" 2>/dev/null)
	case "$code" in 2*) return 0 ;; *) return 1 ;; esac
}

# Retry all buffered payloads in timestamp order.
# Stops on first failure to avoid hammering a down hub.
telemetry_flush() {
	[ -d "$TELEMETRY_BUFFER_DIR" ] || return 0
	local f
	for f in "$TELEMETRY_BUFFER_DIR"/*.json; do
		[ -f "$f" ] || continue
		if _telemetry_try_post "$f"; then
			rm -f "$f"
			logger -t busrouter -p daemon.debug "telemetry_flush: sent $(basename "$f")"
		else
			logger -t busrouter "telemetry_flush: hub unreachable, stopping"
			return 1
		fi
	done
}

# Collect metrics, flush old buffer, POST current payload.
# On POST failure: saves payload to TELEMETRY_BUFFER_DIR.
# Never blocks the caller on hub downtime.
telemetry_send() {
	local payload
	payload=$(telemetry_collect)

	telemetry_flush 2>/dev/null || true

	mkdir -p "$TELEMETRY_BUFFER_DIR"
	_TELEM_SEQ=$(( _TELEM_SEQ + 1 ))
	local tmpfile="${TELEMETRY_BUFFER_DIR}/$(date +%s)_$$_${_TELEM_SEQ}.json"
	# Cap buffer to 60 files to protect tmpfs
	buf_count=$(ls "$TELEMETRY_BUFFER_DIR"/*.json 2>/dev/null | wc -l)
	if [ "${buf_count:-0}" -ge 60 ]; then
		oldest=$(ls "$TELEMETRY_BUFFER_DIR"/*.json 2>/dev/null | sort | head -n 1)
		[ -f "$oldest" ] && rm -f "$oldest"
	fi
	printf '%s\n' "$payload" > "$tmpfile"

	if _telemetry_try_post "$tmpfile"; then
		rm -f "$tmpfile"
		logger -t busrouter -p daemon.debug "telemetry_send: posted ok"
	else
		logger -t busrouter "telemetry_send: hub unreachable, buffered $(basename "$tmpfile")"
	fi
}
