#!/bin/sh
# Bandwidth Manager RPCD Backend
# Provides QoS rules, client quotas, and traffic statistics

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

# Configuration paths
CONFIG_FILE="/etc/config/bandwidth"
USAGE_DB="/tmp/bandwidth_usage.db"
IPTABLES_CHAIN="BW_TRACKING"

# Initialize usage database
init_usage_db() {
	if [ ! -f "$USAGE_DB" ]; then
		cat > "$USAGE_DB" << 'EOF'
# MAC|Timestamp|RX_Bytes|TX_Bytes
EOF
	fi
}

# Get system status and global stats
status() {
	json_init

	local enabled interface sqm_enabled
	config_load bandwidth
	config_get enabled global enabled "0"
	config_get interface global interface "br-lan"
	config_get sqm_enabled global sqm_enabled "0"

	json_add_boolean "enabled" "$enabled"
	json_add_string "interface" "$interface"
	json_add_boolean "sqm_enabled" "$sqm_enabled"

	# Check QoS status
	local qos_active=0
	tc qdisc show dev "$interface" 2>/dev/null | grep -qE "(cake|htb|fq_codel)" && qos_active=1
	json_add_boolean "qos_active" "$qos_active"

	# Get interface stats
	if [ -d "/sys/class/net/$interface" ]; then
		local rx_bytes=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0)
		local tx_bytes=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0)
		local rx_packets=$(cat /sys/class/net/$interface/statistics/rx_packets 2>/dev/null || echo 0)
		local tx_packets=$(cat /sys/class/net/$interface/statistics/tx_packets 2>/dev/null || echo 0)

		json_add_object "stats"
		json_add_int "rx_bytes" "$rx_bytes"
		json_add_int "tx_bytes" "$tx_bytes"
		json_add_int "rx_packets" "$rx_packets"
		json_add_int "tx_packets" "$tx_packets"
		json_close_object
	fi

	# Count rules and quotas
	local rule_count=0
	local quota_count=0
	config_foreach count_section rule && rule_count=$?
	config_foreach count_section quota && quota_count=$?

	json_add_int "rule_count" "$rule_count"
	json_add_int "quota_count" "$quota_count"

	json_dump
}

count_section() {
	return $(( $? + 1 ))
}

# List all QoS rules
list_rules() {
	config_load bandwidth
	json_init
	json_add_array "rules"

	_add_rule() {
		local name type target limit_down limit_up priority enabled schedule
		config_get name "$1" name ""
		config_get type "$1" type "application"
		config_get target "$1" target ""
		config_get limit_down "$1" limit_down "0"
		config_get limit_up "$1" limit_up "0"
		config_get priority "$1" priority "5"
		config_get enabled "$1" enabled "1"
		config_get schedule "$1" schedule ""

		json_add_object ""
		json_add_string "id" "$1"
		json_add_string "name" "$name"
		json_add_string "type" "$type"
		json_add_string "target" "$target"
		json_add_int "limit_down" "$limit_down"
		json_add_int "limit_up" "$limit_up"
		json_add_int "priority" "$priority"
		json_add_boolean "enabled" "$enabled"
		json_add_string "schedule" "$schedule"
		json_close_object
	}

	config_foreach _add_rule rule

	json_close_array
	json_dump
}

# Add new QoS rule
add_rule() {
	read -r input
	json_load "$input"

	local name type target limit_down limit_up priority
	json_get_var name name
	json_get_var type type "application"
	json_get_var target target
	json_get_var limit_down limit_down "0"
	json_get_var limit_up limit_up "0"
	json_get_var priority priority "5"

	json_cleanup

	if [ -z "$name" ] || [ -z "$target" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Name and target are required"
		json_dump
		return 1
	fi

	# Generate unique ID
	local rule_id="rule_$(date +%s)"

	# Add to UCI config
	uci -q batch << EOF
set bandwidth.$rule_id=rule
set bandwidth.$rule_id.name='$name'
set bandwidth.$rule_id.type='$type'
set bandwidth.$rule_id.target='$target'
set bandwidth.$rule_id.limit_down='$limit_down'
set bandwidth.$rule_id.limit_up='$limit_up'
set bandwidth.$rule_id.priority='$priority'
set bandwidth.$rule_id.enabled='1'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "rule_id" "$rule_id"
	json_add_string "message" "Rule created successfully"
	json_dump
}

# Delete QoS rule
delete_rule() {
	read -r input
	json_load "$input"

	local rule_id
	json_get_var rule_id rule_id
	json_cleanup

	if [ -z "$rule_id" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Rule ID is required"
		json_dump
		return 1
	fi

	# Check if rule exists
	if ! uci -q get bandwidth.$rule_id >/dev/null 2>&1; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Rule not found"
		json_dump
		return 1
	fi

	uci -q delete bandwidth.$rule_id
	uci -q commit bandwidth

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Rule deleted successfully"
	json_dump
}

# List all client quotas
list_quotas() {
	config_load bandwidth
	json_init
	json_add_array "quotas"

	_add_quota() {
		local mac name limit_mb used_mb action reset_day enabled
		config_get mac "$1" mac ""
		config_get name "$1" name ""
		config_get limit_mb "$1" limit_mb "0"
		config_get action "$1" action "throttle"
		config_get reset_day "$1" reset_day "1"
		config_get enabled "$1" enabled "1"

		# Get current usage
		used_mb=$(get_mac_usage "$mac")
		local percent=0
		if [ "$limit_mb" -gt 0 ]; then
			percent=$(( (used_mb * 100) / limit_mb ))
		fi

		json_add_object ""
		json_add_string "id" "$1"
		json_add_string "mac" "$mac"
		json_add_string "name" "$name"
		json_add_int "limit_mb" "$limit_mb"
		json_add_int "used_mb" "$used_mb"
		json_add_int "percent" "$percent"
		json_add_string "action" "$action"
		json_add_int "reset_day" "$reset_day"
		json_add_boolean "enabled" "$enabled"
		json_close_object
	}

	config_foreach _add_quota quota

	json_close_array
	json_dump
}

# Get usage for a specific MAC address
get_mac_usage() {
	local mac="$1"
	local total_bytes=0

	# Get from iptables counters
	if iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -qi "$mac"; then
		local bytes=$(iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -i "$mac" | awk '{sum+=$2} END {print sum}')
		total_bytes=${bytes:-0}
	fi

	# Convert to MB
	echo $(( total_bytes / 1024 / 1024 ))
}

# Get quota details for specific client
get_quota() {
	read -r input
	json_load "$input"

	local mac
	json_get_var mac mac
	json_cleanup

	if [ -z "$mac" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "MAC address is required"
		json_dump
		return 1
	fi

	config_load bandwidth
	local found=0
	local quota_id name limit_mb action reset_day

	_find_quota() {
		local this_mac
		config_get this_mac "$1" mac ""
		if [ "$this_mac" = "$mac" ]; then
			quota_id="$1"
			config_get name "$1" name ""
			config_get limit_mb "$1" limit_mb "0"
			config_get action "$1" action "throttle"
			config_get reset_day "$1" reset_day "1"
			found=1
		fi
	}

	config_foreach _find_quota quota

	if [ "$found" -eq 0 ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Quota not found for this MAC"
		json_dump
		return 1
	fi

	local used_mb=$(get_mac_usage "$mac")
	local remaining_mb=$(( limit_mb - used_mb ))
	[ "$remaining_mb" -lt 0 ] && remaining_mb=0

	local percent=0
	if [ "$limit_mb" -gt 0 ]; then
		percent=$(( (used_mb * 100) / limit_mb ))
	fi

	json_init
	json_add_boolean "success" 1
	json_add_string "quota_id" "$quota_id"
	json_add_string "mac" "$mac"
	json_add_string "name" "$name"
	json_add_int "limit_mb" "$limit_mb"
	json_add_int "used_mb" "$used_mb"
	json_add_int "remaining_mb" "$remaining_mb"
	json_add_int "percent" "$percent"
	json_add_string "action" "$action"
	json_add_int "reset_day" "$reset_day"
	json_dump
}

# Set or update quota
set_quota() {
	read -r input
	json_load "$input"

	local mac name limit_mb action reset_day
	json_get_var mac mac
	json_get_var name name ""
	json_get_var limit_mb limit_mb "0"
	json_get_var action action "throttle"
	json_get_var reset_day reset_day "1"
	json_cleanup

	if [ -z "$mac" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "MAC address is required"
		json_dump
		return 1
	fi

	# Check if quota exists for this MAC
	config_load bandwidth
	local quota_id=""

	_find_existing() {
		local this_mac
		config_get this_mac "$1" mac ""
		if [ "$this_mac" = "$mac" ]; then
			quota_id="$1"
		fi
	}

	config_foreach _find_existing quota

	if [ -z "$quota_id" ]; then
		# Create new quota
		quota_id="quota_$(date +%s)"
		uci -q batch << EOF
set bandwidth.$quota_id=quota
set bandwidth.$quota_id.mac='$mac'
set bandwidth.$quota_id.name='$name'
set bandwidth.$quota_id.limit_mb='$limit_mb'
set bandwidth.$quota_id.action='$action'
set bandwidth.$quota_id.reset_day='$reset_day'
set bandwidth.$quota_id.enabled='1'
commit bandwidth
EOF
		local msg="Quota created successfully"
	else
		# Update existing quota
		uci -q batch << EOF
set bandwidth.$quota_id.name='$name'
set bandwidth.$quota_id.limit_mb='$limit_mb'
set bandwidth.$quota_id.action='$action'
set bandwidth.$quota_id.reset_day='$reset_day'
commit bandwidth
EOF
		local msg="Quota updated successfully"
	fi

	json_init
	json_add_boolean "success" 1
	json_add_string "quota_id" "$quota_id"
	json_add_string "message" "$msg"
	json_dump
}

# Reset quota counter for a client
reset_quota() {
	read -r input
	json_load "$input"

	local mac
	json_get_var mac mac
	json_cleanup

	if [ -z "$mac" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "MAC address is required"
		json_dump
		return 1
	fi

	# Reset iptables counters for this MAC
	iptables -Z $IPTABLES_CHAIN 2>/dev/null

	# Remove from usage DB
	if [ -f "$USAGE_DB" ]; then
		sed -i "/^${mac}|/d" "$USAGE_DB"
	fi

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Quota counter reset for $mac"
	json_dump
}

# Get real-time usage for all clients
get_usage_realtime() {
	json_init
	json_add_array "clients"

	# Parse DHCP leases for active clients
	if [ -f /tmp/dhcp.leases ]; then
		while read -r expires mac ip hostname clientid; do
			local rx_bytes=0 tx_bytes=0

			# Get current bytes from iptables
			if iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -qi "$mac"; then
				rx_bytes=$(iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -i "$mac" | awk '{print $2}' | head -1)
				tx_bytes=$(iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -i "$mac" | awk '{print $2}' | tail -1)
			fi

			# Get quota info if exists
			config_load bandwidth
			local has_quota=0 limit_mb=0 used_mb=0

			_check_quota() {
				local this_mac
				config_get this_mac "$1" mac ""
				if [ "$this_mac" = "$mac" ]; then
					has_quota=1
					config_get limit_mb "$1" limit_mb "0"
					used_mb=$(get_mac_usage "$mac")
				fi
			}

			config_foreach _check_quota quota

			json_add_object ""
			json_add_string "mac" "$mac"
			json_add_string "ip" "$ip"
			json_add_string "hostname" "${hostname:-unknown}"
			json_add_int "rx_bytes" "${rx_bytes:-0}"
			json_add_int "tx_bytes" "${tx_bytes:-0}"
			json_add_boolean "has_quota" "$has_quota"
			if [ "$has_quota" -eq 1 ]; then
				json_add_int "limit_mb" "$limit_mb"
				json_add_int "used_mb" "$used_mb"
			fi
			json_close_object
		done < /tmp/dhcp.leases
	fi

	json_close_array
	json_dump
}

# Get usage history
get_usage_history() {
	read -r input
	json_load "$input"

	local timeframe mac
	json_get_var timeframe timeframe "24h"
	json_get_var mac mac ""
	json_cleanup

	init_usage_db

	json_init
	json_add_array "history"

	# Calculate time threshold
	local now=$(date +%s)
	local threshold=0
	case "$timeframe" in
		"1h") threshold=$(( now - 3600 )) ;;
		"6h") threshold=$(( now - 21600 )) ;;
		"24h") threshold=$(( now - 86400 )) ;;
		"7d") threshold=$(( now - 604800 )) ;;
		"30d") threshold=$(( now - 2592000 )) ;;
		*) threshold=$(( now - 86400 )) ;;
	esac

	# Read usage database
	if [ -f "$USAGE_DB" ]; then
		while IFS='|' read -r db_mac timestamp rx_bytes tx_bytes; do
			# Skip header line
			[ "$db_mac" = "# MAC" ] && continue

			# Filter by MAC if specified
			if [ -n "$mac" ] && [ "$db_mac" != "$mac" ]; then
				continue
			fi

			# Filter by timeframe
			if [ "$timestamp" -lt "$threshold" ]; then
				continue
			fi

			json_add_object ""
			json_add_string "mac" "$db_mac"
			json_add_int "timestamp" "$timestamp"
			json_add_int "rx_bytes" "$rx_bytes"
			json_add_int "tx_bytes" "$tx_bytes"
			json_close_object
		done < "$USAGE_DB"
	fi

	json_close_array
	json_dump
}

# Get media traffic types for classification
get_media() {
	json_init
	json_add_array "media"

	# VoIP
	json_add_object ""
	json_add_string "id" "voip"
	json_add_string "name" "VoIP"
	json_add_string "class" "1 (Highest)"
	json_add_string "description" "Voice over IP (SIP, RTP, VoLTE)"
	json_add_string "ports" "5060-5061,5004,16384-32767"
	json_close_object

	# Gaming
	json_add_object ""
	json_add_string "id" "gaming"
	json_add_string "name" "Gaming"
	json_add_string "class" "2 (High)"
	json_add_string "description" "Online gaming traffic"
	json_add_string "ports" "3074,27015-27030,3478-3479"
	json_close_object

	# Streaming
	json_add_object ""
	json_add_string "id" "streaming"
	json_add_string "name" "Video Streaming"
	json_add_string "class" "4 (Normal)"
	json_add_string "description" "YouTube, Netflix, Twitch"
	json_add_string "ports" "443,80"
	json_add_string "domains" "youtube.com,netflix.com,twitch.tv"
	json_close_object

	# Downloads
	json_add_object ""
	json_add_string "id" "download"
	json_add_string "name" "Downloads"
	json_add_string "class" "7 (Low)"
	json_add_string "description" "HTTP/HTTPS downloads, torrents"
	json_add_string "ports" "6881-6889,51413"
	json_close_object

	# Social Media
	json_add_object ""
	json_add_string "id" "social"
	json_add_string "name" "Social Media"
	json_add_string "class" "5 (Normal)"
	json_add_string "description" "Facebook, Twitter, Instagram"
	json_add_string "ports" "443,80"
	json_close_object

	# Work/Business
	json_add_object ""
	json_add_string "id" "work"
	json_add_string "name" "Work / Business"
	json_add_string "class" "3"
	json_add_string "description" "Email, VPN, remote desktop"
	json_add_string "ports" "22,3389,1194,1723"
	json_close_object

	json_close_array
	json_dump
}

# ============================================
# Smart QoS - DPI Integration with nDPId
# ============================================

NDPID_FLOWS="/tmp/ndpid-flows.json"
DPI_RULES_FILE="/tmp/bandwidth_dpi_rules.json"

# Get DPI detected applications from nDPId
get_dpi_applications() {
	json_init
	json_add_array "applications"

	if [ -f "$NDPID_FLOWS" ] && pgrep -x ndpid >/dev/null 2>&1; then
		# Extract unique applications from nDPId flows
		jq -r '.flows[]? | .app // "Unknown"' "$NDPID_FLOWS" 2>/dev/null | sort -u | while read app; do
			[ -z "$app" ] && continue
			local count=$(jq -r ".flows[] | select(.app == \"$app\") | .src_ip" "$NDPID_FLOWS" 2>/dev/null | wc -l)
			local bytes=$(jq -r ".flows[] | select(.app == \"$app\") | .bytes_rx + .bytes_tx" "$NDPID_FLOWS" 2>/dev/null | awk '{sum+=$1} END {print sum}')

			json_add_object ""
			json_add_string "name" "$app"
			json_add_int "flow_count" "$count"
			json_add_int "total_bytes" "${bytes:-0}"
			json_close_object
		done
	fi

	json_close_array

	# Check DPI source status
	local dpi_source="none"
	if [ -f "$NDPID_FLOWS" ] && pgrep -x ndpid >/dev/null 2>&1; then
		dpi_source="ndpid"
	elif [ -f "/var/run/netifyd/status.json" ] && pgrep -x netifyd >/dev/null 2>&1; then
		dpi_source="netifyd"
	fi
	json_add_string "dpi_source" "$dpi_source"

	json_dump
}

# Get smart QoS suggestions based on detected traffic
get_smart_suggestions() {
	json_init
	json_add_array "suggestions"

	if [ -f "$NDPID_FLOWS" ]; then
		# Check for gaming traffic
		local gaming_count=$(jq -r '.flows[]? | select(.category == "Game" or .app | test("Steam|Xbox|PlayStation|Nintendo|Epic|Riot"; "i")) | .src_ip' "$NDPID_FLOWS" 2>/dev/null | sort -u | wc -l)
		if [ "$gaming_count" -gt 0 ]; then
			json_add_object ""
			json_add_string "type" "gaming"
			json_add_string "title" "Gaming Traffic Detected"
			json_add_string "description" "Detected $gaming_count devices with gaming traffic. Recommend priority class 1-2 for low latency."
			json_add_int "priority" 2
			json_add_int "affected_devices" "$gaming_count"
			json_close_object
		fi

		# Check for streaming traffic
		local streaming_count=$(jq -r '.flows[]? | select(.app | test("YouTube|Netflix|Twitch|Disney|HBO|Amazon.*Video|Spotify"; "i")) | .src_ip' "$NDPID_FLOWS" 2>/dev/null | sort -u | wc -l)
		if [ "$streaming_count" -gt 0 ]; then
			json_add_object ""
			json_add_string "type" "streaming"
			json_add_string "title" "Streaming Traffic Detected"
			json_add_string "description" "Detected $streaming_count devices streaming video/audio. Recommend priority class 4 for consistent quality."
			json_add_int "priority" 4
			json_add_int "affected_devices" "$streaming_count"
			json_close_object
		fi

		# Check for video conferencing
		local video_conf_count=$(jq -r '.flows[]? | select(.app | test("Zoom|Teams|Meet|WebEx|Skype"; "i")) | .src_ip' "$NDPID_FLOWS" 2>/dev/null | sort -u | wc -l)
		if [ "$video_conf_count" -gt 0 ]; then
			json_add_object ""
			json_add_string "type" "videoconf"
			json_add_string "title" "Video Conferencing Detected"
			json_add_string "description" "Detected $video_conf_count devices in video calls. Recommend priority class 1 for real-time communication."
			json_add_int "priority" 1
			json_add_int "affected_devices" "$video_conf_count"
			json_close_object
		fi

		# Check for heavy downloaders
		local download_bytes=$(jq -r '.flows[]? | select(.bytes_rx > 100000000) | "\(.src_ip) \(.bytes_rx)"' "$NDPID_FLOWS" 2>/dev/null | sort -t' ' -k2 -nr | head -3)
		if [ -n "$download_bytes" ]; then
			local heavy_users=$(echo "$download_bytes" | wc -l)
			json_add_object ""
			json_add_string "type" "downloads"
			json_add_string "title" "Heavy Bandwidth Users"
			json_add_string "description" "Detected $heavy_users devices downloading heavily. Consider priority class 6-7 or quotas."
			json_add_int "priority" 7
			json_add_int "affected_devices" "$heavy_users"
			json_close_object
		fi
	fi

	json_close_array
	json_dump
}

# Apply smart QoS rule based on app detection
apply_dpi_rule() {
	read -r input
	json_load "$input"

	local app_name priority limit_down limit_up
	json_get_var app_name app_name
	json_get_var priority priority "5"
	json_get_var limit_down limit_down "0"
	json_get_var limit_up limit_up "0"
	json_cleanup

	if [ -z "$app_name" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Application name is required"
		json_dump
		return 1
	fi

	# Create DPI-based QoS rule
	local rule_id="dpi_rule_$(echo "$app_name" | tr ' ' '_' | tr '[:upper:]' '[:lower:]')_$(date +%s)"

	uci -q batch << EOF
set bandwidth.$rule_id=rule
set bandwidth.$rule_id.name='DPI: $app_name'
set bandwidth.$rule_id.type='dpi_app'
set bandwidth.$rule_id.target='$app_name'
set bandwidth.$rule_id.limit_down='$limit_down'
set bandwidth.$rule_id.limit_up='$limit_up'
set bandwidth.$rule_id.priority='$priority'
set bandwidth.$rule_id.enabled='1'
set bandwidth.$rule_id.dpi_match='1'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "rule_id" "$rule_id"
	json_add_string "message" "DPI rule created for $app_name"
	json_dump
}

# ============================================
# Device Groups Management
# ============================================

# List all device groups
list_groups() {
	config_load bandwidth
	json_init
	json_add_array "groups"

	_add_group() {
		local name description quota_mb priority members enabled
		config_get name "$1" name ""
		config_get description "$1" description ""
		config_get quota_mb "$1" quota_mb "0"
		config_get priority "$1" priority "5"
		config_get members "$1" members ""
		config_get enabled "$1" enabled "1"

		# Calculate group usage
		local total_used=0
		local member_count=0
		for mac in $members; do
			member_count=$(( member_count + 1 ))
			local used=$(get_mac_usage "$mac")
			total_used=$(( total_used + used ))
		done

		json_add_object ""
		json_add_string "id" "$1"
		json_add_string "name" "$name"
		json_add_string "description" "$description"
		json_add_int "quota_mb" "$quota_mb"
		json_add_int "priority" "$priority"
		json_add_int "member_count" "$member_count"
		json_add_int "used_mb" "$total_used"
		json_add_boolean "enabled" "$enabled"
		json_close_object
	}

	config_foreach _add_group group

	json_close_array
	json_dump
}

# Get group details with members
get_group() {
	read -r input
	json_load "$input"

	local group_id
	json_get_var group_id group_id
	json_cleanup

	if [ -z "$group_id" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Group ID is required"
		json_dump
		return 1
	fi

	config_load bandwidth

	local name description quota_mb priority members enabled
	config_get name "$group_id" name ""
	config_get description "$group_id" description ""
	config_get quota_mb "$group_id" quota_mb "0"
	config_get priority "$group_id" priority "5"
	config_get members "$group_id" members ""
	config_get enabled "$group_id" enabled "1"

	if [ -z "$name" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Group not found"
		json_dump
		return 1
	fi

	json_init
	json_add_boolean "success" 1
	json_add_string "id" "$group_id"
	json_add_string "name" "$name"
	json_add_string "description" "$description"
	json_add_int "quota_mb" "$quota_mb"
	json_add_int "priority" "$priority"
	json_add_boolean "enabled" "$enabled"

	# Add members with details
	json_add_array "members"
	for mac in $members; do
		local hostname ip used_mb
		hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}')
		ip=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $3}')
		used_mb=$(get_mac_usage "$mac")

		json_add_object ""
		json_add_string "mac" "$mac"
		json_add_string "hostname" "${hostname:-unknown}"
		json_add_string "ip" "${ip:-unknown}"
		json_add_int "used_mb" "$used_mb"
		json_close_object
	done
	json_close_array

	json_dump
}

# Create device group
create_group() {
	read -r input
	json_load "$input"

	local name description quota_mb priority members
	json_get_var name name
	json_get_var description description ""
	json_get_var quota_mb quota_mb "0"
	json_get_var priority priority "5"
	json_get_var members members ""
	json_cleanup

	if [ -z "$name" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Group name is required"
		json_dump
		return 1
	fi

	local group_id="group_$(date +%s)"

	uci -q batch << EOF
set bandwidth.$group_id=group
set bandwidth.$group_id.name='$name'
set bandwidth.$group_id.description='$description'
set bandwidth.$group_id.quota_mb='$quota_mb'
set bandwidth.$group_id.priority='$priority'
set bandwidth.$group_id.members='$members'
set bandwidth.$group_id.enabled='1'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "group_id" "$group_id"
	json_add_string "message" "Group created successfully"
	json_dump
}

# Update device group
update_group() {
	read -r input
	json_load "$input"

	local group_id name description quota_mb priority members
	json_get_var group_id group_id
	json_get_var name name
	json_get_var description description ""
	json_get_var quota_mb quota_mb "0"
	json_get_var priority priority "5"
	json_get_var members members ""
	json_cleanup

	if [ -z "$group_id" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Group ID is required"
		json_dump
		return 1
	fi

	# Check if group exists
	config_load bandwidth
	local existing_name
	config_get existing_name "$group_id" name ""
	if [ -z "$existing_name" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Group not found"
		json_dump
		return 1
	fi

	uci -q batch << EOF
set bandwidth.$group_id.name='$name'
set bandwidth.$group_id.description='$description'
set bandwidth.$group_id.quota_mb='$quota_mb'
set bandwidth.$group_id.priority='$priority'
set bandwidth.$group_id.members='$members'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Group updated successfully"
	json_dump
}

# Delete device group
delete_group() {
	read -r input
	json_load "$input"

	local group_id
	json_get_var group_id group_id
	json_cleanup

	if [ -z "$group_id" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Group ID is required"
		json_dump
		return 1
	fi

	# Check if group exists
	if ! uci -q get bandwidth.$group_id >/dev/null 2>&1; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Group not found"
		json_dump
		return 1
	fi

	uci -q delete bandwidth.$group_id
	uci -q commit bandwidth

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Group deleted successfully"
	json_dump
}

# Add device to group
add_to_group() {
	read -r input
	json_load "$input"

	local group_id mac
	json_get_var group_id group_id
	json_get_var mac mac
	json_cleanup

	if [ -z "$group_id" ] || [ -z "$mac" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Group ID and MAC address are required"
		json_dump
		return 1
	fi

	config_load bandwidth
	local members
	config_get members "$group_id" members ""

	# Check if already in group
	if echo "$members" | grep -qi "$mac"; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Device already in group"
		json_dump
		return 1
	fi

	members="$members $mac"
	uci -q set bandwidth.$group_id.members="$members"
	uci -q commit bandwidth

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Device added to group"
	json_dump
}

# Remove device from group
remove_from_group() {
	read -r input
	json_load "$input"

	local group_id mac
	json_get_var group_id group_id
	json_get_var mac mac
	json_cleanup

	if [ -z "$group_id" ] || [ -z "$mac" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Group ID and MAC address are required"
		json_dump
		return 1
	fi

	config_load bandwidth
	local members
	config_get members "$group_id" members ""

	# Remove MAC from members list
	members=$(echo "$members" | sed "s/$mac//gi" | tr -s ' ')
	uci -q set bandwidth.$group_id.members="$members"
	uci -q commit bandwidth

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Device removed from group"
	json_dump
}

# ============================================
# Analytics
# ============================================

ANALYTICS_DB="/tmp/bandwidth_analytics.db"

# Initialize analytics database
init_analytics_db() {
	if [ ! -f "$ANALYTICS_DB" ]; then
		cat > "$ANALYTICS_DB" << 'EOF'
# Hour|Date|Total_RX|Total_TX|Peak_RX|Peak_TX|Client_Count
EOF
	fi
}

# Get analytics summary
get_analytics_summary() {
	read -r input
	json_load "$input"

	local period
	json_get_var period period "24h"
	json_cleanup

	json_init

	# Current totals
	local interface
	config_load bandwidth
	config_get interface global interface "br-lan"

	if [ -d "/sys/class/net/$interface" ]; then
		local rx_bytes=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0)
		local tx_bytes=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0)
		json_add_int "total_rx_bytes" "$rx_bytes"
		json_add_int "total_tx_bytes" "$tx_bytes"
	fi

	# Client count
	local client_count=$(cat /tmp/dhcp.leases 2>/dev/null | wc -l)
	json_add_int "active_clients" "$client_count"

	# Top talkers
	json_add_array "top_talkers"
	if [ -f /tmp/dhcp.leases ]; then
		while read -r expires mac ip hostname clientid; do
			local used_mb=$(get_mac_usage "$mac")
			[ "$used_mb" -gt 0 ] && echo "$used_mb|$mac|$ip|${hostname:-unknown}"
		done < /tmp/dhcp.leases | sort -t'|' -k1 -nr | head -5 | while IFS='|' read used mac ip hostname; do
			json_add_object ""
			json_add_string "mac" "$mac"
			json_add_string "ip" "$ip"
			json_add_string "hostname" "$hostname"
			json_add_int "used_mb" "$used"
			json_close_object
		done
	fi
	json_close_array

	# Application breakdown (from nDPId)
	json_add_array "app_breakdown"
	if [ -f "$NDPID_FLOWS" ]; then
		jq -r '[.flows[]? | {app: (.app // "Unknown"), bytes: (.bytes_rx + .bytes_tx)}] | group_by(.app) | map({app: .[0].app, total_bytes: (map(.bytes) | add)}) | sort_by(.total_bytes) | reverse | .[:10][] | "\(.app)|\(.total_bytes)"' "$NDPID_FLOWS" 2>/dev/null | while IFS='|' read app bytes; do
			json_add_object ""
			json_add_string "app" "$app"
			json_add_int "bytes" "${bytes:-0}"
			json_close_object
		done
	fi
	json_close_array

	# Protocol breakdown (from nDPId)
	json_add_array "protocol_breakdown"
	if [ -f "$NDPID_FLOWS" ]; then
		jq -r '[.flows[]? | {protocol: (.l4_proto // "Unknown"), bytes: (.bytes_rx + .bytes_tx)}] | group_by(.protocol) | map({protocol: .[0].protocol, total_bytes: (map(.bytes) | add)}) | sort_by(.total_bytes) | reverse | .[:5][] | "\(.protocol)|\(.total_bytes)"' "$NDPID_FLOWS" 2>/dev/null | while IFS='|' read proto bytes; do
			json_add_object ""
			json_add_string "protocol" "$proto"
			json_add_int "bytes" "${bytes:-0}"
			json_close_object
		done
	fi
	json_close_array

	json_dump
}

# Get hourly usage data for charts
get_hourly_data() {
	read -r input
	json_load "$input"

	local days
	json_get_var days days "7"
	json_cleanup

	init_analytics_db

	json_init
	json_add_array "hourly_data"

	# Read from analytics DB
	local threshold_date=$(date -d "$days days ago" +%Y%m%d 2>/dev/null || date +%Y%m%d)
	if [ -f "$ANALYTICS_DB" ]; then
		while IFS='|' read hour date rx tx peak_rx peak_tx clients; do
			[ "$hour" = "# Hour" ] && continue
			[ "$date" -lt "$threshold_date" ] 2>/dev/null && continue

			json_add_object ""
			json_add_int "hour" "$hour"
			json_add_string "date" "$date"
			json_add_int "rx_bytes" "$rx"
			json_add_int "tx_bytes" "$tx"
			json_add_int "peak_rx" "$peak_rx"
			json_add_int "peak_tx" "$peak_tx"
			json_add_int "clients" "$clients"
			json_close_object
		done < "$ANALYTICS_DB"
	fi

	json_close_array
	json_dump
}

# Record current stats (called by cron)
record_stats() {
	init_analytics_db

	local interface
	config_load bandwidth
	config_get interface global interface "br-lan"

	local hour=$(date +%H)
	local date=$(date +%Y%m%d)
	local rx_bytes=0 tx_bytes=0

	if [ -d "/sys/class/net/$interface" ]; then
		rx_bytes=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0)
		tx_bytes=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0)
	fi

	local client_count=$(cat /tmp/dhcp.leases 2>/dev/null | wc -l)

	echo "$hour|$date|$rx_bytes|$tx_bytes|$rx_bytes|$tx_bytes|$client_count" >> "$ANALYTICS_DB"

	# Keep only last 30 days
	local old_date=$(date -d "30 days ago" +%Y%m%d 2>/dev/null || echo "0")
	if [ -f "$ANALYTICS_DB" ]; then
		awk -F'|' -v old="$old_date" '$2 >= old || /^#/' "$ANALYTICS_DB" > "${ANALYTICS_DB}.tmp"
		mv "${ANALYTICS_DB}.tmp" "$ANALYTICS_DB"
	fi

	json_init
	json_add_boolean "success" 1
	json_dump
}

# Get QoS priority classes
get_classes() {
	json_init
	json_add_array "classes"

	# Class 1 - Highest Priority
	json_add_object ""
	json_add_int "priority" 1
	json_add_string "name" "Real-time (VoIP, Gaming)"
	json_add_string "description" "Latency-sensitive applications requiring immediate delivery"
	json_add_int "rate" 30
	json_add_int "ceil" 100
	json_close_object

	# Class 2 - High Priority
	json_add_object ""
	json_add_int "priority" 2
	json_add_string "name" "Interactive (SSH, RDP)"
	json_add_string "description" "Interactive sessions and remote administration"
	json_add_int "rate" 20
	json_add_int "ceil" 90
	json_close_object

	# Class 3 - Medium-High Priority
	json_add_object ""
	json_add_int "priority" 3
	json_add_string "name" "Business Critical"
	json_add_string "description" "Email, VPN, business applications"
	json_add_int "rate" 15
	json_add_int "ceil" 80
	json_close_object

	# Class 4 - Normal Priority
	json_add_object ""
	json_add_int "priority" 4
	json_add_string "name" "Video Streaming"
	json_add_string "description" "YouTube, Netflix, video conferencing"
	json_add_int "rate" 10
	json_add_int "ceil" 70
	json_close_object

	# Class 5 - Normal Priority
	json_add_object ""
	json_add_int "priority" 5
	json_add_string "name" "Web Browsing"
	json_add_string "description" "HTTP/HTTPS web traffic, social media"
	json_add_int "rate" 10
	json_add_int "ceil" 60
	json_close_object

	# Class 6 - Low Priority
	json_add_object ""
	json_add_int "priority" 6
	json_add_string "name" "Bulk Transfer"
	json_add_string "description" "File transfers, cloud sync"
	json_add_int "rate" 5
	json_add_int "ceil" 50
	json_close_object

	# Class 7 - Lower Priority
	json_add_object ""
	json_add_int "priority" 7
	json_add_string "name" "Downloads (P2P)"
	json_add_string "description" "Torrents, large downloads"
	json_add_int "rate" 5
	json_add_int "ceil" 40
	json_close_object

	# Class 8 - Lowest Priority
	json_add_object ""
	json_add_int "priority" 8
	json_add_string "name" "Background / Scavenger"
	json_add_string "description" "System updates, backups, lowest priority traffic"
	json_add_int "rate" 5
	json_add_int "ceil" 30
	json_close_object

	json_close_array
	json_dump
}

# Main dispatcher
case "$1" in
	list)
		cat << 'EOF'
{
	"status": {},
	"list_rules": {},
	"add_rule": { "name": "string", "type": "string", "target": "string", "limit_down": 0, "limit_up": 0, "priority": 5 },
	"delete_rule": { "rule_id": "string" },
	"list_quotas": {},
	"get_quota": { "mac": "string" },
	"set_quota": { "mac": "string", "name": "string", "limit_mb": 0, "action": "string", "reset_day": 1 },
	"reset_quota": { "mac": "string" },
	"get_usage_realtime": {},
	"get_usage_history": { "timeframe": "24h", "mac": "" },
	"get_media": {},
	"get_classes": {},
	"get_dpi_applications": {},
	"get_smart_suggestions": {},
	"apply_dpi_rule": { "app_name": "string", "priority": 5, "limit_down": 0, "limit_up": 0 },
	"list_groups": {},
	"get_group": { "group_id": "string" },
	"create_group": { "name": "string", "description": "", "quota_mb": 0, "priority": 5, "members": "" },
	"update_group": { "group_id": "string", "name": "string", "description": "", "quota_mb": 0, "priority": 5, "members": "" },
	"delete_group": { "group_id": "string" },
	"add_to_group": { "group_id": "string", "mac": "string" },
	"remove_from_group": { "group_id": "string", "mac": "string" },
	"get_analytics_summary": { "period": "24h" },
	"get_hourly_data": { "days": 7 },
	"record_stats": {}
}
EOF
		;;
	call)
		case "$2" in
			status) status ;;
			list_rules) list_rules ;;
			add_rule) add_rule ;;
			delete_rule) delete_rule ;;
			list_quotas) list_quotas ;;
			get_quota) get_quota ;;
			set_quota) set_quota ;;
			reset_quota) reset_quota ;;
			get_usage_realtime) get_usage_realtime ;;
			get_usage_history) get_usage_history ;;
			get_media) get_media ;;
			get_classes) get_classes ;;
			get_dpi_applications) get_dpi_applications ;;
			get_smart_suggestions) get_smart_suggestions ;;
			apply_dpi_rule) apply_dpi_rule ;;
			list_groups) list_groups ;;
			get_group) get_group ;;
			create_group) create_group ;;
			update_group) update_group ;;
			delete_group) delete_group ;;
			add_to_group) add_to_group ;;
			remove_from_group) remove_from_group ;;
			get_analytics_summary) get_analytics_summary ;;
			get_hourly_data) get_hourly_data ;;
			record_stats) record_stats ;;
			*)
				json_init
				json_add_boolean "success" 0
				json_add_string "error" "Unknown method: $2"
				json_dump
				;;
		esac
		;;
esac
