#!/bin/sh
#
# SecuBox MQTT Bridge daemon
# Handles USB adapter discovery, stats tracking, and automation hooks.

. /lib/functions.sh

UCI_NAMESPACE="mqtt-bridge"
LOGTAG="mqtt-bridge"
SCAN_INTERVAL=10
COMMIT_NEEDED=0
START_TIME="$(date +%s)"
PREV_PAYLOAD_COUNT=0
PREV_TIMESTAMP="$START_TIME"
LAST_EVENT=""

log_msg() {
	logger -t "$LOGTAG" "$*"
}

format_duration() {
	local seconds="$1"
	local h=$((seconds / 3600))
	local m=$(( (seconds % 3600) / 60 ))
	local s=$((seconds % 60))
	printf '%02dh %02dm %02ds' "$h" "$m" "$s"
}

load_interval() {
	config_load "$UCI_NAMESPACE"
	config_get interval monitor interval
	[ -n "$interval" ] && SCAN_INTERVAL="$interval"
}

find_usb_device() {
	local vendor="$1"
	local product="$2"
	local dev

	for dev in /sys/bus/usb/devices/*; do
		[ -f "$dev/idVendor" ] || continue
		[ -f "$dev/idProduct" ] || continue
		local idVendor idProduct
		idVendor="$(cat "$dev/idVendor" 2>/dev/null)"
		idProduct="$(cat "$dev/idProduct" 2>/dev/null)"
		[ "$idVendor" = "$vendor" ] || continue
		[ "$idProduct" = "$product" ] || continue
		echo "$dev"
		return 0
	done

	return 1
}

find_usb_tty() {
	local base="$1"
	local path node
	for path in "$base" "$base"/* "$base"/*/*; do
		[ -d "$path/tty" ] || continue
		for node in "$path"/tty/*; do
			[ -e "$node" ] || continue
			local tty
			tty="$(basename "$node")"
			[ -e "/dev/$tty" ] && { echo "/dev/$tty"; return 0; }
		done
	done
	return 1
}

set_option_if_changed() {
	local section="$1"
	local key="$2"
	local value="$3"
	local current
	current="$(uci -q get ${UCI_NAMESPACE}.adapter.${section}.${key} 2>/dev/null)"
	[ "$current" = "$value" ] && return
	uci set ${UCI_NAMESPACE}.adapter.${section}.${key}="$value"
	COMMIT_NEEDED=1
}

clear_option_if_needed() {
	local section="$1"
	local key="$2"
	local current
	current="$(uci -q get ${UCI_NAMESPACE}.adapter.${section}.${key} 2>/dev/null)"
	[ -z "$current" ] && return
	uci delete ${UCI_NAMESPACE}.adapter.${section}.${key}
	COMMIT_NEEDED=1
}

publish_alert() {
	local message="$1"
	local topic="$2"
	[ -n "$message" ] || return
	log_msg "ALERT: $message"
	if [ -n "$topic" ]; then
		printf '%s %s\n' "$(date -Iseconds)" "$message" >> /tmp/mqtt-bridge-alerts.log
	fi
}

RULE_MATCH_ADAPTER=""
RULE_MATCH_STATE=""

apply_rule() {
	local section="$1"
	local target when action message topic
	config_get target "$section" adapter
	config_get when "$section" when
	config_get action "$section" action
	config_get message "$section" message
	config_get topic "$section" topic
	[ "$RULE_MATCH_ADAPTER" = "$target" ] || return
	[ "$RULE_MATCH_STATE" = "$when" ] || return

	case "$action" in
		alert)
			publish_alert "$message" "$topic"
		;;
		rescan)
			log_msg "Rule $section triggered rescan for $target"
			run_detection_once
		;;
	esac
}

run_rules() {
	RULE_MATCH_ADAPTER="$1"
	RULE_MATCH_STATE="$2"
	config_foreach apply_rule rule
}

update_adapter_section() {
	local section="$1"
	local enabled vendor product title preset

	config_get enabled "$section" enabled "1"
	config_get vendor "$section" vendor
	config_get product "$section" product
	config_get preset "$section" preset
	config_get title "$section" title

	if [ "$enabled" != "1" ]; then
		set_option_if_changed "$section" detected "0"
		set_option_if_changed "$section" health "disabled"
		return
	fi

	if [ -z "$vendor" ] || [ -z "$product" ]; then
		set_option_if_changed "$section" detected "0"
		set_option_if_changed "$section" health "unknown"
		return
	fi

	local dev_path
	dev_path="$(find_usb_device "$vendor" "$product")" || dev_path=""

	local prev_detected
	prev_detected="$(uci -q get ${UCI_NAMESPACE}.adapter.${section}.detected 2>/dev/null)"

	if [ -n "$dev_path" ]; then
		local bus devnum port ts
		bus="$(cat "$dev_path/busnum" 2>/dev/null)"
		devnum="$(cat "$dev_path/devnum" 2>/dev/null)"
		port="$(find_usb_tty "$dev_path")"
		ts="$(date -Iseconds)"

		set_option_if_changed "$section" detected "1"
		set_option_if_changed "$section" health "online"
		[ -n "$bus" ] && set_option_if_changed "$section" bus "$bus"
		[ -n "$devnum" ] && set_option_if_changed "$section" device "$devnum"
		if [ -n "$port" ]; then
			set_option_if_changed "$section" port "$port"
		else
			clear_option_if_needed "$section" port
		fi
		set_option_if_changed "$section" last_seen "$ts"

		if [ "$prev_detected" != "1" ]; then
			LAST_EVENT="$ts"
			log_msg "Adapter $section ($title) detected on bus $bus dev $devnum $port"
			run_rules "$section" "online"
		fi
	else
		set_option_if_changed "$section" detected "0"
		set_option_if_changed "$section" health "missing"
		clear_option_if_needed "$section" port
		clear_option_if_needed "$section" bus
		clear_option_if_needed "$section" device
		if [ "$prev_detected" = "1" ]; then
			LAST_EVENT="$(date -Iseconds)"
			log_msg "Adapter $section ($title) disconnected"
			run_rules "$section" "missing"
		fi
	fi
}

count_enabled_clients() {
	local count=0
	config_foreach _count_client adapter "$1"
	echo "$count"
}

_count_client() {
	local section="$1"
	local enabled detected
	config_get enabled "$section" enabled 1
	config_get detected "$section" detected 0
	if [ "$enabled" = "1" ] && [ "$detected" = "1" ]; then
		count=$((count + 1))
	fi
}

payload_count() {
	uci -q show mqtt-bridge.payloads 2>/dev/null | grep -c '=payload'
}

update_stats() {
	local now payloads clients delta count elapsed
	now="$(date +%s)"

	config_load "$UCI_NAMESPACE"
	count=0
	config_foreach _count_client adapter
	clients="$count"

	payloads="$(payload_count)"
	elapsed=$((now - PREV_TIMESTAMP))
	if [ "$elapsed" -gt 0 ]; then
		delta=$((payloads - PREV_PAYLOAD_COUNT))
		if [ "$delta" -lt 0 ]; then
			delta=0
		fi
		local mps=$((delta / elapsed))
		uci set ${UCI_NAMESPACE}.stats.mps="$mps"
	fi
	PREV_PAYLOAD_COUNT="$payloads"
	PREV_TIMESTAMP="$now"

	uci set ${UCI_NAMESPACE}.stats.clients="$clients"
	uci set ${UCI_NAMESPACE}.stats.retained="${payloads:-0}"
	uci set ${UCI_NAMESPACE}.stats.uptime="$(format_duration $((now - START_TIME)))"
	[ -n "$LAST_EVENT" ] && uci set ${UCI_NAMESPACE}.stats.last_event="$LAST_EVENT"

	uci commit ${UCI_NAMESPACE}
}

run_detection_once() {
	COMMIT_NEEDED=0
	config_load "$UCI_NAMESPACE"
	config_foreach update_adapter_section adapter
	if [ "$COMMIT_NEEDED" -eq 1 ]; then
		uci commit "$UCI_NAMESPACE"
	fi
	update_stats
}

daemon_loop() {
	while true; do
		load_interval
		run_detection_once
		sleep "$SCAN_INTERVAL"
	done
}

case "$1" in
	--rescan)
		run_detection_once
		;;
	--oneshot)
		run_detection_once
		;;
	*)
		daemon_loop
		;;
esac
