#!/bin/sh
# SecuBox Device Intelligence — Core Aggregation Library
# Sources data from mac-guardian, client-guardian, DHCP, P2P, exposure, and emulators

. /lib/functions.sh

DI_CONFIG="device-intel"
DI_CACHE_DIR="/tmp/device-intel"
DI_CACHE_FILE="${DI_CACHE_DIR}/cache-devices.json"
DI_EMULATOR_DIR="/usr/lib/secubox/device-intel/emulators"

# ============================================================================
# Config helpers
# ============================================================================

di_uci_get() {
	uci -q get ${DI_CONFIG}.$1
}

di_cache_valid() {
	local ttl=$(di_uci_get main.cache_ttl)
	ttl=${ttl:-30}

	[ ! -f "$DI_CACHE_FILE" ] && return 1

	local now=$(date +%s)
	local mtime=$(date -r "$DI_CACHE_FILE" +%s 2>/dev/null)
	[ -z "$mtime" ] && return 1

	local age=$((now - mtime))
	[ "$age" -lt "$ttl" ] && return 0
	return 1
}

di_invalidate_cache() {
	rm -f "${DI_CACHE_DIR}"/cache*.json 2>/dev/null
}

# ============================================================================
# Data Source Collectors
# ============================================================================

# Collect mac-guardian client data → pipe-delimited lines
# Format: mac|vendor|iface|hostname|status|randomized|first_seen|last_seen
di_collect_mac_guardian() {
	local db="/var/run/mac-guardian/known.db"
	local oui_db="/usr/lib/secubox/mac-guardian/oui.tsv"

	[ ! -f "$db" ] && return

	while IFS='|' read -r mac oui first_seen last_seen iface hostname status rest; do
		[ -z "$mac" ] && continue
		local vendor=""
		if [ -n "$oui" ] && [ -f "$oui_db" ]; then
			vendor=$(grep -i "^${oui}" "$oui_db" 2>/dev/null | cut -f2 | head -1)
		fi
		# Detect randomized MAC (local bit set)
		local randomized=0
		local second_char=$(echo "$mac" | cut -c2)
		case "$second_char" in
			2|3|6|7|a|b|e|f|A|B|E|F) randomized=1 ;;
		esac
		echo "mg|${mac}|${vendor}|${iface}|${hostname}|${status}|${randomized}|${first_seen}|${last_seen}"
	done < "$db"
}

# Collect client-guardian data via UCI
# Format: cg|mac|name|zone|status|first_seen|last_seen
di_collect_client_guardian() {
	local idx=0
	while uci -q get client-guardian.@client[$idx] >/dev/null 2>&1; do
		local mac=$(uci -q get client-guardian.@client[$idx].mac)
		local name=$(uci -q get client-guardian.@client[$idx].name)
		local zone=$(uci -q get client-guardian.@client[$idx].zone)
		local status=$(uci -q get client-guardian.@client[$idx].status)
		local first_seen=$(uci -q get client-guardian.@client[$idx].first_seen)
		local last_seen=$(uci -q get client-guardian.@client[$idx].last_seen)

		[ -n "$mac" ] && echo "cg|${mac}|${name}|${zone}|${status}|${first_seen}|${last_seen}"
		idx=$((idx + 1))
	done
}

# Collect ARP table for IP-to-MAC resolution and online detection
# Format: arp|mac|ip|iface
di_collect_arp() {
	while read -r ip hw flags mac mask iface; do
		[ "$ip" = "IP" ] && continue  # skip header
		[ "$mac" = "00:00:00:00:00:00" ] && continue
		[ "$flags" = "0x0" ] && continue  # incomplete entry
		echo "arp|${mac}|${ip}|${iface}"
	done < /proc/net/arp
}

# Collect DHCP lease data
# Format: dhcp|mac|ip|hostname|expires
di_collect_dhcp() {
	local lease_file="/tmp/dhcp.leases"
	[ ! -f "$lease_file" ] && return

	while read -r expires mac ip hostname clientid; do
		[ -z "$mac" ] && continue
		[ "$mac" = "*" ] && continue
		echo "dhcp|${mac}|${ip}|${hostname}|${expires}"
	done < "$lease_file"
}

# Collect P2P mesh peers
# Format: p2p|id|address|name|status|services
di_collect_p2p_peers() {
	# Try ubus first
	local peers=$(ubus call luci.secubox-p2p get_peers 2>/dev/null)
	[ -z "$peers" ] && return

	echo "$peers" | jsonfilter -e '@.peers[*]' 2>/dev/null | while read -r peer_json; do
		local id=$(echo "$peer_json" | jsonfilter -e '@.id' 2>/dev/null)
		local addr=$(echo "$peer_json" | jsonfilter -e '@.address' 2>/dev/null)
		local name=$(echo "$peer_json" | jsonfilter -e '@.name' 2>/dev/null)
		local status=$(echo "$peer_json" | jsonfilter -e '@.status' 2>/dev/null)
		[ -n "$id" ] && echo "p2p|${id}|${addr}|${name}|${status}"
	done
}

# Collect exposure scan data (listening services by IP)
# Format: exp|ip|port|service_name
di_collect_exposure() {
	# Simple port scan from /proc/net/tcp
	local hex_ports=""
	while read -r sl local_addr rem_addr st rest; do
		[ "$st" != "0A" ] && continue  # 0A = LISTEN
		local hex_port=$(echo "$local_addr" | cut -d: -f2)
		local port=$(printf "%d" "0x${hex_port}" 2>/dev/null)
		local hex_ip=$(echo "$local_addr" | cut -d: -f1)

		# Determine bind address
		local bind="local"
		[ "$hex_ip" = "00000000" ] && bind="all"

		echo "exp|${bind}|${port}"
	done < /proc/net/tcp 2>/dev/null
}

# ============================================================================
# Emulator Collection
# ============================================================================

di_collect_emulators() {
	for emu in usb mqtt zigbee; do
		local enabled=$(di_uci_get "${emu}.enabled")
		[ "$enabled" != "1" ] && continue
		local script="${DI_EMULATOR_DIR}/${emu}.sh"
		[ -f "$script" ] && {
			. "$script"
			emulate_${emu} 2>/dev/null
		}
	done
}

# ============================================================================
# Merge & Aggregate
# ============================================================================

# Merge all sources into a unified device map keyed by MAC
# Output: JSON array of device objects
di_aggregate_devices() {
	local tmp_dir="${DI_CACHE_DIR}/collect_$$"
	mkdir -p "$tmp_dir"

	# Collect from all sources
	di_collect_mac_guardian > "${tmp_dir}/mg.dat" 2>/dev/null
	di_collect_client_guardian > "${tmp_dir}/cg.dat" 2>/dev/null
	di_collect_dhcp > "${tmp_dir}/dhcp.dat" 2>/dev/null
	di_collect_arp > "${tmp_dir}/arp.dat" 2>/dev/null
	di_collect_p2p_peers > "${tmp_dir}/p2p.dat" 2>/dev/null
	di_collect_exposure > "${tmp_dir}/exp.dat" 2>/dev/null
	di_collect_emulators > "${tmp_dir}/emu.dat" 2>/dev/null

	# Build unified device map
	# Step 1: Extract all unique MACs
	local all_macs=$(cat "${tmp_dir}"/*.dat 2>/dev/null | \
		awk -F'|' '{print tolower($2)}' | \
		grep -E '^[0-9a-f]{2}(:[0-9a-f]{2}){5}$' | \
		sort -u)

	# Step 2: Build JSON output
	local first=1
	printf '['

	for mac in $all_macs; do
		[ $first -eq 1 ] && first=0 || printf ','

		# Defaults
		local ip="" hostname="" vendor="" iface="" online="false"
		local mg_status="" cg_zone="" cg_status="" randomized="false"
		local first_seen="" last_seen="" label="" device_type=""
		local device_type_source="" emulator_source=""
		local source_node="local"

		# Mac-Guardian data
		local mg=$(grep "^mg|${mac}|" "${tmp_dir}/mg.dat" 2>/dev/null | head -1)
		if [ -n "$mg" ]; then
			vendor=$(echo "$mg" | cut -d'|' -f3)
			iface=$(echo "$mg" | cut -d'|' -f4)
			hostname=$(echo "$mg" | cut -d'|' -f5)
			mg_status=$(echo "$mg" | cut -d'|' -f6)
			local rand_flag=$(echo "$mg" | cut -d'|' -f7)
			[ "$rand_flag" = "1" ] && randomized="true"
			first_seen=$(echo "$mg" | cut -d'|' -f8)
			last_seen=$(echo "$mg" | cut -d'|' -f9)
		fi

		# Client-Guardian data (format: cg|mac|name|zone|status|first_seen|last_seen)
		local cg=$(grep -i "^cg|${mac}|" "${tmp_dir}/cg.dat" 2>/dev/null | head -1)
		if [ -n "$cg" ]; then
			local cg_name=$(echo "$cg" | cut -d'|' -f3)
			[ -n "$cg_name" ] && [ -z "$hostname" ] && hostname="$cg_name"
			cg_zone=$(echo "$cg" | cut -d'|' -f4)
			cg_status=$(echo "$cg" | cut -d'|' -f5)
			local cg_first=$(echo "$cg" | cut -d'|' -f6)
			local cg_last=$(echo "$cg" | cut -d'|' -f7)
			[ -n "$cg_first" ] && [ -z "$first_seen" ] && first_seen="$cg_first"
			[ -n "$cg_last" ] && [ -z "$last_seen" ] && last_seen="$cg_last"
		fi

		# ARP data — provides IP and online detection
		local arp=$(grep -i "|${mac}|" "${tmp_dir}/arp.dat" 2>/dev/null | head -1)
		if [ -n "$arp" ]; then
			local arp_ip=$(echo "$arp" | cut -d'|' -f3)
			[ -n "$arp_ip" ] && [ -z "$ip" ] && ip="$arp_ip"
			local arp_iface=$(echo "$arp" | cut -d'|' -f4)
			[ -n "$arp_iface" ] && [ -z "$iface" ] && iface="$arp_iface"
			online="true"  # present in ARP = reachable
		fi

		# DHCP data
		local dhcp=$(grep "^dhcp|${mac}|" "${tmp_dir}/dhcp.dat" 2>/dev/null | head -1)
		if [ -n "$dhcp" ]; then
			local dhcp_ip=$(echo "$dhcp" | cut -d'|' -f3)
			[ -n "$dhcp_ip" ] && [ -z "$ip" ] && ip="$dhcp_ip"
			local dhcp_host=$(echo "$dhcp" | cut -d'|' -f4)
			[ -n "$dhcp_host" ] && [ "$dhcp_host" != "*" ] && [ -z "$hostname" ] && hostname="$dhcp_host"
			local dhcp_exp=$(echo "$dhcp" | cut -d'|' -f5)
			# Active lease = online
			local now=$(date +%s)
			[ -n "$dhcp_exp" ] && [ "$dhcp_exp" -gt "$now" ] 2>/dev/null && online="true"
		fi

		# Emulator data
		local emu=$(grep "^emu|${mac}|" "${tmp_dir}/emu.dat" 2>/dev/null | head -1)
		if [ -n "$emu" ]; then
			emulator_source=$(echo "$emu" | cut -d'|' -f3)
			local emu_label=$(echo "$emu" | cut -d'|' -f4)
			[ -n "$emu_label" ] && [ -z "$hostname" ] && hostname="$emu_label"
		fi

		# User override from UCI
		local mac_clean=$(echo "$mac" | tr -d ':')
		local user_type=$(uci -q get ${DI_CONFIG}.${mac_clean}.type)
		local user_label=$(uci -q get ${DI_CONFIG}.${mac_clean}.label)
		[ -n "$user_label" ] && label="$user_label"

		# OUI vendor lookup fallback (for devices not in mac-guardian)
		if [ -z "$vendor" ]; then
			local oui_prefix=$(echo "$mac" | cut -d: -f1-3 | tr 'a-f' 'A-F')
			local oui_file="/usr/lib/secubox/mac-guardian/oui.tsv"
			if [ -n "$oui_prefix" ] && [ -f "$oui_file" ]; then
				vendor=$(grep -i "^${oui_prefix}	" "$oui_file" 2>/dev/null | cut -f2 | head -1)
			fi
		fi

		# Determine connection type
		local connection_type="ethernet"
		case "$iface" in
			wlan*|phy*) connection_type="wifi" ;;
		esac

		# Classify device
		if [ -n "$user_type" ]; then
			device_type="$user_type"
			device_type_source="user"
		elif [ -n "$emulator_source" ]; then
			device_type="${emulator_source}_device"
			device_type_source="emulator"
		fi

		# JSON output
		printf '{'
		printf '"mac":"%s"' "$mac"
		printf ',"ip":"%s"' "$ip"
		printf ',"hostname":"%s"' "$hostname"
		[ -n "$label" ] && printf ',"label":"%s"' "$label"
		printf ',"vendor":"%s"' "$vendor"
		printf ',"online":%s' "$online"
		printf ',"connection_type":"%s"' "$connection_type"
		printf ',"iface":"%s"' "$iface"
		printf ',"randomized":%s' "$randomized"
		[ -n "$mg_status" ] && printf ',"mg_status":"%s"' "$mg_status"
		[ -n "$cg_zone" ] && printf ',"cg_zone":"%s"' "$cg_zone"
		[ -n "$cg_status" ] && printf ',"cg_status":"%s"' "$cg_status"
		[ -n "$device_type" ] && printf ',"device_type":"%s"' "$device_type"
		[ -n "$device_type_source" ] && printf ',"device_type_source":"%s"' "$device_type_source"
		[ -n "$emulator_source" ] && printf ',"emulator_source":"%s"' "$emulator_source"
		printf ',"source_node":"%s"' "$source_node"
		[ -n "$first_seen" ] && printf ',"first_seen":"%s"' "$first_seen"
		[ -n "$last_seen" ] && printf ',"last_seen":"%s"' "$last_seen"
		printf '}'
	done

	# Add P2P mesh peers as pseudo-devices
	if [ -s "${tmp_dir}/p2p.dat" ]; then
		while IFS='|' read -r src id addr name status rest; do
			[ -z "$id" ] && continue
			[ $first -eq 1 ] && first=0 || printf ','
			printf '{'
			printf '"mac":"mesh-%s"' "$id"
			printf ',"ip":"%s"' "$(echo "$addr" | cut -d: -f1)"
			printf ',"hostname":"%s"' "$name"
			printf ',"online":%s' "$([ "$status" = "online" ] && echo true || echo false)"
			printf ',"device_type":"mesh_peer"'
			printf ',"device_type_source":"p2p"'
			printf ',"source_node":"mesh"'
			printf '}'
		done < "${tmp_dir}/p2p.dat"
	fi

	# Add emulator-only entries (no MAC)
	if [ -s "${tmp_dir}/emu.dat" ]; then
		grep "^emu|synth-" "${tmp_dir}/emu.dat" 2>/dev/null | while IFS='|' read -r src synth_id emu_type emu_name emu_model emu_caps; do
			[ -z "$synth_id" ] && continue
			[ $first -eq 1 ] && first=0 || printf ','
			printf '{'
			printf '"mac":"%s"' "$synth_id"
			printf ',"hostname":"%s"' "$emu_name"
			printf ',"vendor":"%s"' "$emu_model"
			printf ',"online":true'
			printf ',"device_type":"%s_device"' "$emu_type"
			printf ',"device_type_source":"emulator"'
			printf ',"emulator_source":"%s"' "$emu_type"
			printf ',"source_node":"local"'
			printf '}'
		done
	fi

	printf ']'

	# Cleanup
	rm -rf "$tmp_dir"
}

# Get cached or fresh device list
di_get_devices() {
	mkdir -p "$DI_CACHE_DIR"

	if di_cache_valid; then
		cat "$DI_CACHE_FILE"
	else
		di_aggregate_devices | tee "$DI_CACHE_FILE"
	fi
}

# ============================================================================
# Summary Statistics
# ============================================================================

di_get_summary() {
	local devices=$(di_get_devices)
	local total=0 online=0 mesh=0

	total=$(echo "$devices" | jsonfilter -e '@[*].mac' 2>/dev/null | wc -l)
	online=$(echo "$devices" | jsonfilter -e '@[*].online' 2>/dev/null | grep -c "true")
	mesh=$(echo "$devices" | jsonfilter -e '@[*].device_type' 2>/dev/null | grep -c "mesh_peer")

	printf '{"total":%d,"online":%d,"mesh_peers":%d}' \
		"$total" "$online" "$mesh"
}

# ============================================================================
# Device Type Registry
# ============================================================================

di_get_device_types() {
	printf '['
	local first=1

	config_load "$DI_CONFIG"

	get_type() {
		local section="$1"
		local name icon color

		config_get name "$section" name
		config_get icon "$section" icon
		config_get color "$section" color

		[ -z "$name" ] && return

		[ $first -eq 1 ] && first=0 || printf ','
		printf '{"id":"%s","name":"%s","icon":"%s","color":"%s"}' \
			"$section" "$name" "$icon" "$color"
	}
	config_foreach get_type device_type

	printf ']'
}
