#!/bin/sh
# modem-sim.sh - modem signal read + SIM slot management.
#
# Config:
#   MODEM_BUS      gl_modem -B arg (default: 1-1.2)
#   MODEM_AT_CMD   AT command binary (default: gl_modem)
#
# Public API:
#   modem_signal         echo 'rsrp rsrq sinr'; exit 1 if modem absent
#   modem_sim_slot       echo active SIM slot number (1 or 2)
#   modem_sim_switch N   switch to SIM slot N and reconnect

LIB_DIR="${LIB_DIR:-/usr/lib/busrouter}"
: "${MODEM_BUS:=1-1.2}"
: "${MODEM_AT_CMD:=gl_modem}"

# Task 4.2 SIM switch config
: "${SIM_SWITCH_RSRP:=-110}"     # dBm threshold for bad-signal detection
: "${SIM_SWITCH_CYCLES:=3}"      # consecutive bad cycles before switch
: "${SIM_SWITCH_COOLDOWN:=300}"  # seconds between allowed switches
: "${SIM_STATE_DIR:=/tmp/busrouter}"

# Source decision helpers (provides sim_should_switch).
if [ -f "${LIB_DIR}/lib-decide.sh" ]; then
	. "${LIB_DIR}/lib-decide.sh"
fi
# Inline fallback: define sim_should_switch if not provided by lib.
if ! command -v sim_should_switch >/dev/null 2>&1; then
	sim_should_switch() { [ "$1" -ge "$2" ]; }
fi

# Parse ubus JSON signal output -> 'rsrp rsrq sinr'.
# Input: JSON from 'ubus call modem.signal get_signals {"time":1}'
# Extracts value after each key, handling multiple keys on the same line.
_modem_parse_ubus() {
	awk '
		BEGIN { rsrp=""; rsrq=""; sinr="" }
		{
			if (match($0, /"rsrp": *-?[0-9]+/)) {
				t = substr($0, RSTART, RLENGTH)
				match(t, /-?[0-9]+/); rsrp = substr(t, RSTART, RLENGTH)
			}
			if (match($0, /"rsrq": *-?[0-9]+/)) {
				t = substr($0, RSTART, RLENGTH)
				match(t, /-?[0-9]+/); rsrq = substr(t, RSTART, RLENGTH)
			}
			if (match($0, /"sinr": *-?[0-9]+/)) {
				t = substr($0, RSTART, RLENGTH)
				match(t, /-?[0-9]+/); sinr = substr(t, RSTART, RLENGTH)
			}
		}
		END {
			if (rsrp != "") printf "%s %s %s\n", rsrp, rsrq, sinr
			else exit 1
		}
	'
}

# Parse AT+QCSQ response line -> 'rsrp rsrq sinr'.
# Format: +QCSQ: "<mode>",<rsrp>,<sinr>,<rsrq>  (Quectel RM520N NR5G)
_modem_parse_at() {
	awk -F',' '
		BEGIN { ok=0 }
		/\+QCSQ:/ { rsrp=$2+0; sinr=$3+0; rsrq=$4+0; ok=1 }
		END {
			if (ok) printf "%s %s %s\n", rsrp, rsrq, sinr
			else exit 1
		}
	'
}

# Read signal metrics from modem.
# Primary: ubus call modem.signal get_signals; fallback: AT+QCSQ.
# Emits 'rsrp rsrq sinr' (dBm / dB integers) on success; exit 1 on failure.
modem_signal() {
	local result
	result=$(ubus call modem.signal get_signals '{"time":1}' 2>/dev/null | _modem_parse_ubus)
	if [ -n "$result" ]; then
		logger -t busrouter -p daemon.debug "modem_signal[ubus]: $result"
		echo "$result"; return 0
	fi
	# AT fallback
	result=$($MODEM_AT_CMD -B "$MODEM_BUS" AT AT+QCSQ 2>/dev/null | _modem_parse_at)
	if [ -n "$result" ]; then
		logger -t busrouter -p daemon.debug "modem_signal[at]: $result"
		echo "$result"; return 0
	fi
	logger -t busrouter "modem_signal: no signal data"
	return 1
}

# Return active SIM slot number (1 or 2).
modem_sim_slot() {
	$MODEM_AT_CMD -B "$MODEM_BUS" AT 'AT+QUIMSLOT?' 2>/dev/null | awk '/\+QUIMSLOT:/ { match($0, /[0-9]+/); print substr($0, RSTART, RLENGTH); exit }'
}

# Switch to SIM slot N and trigger modem reconnect.
modem_sim_switch() {
	local slot="$1"
	[ "$slot" = "1" ] || [ "$slot" = "2" ] || { logger -t busrouter "modem_sim_switch: invalid slot '$slot'"; return 1; }
	logger -t busrouter "modem_sim_switch: switching to slot $slot"
	$MODEM_AT_CMD -B "$MODEM_BUS" AT "AT+QUIMSLOT=$slot" 2>/dev/null | grep -q "^OK" || {
		logger -t busrouter "modem_sim_switch: AT+QUIMSLOT=$slot failed; not cycling interface"
		return 1
	}
	ifdown modem_0001 2>/dev/null; ifup modem_0001 2>/dev/null
}

# Evaluate current signal and switch SIM if persistently weak.
# Usage: sim_check_and_switch <rsrp_dBm>
# Returns 0 if a switch was triggered; 1 if not due; 2 if switch command failed.
sim_check_and_switch() {
	local rsrp="$1"
	local state_bad="${SIM_STATE_DIR}/sim_bad_cycles"
	local state_last="${SIM_STATE_DIR}/sim_last_switch"
	mkdir -p "$SIM_STATE_DIR"

	# Cooldown guard
	local now
	now=$(date +%s)
	if [ -f "$state_last" ]; then
		local last
		last=$(cat "$state_last"); last="${last%%[!0-9]*}"
		if [ "$(( now - last ))" -lt "${SIM_SWITCH_COOLDOWN:-300}" ]; then
			logger -t busrouter -p daemon.debug "sim_check: cooldown active"
			return 1
		fi
	fi

	# Update bad-cycle counter
	local bad
	bad=$(cat "$state_bad" 2>/dev/null); bad="${bad%%[!0-9]*}"; bad="${bad:-0}"
	if [ "${rsrp:-0}" -lt "${SIM_SWITCH_RSRP:--110}" ]; then
		bad=$(( bad + 1 ))
		printf '%d\n' "$bad" > "$state_bad"
		logger -t busrouter -p daemon.info "sim_check: rsrp=$rsrp below ${SIM_SWITCH_RSRP}, bad_cycles=$bad/${SIM_SWITCH_CYCLES}"
	else
		printf '0\n' > "$state_bad"
		return 1
	fi

	# Switch if threshold reached
	sim_should_switch "$bad" "${SIM_SWITCH_CYCLES:-3}" || return 1

	local slot new_slot
	slot=$(modem_sim_slot)
	[ "$slot" = "1" ] && new_slot=2 || new_slot=1
	logger -t busrouter "sim_check: switching from slot $slot to $new_slot (rsrp=$rsrp, cycles=$bad)"
	modem_sim_switch "$new_slot" && {
		printf '%d\n' "$now" > "$state_last"
		printf '0\n' > "$state_bad"
	} || return 2
}
