#!/bin/sh
# daemon.sh - busrouter control loop.
# Sources all libs and runs the scoring/balancing/telemetry cycle.
# Helper functions are defined in global scope so tests can load this file
# without starting the loop. The loop only runs via busrouter_main().

CONFIG=/etc/busrouter/busrouter.conf
[ -f "$CONFIG" ] && . "$CONFIG"

LIB_DIR="${LIB_DIR:-/usr/lib/busrouter}"

. "${LIB_DIR}/lib-decide.sh"
. "${LIB_DIR}/lib-score.sh"
. "${LIB_DIR}/metrics-net.sh"
. "${LIB_DIR}/speedtest.sh"
. "${LIB_DIR}/modem-sim.sh"
. "${LIB_DIR}/starlink.sh"
. "${LIB_DIR}/wan-mwan.sh"
. "${LIB_DIR}/telemetry.sh"

: "${INTERVAL:=30}"
: "${CELL_IFACE:=rmnet_mhi0}"
: "${ETH_IFACE:=eth0}"
: "${CELL_MBR:=modem_0001}"
: "${ETH_MBR:=wan}"
: "${STATE_DIR:=/tmp/busrouter}"
: "${SCORE_SAMPLES:=3}"

# Update score history for one WAN member (keeps last SCORE_SAMPLES scores).
# Prints current history (space-separated) for use with rolling_avg.
_score_history_update() {
	local key="$1" new_score="$2"
	local hist_file="${STATE_DIR}/score_history_${key}"
	local hist
	hist=$(cat "$hist_file" 2>/dev/null || echo "50 50 50")
	local new_hist
	new_hist=$(echo "$hist $new_score" | awk -v n="${SCORE_SAMPLES:-3}" '{
		start = (NF > n) ? NF - n + 1 : 1
		for (i = start; i <= NF; i++) printf "%s%s", $i, (i < NF ? " " : "\n")
	}')
	printf '%s\n' "$new_hist" > "$hist_file"
	echo "$new_hist"
}

# Collect net+speed metrics for one WAN and write state files.
# Usage: _collect_wan <linux_iface> <mwan3_mbr> [rsrp_dBm]
# Writes: STATE_DIR/metric_<mbr>, STATE_DIR/score_<mbr>
# Prints rolling-average score on stdout.
_collect_wan() {
	local iface="$1" mbr="$2" rsrp="${3:-}"
	mkdir -p "$STATE_DIR"

	# Net probe: lat_ms jitter_ms loss_pct (fail-open: use worst-case)
	local net_out lat jitter loss
	net_out=$(net_probe_iface "$iface" 2>/dev/null) || net_out="0 0 100"
	lat=$(echo "$net_out" | awk '{print $1}'); : "${lat:=0}"
	jitter=$(echo "$net_out" | awk '{print $2}'); : "${jitter:=0}"
	loss=$(echo "$net_out" | awk '{print $3}'); : "${loss:=100}"

	# Speed: use cached value unless a new test is due
	local dl ul speed_out
	if speed_test_due "$iface" 2>/dev/null; then
		speed_out=$(speed_test_iface "$iface" 2>/dev/null) || speed_out="0 0"
		dl=$(echo "$speed_out" | awk '{print $1}'); : "${dl:=0}"
		ul=$(echo "$speed_out" | awk '{print $2}'); : "${ul:=0}"
		printf '%s %s\n' "$dl" "$ul" > "${STATE_DIR}/speed_cache_${iface}"
	else
		speed_out=$(cat "${STATE_DIR}/speed_cache_${iface}" 2>/dev/null || echo "0 0")
		dl=$(echo "$speed_out" | awk '{print $1}'); : "${dl:=0}"
		ul=$(echo "$speed_out" | awk '{print $2}'); : "${ul:=0}"
	fi

	# Score composite + rolling average
	local score hist avg
	if [ -n "$rsrp" ]; then
		score=$(score_composite "$lat" "$dl" "$ul" "$jitter" "$loss" "$rsrp" 2>/dev/null) || score=0
		printf '%s %s %s %s %s %s\n' "$lat" "$dl" "$ul" "$jitter" "$loss" "$rsrp" > "${STATE_DIR}/metric_${mbr}"
	else
		score=$(score_composite "$lat" "$dl" "$ul" "$jitter" "$loss" 2>/dev/null) || score=0
		printf '%s %s %s %s %s\n' "$lat" "$dl" "$ul" "$jitter" "$loss" > "${STATE_DIR}/metric_${mbr}"
	fi
	: "${score:=0}"
	hist=$(_score_history_update "$mbr" "$score")
	avg=$(rolling_avg $hist 2>/dev/null) || avg=50
	printf '%d\n' "${avg:-50}" > "${STATE_DIR}/score_${mbr}"

	logger -t busrouter "metrics[$iface]: lat=${lat}ms dl=${dl} ul=${ul} jitter=${jitter}ms loss=${loss}% rsrp=${rsrp:-N/A} score=${score} avg=${avg}"
	echo "${avg:-50}"
}

# One control loop iteration. Exported as a function for bench testing.
busrouter_cycle() {
	logger -t busrouter -p daemon.debug "cycle start: $(date +%s)"

	# Cellular signal (fail-open: rsrp stays empty)
	local cell_rsrp sig_out
	cell_rsrp=""
	sig_out=$(modem_signal 2>/dev/null) && {
		cell_rsrp=$(echo "$sig_out" | awk '{print $1}')
	} || logger -t busrouter "daemon: modem_signal unavailable (continuing)"

	# Per-WAN metrics + rolling scores
	local cell_avg eth_avg
	cell_avg=$(_collect_wan "$CELL_IFACE" "$CELL_MBR" "$cell_rsrp" 2>/dev/null) || cell_avg=50
	eth_avg=$(_collect_wan "$ETH_IFACE" "$ETH_MBR" 2>/dev/null) || eth_avg=50

	# Starlink status
	local sl_out
	sl_out=$(starlink_status 2>/dev/null) && {
		printf '%s\n' "$sl_out" > "${STATE_DIR}/starlink_last"
	} || logger -t busrouter -p daemon.debug "daemon: starlink_status unavailable"

	# Weight decision (mwan_set_weight has internal hysteresis guard)
	local new_cell_w new_eth_w
	new_cell_w=$(wan_weight "$cell_avg" "$eth_avg" 2>/dev/null) || new_cell_w=50
	new_eth_w=$(wan_weight "$eth_avg" "$cell_avg" 2>/dev/null) || new_eth_w=50
	mwan_set_weight "$CELL_MBR" "$new_cell_w" 2>/dev/null || true
	mwan_set_weight "$ETH_MBR" "$new_eth_w" 2>/dev/null || true

	# Dead-gateway check (cellular only; Starlink manages its own uplink)
	if ! gw_probe_iface "$CELL_IFACE" 2>/dev/null; then
		logger -t busrouter "daemon: cellular gateway dead, cycling $CELL_MBR"
		ifdown "$CELL_MBR" 2>/dev/null; ifup "$CELL_MBR" 2>/dev/null
	fi

	# SIM auto-switch
	[ -n "$cell_rsrp" ] && sim_check_and_switch "$cell_rsrp" 2>/dev/null || true

	# GPS
	local gps_out
	gps_out=$(gps_fix 2>/dev/null) && {
		printf '%s\n' "$gps_out" > "${STATE_DIR}/gps_last"
	} || true

	# SIM slot (for telemetry)
	local sim_slot
	sim_slot=$(modem_sim_slot 2>/dev/null) && {
		printf '%s\n' "$sim_slot" > "${STATE_DIR}/sim_slot"
	} || true

	# Telemetry
	telemetry_send 2>/dev/null || true

	logger -t busrouter -p daemon.debug "cycle done"
}

# Main loop — runs only when this file is executed directly (not sourced).
busrouter_main() {
	mkdir -p "$STATE_DIR"
	local ver
	ver=$(cat /etc/busrouter/version 2>/dev/null || echo "0.2.0")
	logger -t busrouter "daemon started (v${ver})"
	while :; do
		busrouter_cycle
		sleep "${INTERVAL:-30}"
	done
}

# Guard: only start loop when executed directly, not when sourced for testing.
case "${0##*/}" in daemon.sh) busrouter_main ;; esac
