#!/bin/sh

# Script: bbf_config_reload.sh
# Description:
#   Reloads WiFi configurations based on provided arguments.
# 
# Usage:
#   sh bbf_config_reload.sh [wireless] [mapcontroller]
# 
# Input arguments:
#   wireless      Reload only wireless configuration
#   mapcontroller Reload only mapcontroller configuration
# 
# Actions:
#   - If both "wireless" and "mapcontroller" are specified:
#        -> Reload mapcontroller (send SIGHUP)
#   - If only "wireless" is specified:
#        -> Commit wireless configuration via ubus
#   - If only "mapcontroller" is specified:
#        -> Reload mapcontroller (send SIGHUP)
#   - If no valid arguments are provided:
#        -> Do nothing

. /lib/functions.sh
. /usr/share/libubox/jshn.sh

MAPCONTROLLER=0
WIRELESS=0
MAX_RETRIES=15

info() {
	echo "${@}" | logger -t bbf.config.wifi.reload -p info
}

debug() {
	echo "${@}"
}

json_add_aps() {
	json_add_object
	json_add_string ssid "$1"
	json_add_string band "$2"
	json_add_string encryption "$3"
	json_add_string key "$4"
	json_add_string ifname "$5"
	json_close_object
}

parse_mapcontroller_ap() {
	local section="$1"

	config_get type "$section" type "fronthaul"
	[ "$type" = "fronthaul" ] || return 0

	config_get enabled "$section" enabled 1
	[ "$enabled" = "1" ] || return 0

	config_get ssid "$section" ssid
	[ -n "$ssid" ] || return 0

	config_get band "$section" band
	[ -n "$band" ] || return 0

	case "$band" in
		2|5|6) ;;
		*) return 0 ;;
	esac

	config_get encryption "$section" encryption "none"
	config_get key "$section" key

	json_add_aps "$ssid" "$band" "$encryption" "$key" ""
	debug "mapcontroller AP added: ssid=|$ssid| band=|$band| encryption=|$encryption| key=|$key|"
}

parse_wireless_ap() {
	local section="$1"

	config_get disabled "$section" disabled 0
	[ "$disabled" = "1" ] && return 0

	config_get multi_ap "$section" multi_ap 0
	case "$multi_ap" in
		1|2) return 0 ;;
	esac

	config_get ssid "$section" ssid
	[ -n "$ssid" ] || return 0

	config_get device "$section" device
	[ -n "$device" ] || return 0

	config_get band "$device" band
	case "$band" in
		2g) band="2" ;;
		5g) band="5" ;;
		6g) band="6" ;;
		*) return 0 ;;
	esac

	config_get radio_disabled "$device" disabled 0
	[ "$radio_disabled" = "1" ] && return 0

	config_get encryption "$section" encryption "none"
	config_get key "$section" key

	for try in $(seq 1 "$MAX_RETRIES"); do
		config_get ifname "$section" ifname
		[ -n "$ifname" ] && break
		debug "FAIL: no ifname found for ssid=|$ssid| band=|$band| (try $try/$MAX_RETRIES)"
		sleep 1
		config_load wireless
	done
	
	if [ -z "$ifname" ]; then
		info "FAIL: could not match wireless AP with ssid=|$ssid| band=|$band| after $MAX_RETRIES tries"
		return O
	fi

	json_add_aps "$ssid" "$band" "$encryption" "$key" "$ifname"
	debug "wireless AP added: ssid=|$ssid| band=|$band| ifname=|$ifname|"
}

match_ap_to_wireless_interface() {
	local section="$1"

	config_get disabled "$section" disabled 0
	[ "$disabled" = "1" ] && return 0

	config_get multi_ap "$section" multi_ap 0
	[ "$multi_ap" = "2" ] || return 0

	config_get ssid "$section" ssid
	[ "$ssid" = "$SSID" ] || return 0

	config_get device "$section" device
	[ -n "$device" ] || return 0

	config_get band "$device" band
	case "$band" in
		2g) band="2" ;;
		5g) band="5" ;;
		6g) band="6" ;;
		*) return 0 ;;
	esac
	[ "$band" = "$BAND" ] || return 0

	config_get radio_disabled "$device" disabled 0
	[ "$radio_disabled" = "1" ] && RADIO_DISABLED=1 && return 0

	config_get key "$section" key
	[ -n "$KEY" ] && [ "$key" != "$KEY" ] && return 0

	config_get encryption "$section" encryption "none"
	[ -n "$ENCRYPTION" ] && [ "$encryption" != "$ENCRYPTION" ] && return 0

	config_get MATCHED_IFNAME "$section" ifname
}

match_aps_to_wireless_interfaces() {
	config_load wireless

	json_get_keys indices

	for i in $indices; do
		json_select "$i"

		json_get_var SSID ssid
		json_get_var BAND band
		json_get_var ENCRYPTION encryption
		json_get_var KEY key

		debug "matching AP[$i]: ssid=|$SSID| band=|$BAND| encryption=|$ENCRYPTION| key=|$KEY|"

		MATCHED_IFNAME=""
		RADIO_DISABLED=0

		for try in $(seq 1 "$MAX_RETRIES"); do
			config_foreach match_ap_to_wireless_interface wifi-iface

			if [ -n "$MATCHED_IFNAME" ]; then
				json_add_string ifname "$MATCHED_IFNAME"
				debug "matched AP[$i]: ssid=|$SSID| band=|$BAND| -> ifname=|$MATCHED_IFNAME|"
				break
			fi

			[ "$RADIO_DISABLED" = "1" ] && break

			sleep 1
			config_load wireless
		done

		if [ -z "$MATCHED_IFNAME" ] && [ "$RADIO_DISABLED" != "1" ]; then
			info "FAIL: no match for AP[$i]: ssid=|$SSID| band=|$BAND| after $MAX_RETRIES tries"
		fi

		json_select ..
	done
}

verify_aps_runtime_state() {
	json_get_keys indices

	for i in $indices; do
		json_select "$i"

		json_get_var ifname ifname
		json_get_var ssid ssid
		json_get_var band band

		[ -z "$ifname" ] && json_select .. && continue

		debug "validating runtime AP[$i]: ssid=|$ssid| band=|$band| ifname=|$ifname|"

		case "$band" in
			2) bandstr="2.4GHz" ;;
			5) bandstr="5GHz" ;;
			6) bandstr="6GHz" ;;
			*) json_select .. && continue ;;
		esac

		ok=0
		for try in $(seq 1 "$MAX_RETRIES"); do
			json="$(ubus -t 2 call wifi.ap."$ifname" status 2>/dev/null)"

			if [ -z "$json" ]; then
				debug "FAIL: empty ubus response for wifi.ap.$ifname status (try $try/$MAX_RETRIES)"
				sleep 2
				continue
			fi

			ubus_ssid="$(echo "$json" | jsonfilter -q -e '@.ssid')"
			ubus_band="$(echo "$json" | jsonfilter -q -e '@.band')"

			[ "$ubus_ssid" = "$ssid" -a "$ubus_band" = "$bandstr" ] && {
				ok=1
				break
			}

			debug "FAIL: $ifname ssid/band mismatch ($try/$MAX_RETRIES) exp[$ssid/$bandstr] got[$ubus_ssid/$ubus_band]"
			sleep 2
		done

		[ "$ok" -ne 1 ] && info "FAIL: runtime validation failed for AP[$i]: ssid=|$ssid| band=|$band| ifname=|$ifname|"
		json_select ..
		debug "runtime validation completed for AP[$i]: ssid=|$ssid| band=|$band| ifname=|$ifname|"
	done
}

htmode_to_bandwidth() {
	local htmode="$1"
	# Extract trailing numeric suffix: EHT160->160, HE80->80, VHT40->40, HT20->20
	echo "$htmode" | sed 's/[^0-9]*\([0-9]*\)$/\1/'
}

validate_radio() {
	local section="$1"

	config_get disabled "$section" disabled 0
	[ "$disabled" = "1" ] && debug "radio section=|$section| is disabled, skipping" && return 0

	config_get channel "$section" channel "auto"
	config_get htmode  "$section" htmode
	config_get band    "$section" band

	expected_bw="$(htmode_to_bandwidth "$htmode")"

	debug "validating radio: section=|$section| band=|$band| channel=|$channel| htmode=|$htmode| expected_bw=|${expected_bw}MHz|"

	# Determine whether the channel is statically configured or left to ACS/auto
	case "$channel" in
		auto|"") static_channel=0 ;;
		*)       static_channel=1 ;;
	esac

	ok=0
	for try in $(seq 1 "$MAX_RETRIES"); do
		radio_json="$(ubus -t 2 call wifi.radio."$section" status 2>/dev/null)"

		if [ -z "$radio_json" ]; then
			debug "FAIL: empty ubus response for wifi.radio.$section status (try $try/$MAX_RETRIES)"
			sleep 2
			continue
		fi

		ubus_isup="$(echo "$radio_json" | jsonfilter -q -e '@.isup')"
		if [ "$ubus_isup" != "true" ]; then
			debug "FAIL: wifi.radio.$section isup=|$ubus_isup| (try $try/$MAX_RETRIES)"
			sleep 2
			continue
		fi

		if [ "$static_channel" = "1" ]; then
			# Static channel: verify both channel and bandwidth match UCI config
			ubus_channel="$(echo "$radio_json" | jsonfilter -q -e '@.channel')"
			ubus_bw="$(echo "$radio_json" | jsonfilter -q -e '@.bandwidth')"

			if [ "$ubus_channel" = "$channel" ] && [ "$ubus_bw" = "$expected_bw" ]; then
				debug "radio OK: section=|$section| channel=|$ubus_channel| bandwidth=|${ubus_bw}MHz|"
				ok=1
				break
			fi

			debug "MISMATCH: wifi.radio.$section channel=|$ubus_channel| (want $channel) bandwidth=|$ubus_bw| (want $expected_bw) (try $try/$MAX_RETRIES)"
		else
			# Auto/ACS channel: radio is valid as long as it is up and has a non-zero channel assigned
			ubus_channel="$(echo "$radio_json" | jsonfilter -q -e '@.channel')"
			if [ -n "$ubus_channel" ] && [ "$ubus_channel" != "0" ]; then
				debug "radio OK (auto channel): section=|$section| active_channel=|$ubus_channel|"
				ok=1
				break
			fi
			debug "FAIL: wifi.radio.$section auto channel not yet assigned, got=|$ubus_channel| (try $try/$MAX_RETRIES)"
		fi

		sleep 2
	done

	if [ "$ok" -ne 1 ]; then
		if [ "$static_channel" = "1" ]; then
			info "FAIL: radio validation failed for section=|$section| expected channel=|$channel| bandwidth=|${expected_bw}MHz| after $MAX_RETRIES tries"
		else
			info "FAIL: radio validation failed for section=|$section| (auto channel not assigned) after $MAX_RETRIES tries"
		fi
	fi
}

validate_radios() {
	config_load wireless
	config_foreach validate_radio wifi-device
}

validate_mapcontroller_changes() {
	json_init
	json_add_array aps
	config_load mapcontroller
	config_foreach parse_mapcontroller_ap ap
	match_aps_to_wireless_interfaces
	verify_aps_runtime_state
	json_cleanup
	validate_radios
}

validate_wireless_changes() {
	json_init
	json_add_array aps
	config_load wireless
	config_foreach parse_wireless_ap wifi-iface
	verify_aps_runtime_state
	json_cleanup
	validate_radios
}

reload_mapcontroller() {
	pid="$(pidof mapcontroller)"
	if [ -n "$pid" ]; then
		info "Reloading mapcontroller (PID: $pid)..."
		kill -SIGHUP "$pid"
		sleep 8
		validate_mapcontroller_changes
	else
		info "Warning: mapcontroller process not found"
	fi
}

reload_wireless() {
	info "Reloading wireless config..."
	ubus call uci commit '{"config":"wireless"}'
	sleep 5
	validate_wireless_changes
}

dmmap_commit_new_aps() {
	DMAP_PATH="uci -q -c /etc/bbfdm/dmmap"
	for sec in $($DMAP_PATH show WiFi | grep "=AccessPoint" | cut -d. -f2 | cut -d= -f1); do
	    is_new=$($DMAP_PATH get WiFi.${sec}.__is_new__)
	    [ "$is_new" = "1" ] || continue
	
	    sec_name=$($DMAP_PATH get WiFi.${sec}.__section_name__)
	    case "$sec_name" in
	        wireless.*) MAPCONTROLLER=0 ;;
	    esac
	    $DMAP_PATH delete WiFi.${sec}.__is_new__
	done

	$DMAP_PATH commit WiFi
}

for arg in "$@"; do
	case "$arg" in
		wireless) WIRELESS=1 ;;
		mapcontroller) MAPCONTROLLER=1 ;;
		*)
			info "Unknown option: $arg"
			exit 1
			;;
	esac
done

dmmap_commit_new_aps

if [ "$MAPCONTROLLER" -eq 1 ]; then
	reload_mapcontroller
elif [ "$WIRELESS" -eq 1 ]; then
	reload_wireless
else
	info "No valid arguments provided."
	exit 1
fi

exit 0
