#!/bin/sh
# SecuBox P2P Hub Manager
# Handles peer discovery, mesh networking, and service federation

VERSION="0.2.0"
CONFIG_FILE="/etc/config/secubox-p2p"
PEERS_FILE="/tmp/secubox-p2p-peers.json"
SERVICES_FILE="/tmp/secubox-p2p-services.json"
STATE_DIR="/var/run/secubox-p2p"
MDNS_PID_FILE="$STATE_DIR/mdns.pid"
API_PORT=7331

# Initialize
init() {
	mkdir -p "$STATE_DIR"
	[ -f "$PEERS_FILE" ] || echo '{"peers":[]}' > "$PEERS_FILE"
	[ -f "$SERVICES_FILE" ] || echo '{"services":[]}' > "$SERVICES_FILE"

	# Initialize node info
	_init_node_info
}

# Initialize node identity
_init_node_info() {
	local node_name
	node_name=$(get_config main node_name "")

	# Generate node name from hostname if not set
	if [ -z "$node_name" ]; then
		node_name=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo "secubox")
		set_config main node_name "$node_name"
	fi

	# Generate node ID if not exists
	local node_id_file="$STATE_DIR/node.id"
	if [ ! -f "$node_id_file" ]; then
		# Generate unique node ID from MAC address
		local mac
		mac=$(ip link show br-lan 2>/dev/null | grep ether | awk '{print $2}' | tr -d ':')
		[ -z "$mac" ] && mac=$(cat /proc/sys/kernel/random/uuid | tr -d '-' | head -c 12)
		echo "sb-${mac}" > "$node_id_file"
	fi
}

# Get config value
get_config() {
	local section="$1"
	local option="$2"
	local default="$3"
	uci -q get "secubox-p2p.${section}.${option}" || echo "$default"
}

# Set config value
set_config() {
	local section="$1"
	local option="$2"
	local value="$3"
	uci set "secubox-p2p.${section}.${option}=$value"
	uci commit secubox-p2p
}

# Discover peers via mDNS
discover_mdns() {
	local timeout="${1:-5}"
	local peers="[]"

	# Check if avahi-browse is available
	if command -v avahi-browse >/dev/null 2>&1; then
		# Discover _secubox._tcp services
		local discovered=$(avahi-browse -t -r _secubox._tcp 2>/dev/null | grep -E "^=|hostname|address" || true)

		if [ -n "$discovered" ]; then
			# Parse discovered services into JSON
			echo "$discovered" | awk '
				/^=/ { name=$4 }
				/hostname/ { host=$3 }
				/address/ {
					addr=$3
					if (name && addr) {
						printf "{\"id\":\"%s\",\"name\":\"%s\",\"address\":\"%s\",\"status\":\"online\"},", name, name, addr
					}
				}
			' | sed 's/,$//' | awk '{print "["$0"]"}'
		else
			echo "[]"
		fi
	else
		# Fallback: scan local network
		local gateway=$(ip route | grep default | awk '{print $3}')
		local subnet=$(echo "$gateway" | sed 's/\.[0-9]*$/./')

		# Quick ping scan
		for i in $(seq 1 254); do
			ping -c1 -W1 "${subnet}${i}" >/dev/null 2>&1 &
		done
		wait

		# Check for SecuBox peers via HTTP
		arp -n | grep -v incomplete | awk '{print $1}' | while read ip; do
			if curl -s --connect-timeout 1 "http://${ip}/cgi-bin/luci/admin/secubox" >/dev/null 2>&1; then
				echo "{\"id\":\"peer-${ip}\",\"name\":\"SecuBox@${ip}\",\"address\":\"${ip}\",\"status\":\"online\"}"
			fi
		done | jq -s '.' 2>/dev/null || echo "[]"
	fi
}

# Get peers list
get_peers() {
	if [ -f "$PEERS_FILE" ]; then
		cat "$PEERS_FILE"
	else
		echo '{"peers":[]}'
	fi
}

# Add peer
add_peer() {
	local address="$1"
	local name="${2:-Peer}"
	local id="peer-$(echo "$address" | md5sum | cut -c1-8)"

	local peers=$(get_peers)
	local new_peer="{\"id\":\"$id\",\"name\":\"$name\",\"address\":\"$address\",\"status\":\"unknown\",\"added\":\"$(date -Iseconds)\"}"

	echo "$peers" | jq ".peers += [$new_peer]" > "$PEERS_FILE"
	echo "{\"success\":true,\"peer_id\":\"$id\"}"
}

# Remove peer
remove_peer() {
	local peer_id="$1"
	local peers=$(get_peers)
	echo "$peers" | jq ".peers = [.peers[] | select(.id != \"$peer_id\")]" > "$PEERS_FILE"
	echo "{\"success\":true}"
}

# Get settings
get_settings() {
	cat <<EOF
{
	"enabled": $(get_config main enabled 1),
	"node_name": "$(get_config main node_name "")",
	"discovery_enabled": $(get_config main discovery_enabled 1),
	"sharing_enabled": $(get_config main sharing_enabled 1),
	"auto_sync": $(get_config main auto_sync 1),
	"sync_interval": $(get_config main sync_interval 60),
	"dns_federation": {
		"enabled": $(get_config dns enabled 0),
		"primary_dns": "$(get_config dns primary_dns "127.0.0.1:53")",
		"base_domain": "$(get_config dns base_domain "sb.local")"
	},
	"wireguard": {
		"enabled": $(get_config wireguard enabled 0),
		"listen_port": $(get_config wireguard listen_port 51820),
		"network_cidr": "$(get_config wireguard network_cidr "10.100.0.0/24")"
	},
	"haproxy": {
		"enabled": $(get_config haproxy enabled 0),
		"strategy": "$(get_config haproxy strategy "round-robin")"
	},
	"registry": {
		"base_url": "$(get_config registry base_url "sb.local")",
		"cache_enabled": $(get_config registry cache_enabled 1)
	}
}
EOF
}

# Set settings
set_settings() {
	local json="$1"

	# Parse and apply settings
	local enabled=$(echo "$json" | jsonfilter -e '@.enabled')
	local sharing=$(echo "$json" | jsonfilter -e '@.sharing_enabled')
	local discovery=$(echo "$json" | jsonfilter -e '@.discovery_enabled')

	[ -n "$enabled" ] && set_config main enabled "$enabled"
	[ -n "$sharing" ] && set_config main sharing_enabled "$sharing"
	[ -n "$discovery" ] && set_config main discovery_enabled "$discovery"

	echo "{\"success\":true}"
}

# Get local services - scan init.d and detect running status
get_services() {
	local services=""
	local count=0

	# Service port mapping
	get_service_port() {
		case "$1" in
			dnsmasq) echo "53" ;;
			uhttpd) echo "80" ;;
			nginx) echo "80" ;;
			haproxy) echo "80,443" ;;
			crowdsec) echo "8080" ;;
			crowdsec-firewall-bouncer) echo "" ;;
			dropbear) echo "22" ;;
			sshd) echo "22" ;;
			firewall*) echo "" ;;
			wireguard|wg*) echo "51820" ;;
			gitea) echo "3000" ;;
			localai) echo "8080" ;;
			ollama) echo "11434" ;;
			nextcloud) echo "8080" ;;
			lxc*) echo "" ;;
			rpcd) echo "" ;;
			*) echo "" ;;
		esac
	}

	# Scan init.d services
	for init_script in /etc/init.d/*; do
		[ -x "$init_script" ] || continue
		local svc_name=$(basename "$init_script")

		# Skip system services
		case "$svc_name" in
			boot|done|rcS|rc.local|umount|sysfixtime|sysntpd|gpio_switch) continue ;;
		esac

		# Check if service is running
		local status="stopped"
		local pid=""

		# Method 1: Check via init script status
		if "$init_script" status >/dev/null 2>&1; then
			if "$init_script" status 2>&1 | grep -qiE "running|active"; then
				status="running"
			fi
		fi

		# Method 2: Check via pgrep (fallback)
		if [ "$status" = "stopped" ]; then
			if pgrep -f "$svc_name" >/dev/null 2>&1; then
				status="running"
				pid=$(pgrep -f "$svc_name" | head -1)
			fi
		fi

		# Method 3: Check if enabled in UCI/procd
		local enabled="0"
		if "$init_script" enabled 2>/dev/null; then
			enabled="1"
		fi

		local port=$(get_service_port "$svc_name")

		# Build JSON entry
		if [ $count -gt 0 ]; then
			services="$services,"
		fi
		services="$services{\"name\":\"$svc_name\",\"status\":\"$status\",\"enabled\":$enabled,\"port\":\"$port\",\"pid\":\"$pid\"}"
		count=$((count + 1))
	done

	echo "{\"services\":[$services],\"total\":$count}"
}

# Get shared services (from peers)
get_shared_services() {
	local all_services="[]"
	local peers=$(get_peers | jq -r '.peers[] | select(.status=="online") | .address')

	for peer_addr in $peers; do
		local peer_services=$(curl -s --connect-timeout 2 "http://${peer_addr}:8080/p2p/services" 2>/dev/null || echo "[]")
		all_services=$(echo "$all_services" | jq ". += $peer_services")
	done

	echo "{\"shared_services\":$all_services}"
}

# Sync with peers
sync_catalog() {
	local peers=$(get_peers | jq -r '.peers[].address')
	local synced=0

	for peer_addr in $peers; do
		if curl -s --connect-timeout 2 "http://${peer_addr}/cgi-bin/luci" >/dev/null 2>&1; then
			synced=$((synced + 1))
		fi
	done

	echo "{\"success\":true,\"synced_peers\":$synced}"
}

# Broadcast command to all peers
broadcast_command() {
	local cmd="$1"
	local peers=$(get_peers | jq -r '.peers[] | select(.status=="online") | .address')
	local success=0
	local failed=0

	for peer_addr in $peers; do
		if curl -s --connect-timeout 5 -X POST "http://${peer_addr}:8080/p2p/command" -d "{\"command\":\"$cmd\"}" >/dev/null 2>&1; then
			success=$((success + 1))
		else
			failed=$((failed + 1))
		fi
	done

	echo "{\"success\":true,\"broadcast_success\":$success,\"broadcast_failed\":$failed}"
}

# Publish mDNS service announcement
publish_mdns() {
	local node_name
	node_name=$(get_config main node_name "secubox")

	# Kill any existing avahi-publish process
	[ -f "$MDNS_PID_FILE" ] && kill $(cat "$MDNS_PID_FILE") 2>/dev/null
	rm -f "$MDNS_PID_FILE"

	# Check if avahi-publish is available
	if command -v avahi-publish >/dev/null 2>&1; then
		# Get LAN IP for service registration
		local lan_ip
		lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1)
		[ -z "$lan_ip" ] && lan_ip=$(uci -q get network.lan.ipaddr)

		# Publish _secubox._tcp service
		avahi-publish -s "$node_name" "_secubox._tcp" "$API_PORT" \
			"version=$VERSION" \
			"type=mesh-node" \
			"ip=$lan_ip" &
		echo $! > "$MDNS_PID_FILE"

		logger -t secubox-p2p "mDNS service published: $node_name._secubox._tcp on port $API_PORT"
		return 0
	else
		logger -t secubox-p2p "WARNING: avahi-publish not available, mDNS disabled"
		return 1
	fi
}

# Stop mDNS service announcement
stop_mdns() {
	if [ -f "$MDNS_PID_FILE" ]; then
		kill $(cat "$MDNS_PID_FILE") 2>/dev/null
		rm -f "$MDNS_PID_FILE"
		logger -t secubox-p2p "mDNS service stopped"
	fi
}

# Get node status JSON (for REST API)
get_node_status() {
	local node_name node_id lan_ip uptime
	node_name=$(get_config main node_name "secubox")
	node_id=$(cat "$STATE_DIR/node.id" 2>/dev/null || echo "unknown")
	lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1)
	uptime=$(cat /proc/uptime | cut -d' ' -f1)

	cat <<EOF
{
	"node_id": "$node_id",
	"node_name": "$node_name",
	"version": "$VERSION",
	"address": "$lan_ip",
	"api_port": $API_PORT,
	"uptime": $uptime,
	"discovery_enabled": $(get_config main discovery_enabled 1),
	"sharing_enabled": $(get_config main sharing_enabled 1),
	"peer_count": $(get_peers | jsonfilter -e '@.peers[*]' 2>/dev/null | wc -l)
}
EOF
}

# Register self in peer list (ensure node is visible in its own mesh view)
register_self() {
	local node_name node_id lan_ip
	node_name=$(get_config main node_name "secubox")
	node_id=$(cat "$STATE_DIR/node.id" 2>/dev/null || echo "unknown")
	lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1)

	# Check if self is already registered
	local current=$(get_peers)
	local exists
	exists=$(echo "$current" | jsonfilter -e "@.peers[@.id='$node_id']" 2>/dev/null)

	if [ -z "$exists" ]; then
		local self_peer="{\"id\":\"$node_id\",\"name\":\"$node_name (local)\",\"address\":\"$lan_ip\",\"status\":\"online\",\"is_local\":true,\"added\":\"$(date -Iseconds)\"}"
		echo "$current" | jsonfilter -e '@' 2>/dev/null | sed "s/\"peers\":\[/\"peers\":[$self_peer,/" > "$PEERS_FILE.tmp"
		if [ -s "$PEERS_FILE.tmp" ]; then
			mv "$PEERS_FILE.tmp" "$PEERS_FILE"
		else
			# Fallback: just create a fresh peers file with self
			echo "{\"peers\":[$self_peer]}" > "$PEERS_FILE"
		fi
		logger -t secubox-p2p "Registered local node: $node_name ($node_id)"
	fi
}

# Daemon mode
daemon_loop() {
	init

	# Publish mDNS service
	publish_mdns

	# Register self in peer list
	register_self

	# Setup signal handlers
	trap 'stop_mdns; exit 0' INT TERM

	while true; do
		# Auto-discovery if enabled
		if [ "$(get_config main discovery_enabled 1)" = "1" ]; then
			local discovered=$(discover_mdns 3)
			if [ "$discovered" != "[]" ] && [ -n "$discovered" ]; then
				# Update peers file with discovered peers
				local current=$(get_peers)
				# Parse discovered peers (handle jq or jsonfilter)
				if command -v jq >/dev/null 2>&1; then
					for peer in $(echo "$discovered" | jq -c '.[]' 2>/dev/null); do
						local peer_id=$(echo "$peer" | jq -r '.id')
						local exists=$(echo "$current" | jq ".peers[] | select(.id==\"$peer_id\")")
						if [ -z "$exists" ]; then
							current=$(echo "$current" | jq ".peers += [$peer]")
						fi
					done
					echo "$current" > "$PEERS_FILE"
				fi
			fi
		fi

		# Update peer status (skip local node)
		local peers=$(get_peers)
		if command -v jq >/dev/null 2>&1; then
			local updated_peers=$(echo "$peers" | jq -c '.peers[] | select(.is_local != true)' 2>/dev/null)

			for peer in $updated_peers; do
				local addr=$(echo "$peer" | jq -r '.address')
				local id=$(echo "$peer" | jq -r '.id')

				if ping -c1 -W1 "$addr" >/dev/null 2>&1; then
					peers=$(echo "$peers" | jq "(.peers[] | select(.id==\"$id\")).status = \"online\"")
				else
					peers=$(echo "$peers" | jq "(.peers[] | select(.id==\"$id\")).status = \"offline\"")
				fi
			done

			echo "$peers" > "$PEERS_FILE"
		fi

		# Sleep interval
		local interval=$(get_config main sync_interval 60)
		sleep "$interval"
	done
}

# Main
case "$1" in
	daemon)
		daemon_loop
		;;
	discover)
		discover_mdns "${2:-5}"
		;;
	peers)
		get_peers
		;;
	add-peer)
		add_peer "$2" "$3"
		;;
	remove-peer)
		remove_peer "$2"
		;;
	settings)
		get_settings
		;;
	set-settings)
		set_settings "$2"
		;;
	services)
		get_services
		;;
	shared-services)
		get_shared_services
		;;
	sync)
		sync_catalog
		;;
	broadcast)
		broadcast_command "$2"
		;;
	version)
		echo "$VERSION"
		;;
	status)
		get_node_status
		;;
	publish-mdns)
		publish_mdns
		;;
	stop-mdns)
		stop_mdns
		;;
	register-self)
		init
		register_self
		;;
	*)
		echo "Usage: $0 {daemon|discover|peers|add-peer|remove-peer|settings|set-settings|services|shared-services|sync|broadcast|version|status|publish-mdns|stop-mdns|register-self}"
		exit 1
		;;
esac
