#!/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 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 ndpid >/dev/null 2>&1; then
		dpi_source="ndpid"
	elif [ -f "/var/run/netifyd/status.json" ] && pgrep 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
}

# ============================================
# Device Profiles
# ============================================

PROFILES_CACHE="/tmp/bandwidth_profiles.cache"

# Get built-in profile definitions
get_builtin_profiles() {
	json_init
	json_add_array "profiles"

	# Gaming profile
	json_add_object ""
	json_add_string "id" "builtin_gaming"
	json_add_string "name" "Gaming"
	json_add_string "description" "Low latency, high priority for gaming"
	json_add_string "icon" "gamepad"
	json_add_string "color" "#8b5cf6"
	json_add_int "priority" 1
	json_add_int "limit_down" 0
	json_add_int "limit_up" 0
	json_add_string "latency_mode" "ultra"
	json_add_boolean "builtin" 1
	json_close_object

	# Streaming profile
	json_add_object ""
	json_add_string "id" "builtin_streaming"
	json_add_string "name" "Streaming"
	json_add_string "description" "Optimized for video streaming"
	json_add_string "icon" "play"
	json_add_string "color" "#06b6d4"
	json_add_int "priority" 4
	json_add_int "limit_down" 25000
	json_add_int "limit_up" 5000
	json_add_string "latency_mode" "normal"
	json_add_boolean "builtin" 1
	json_close_object

	# IoT profile
	json_add_object ""
	json_add_string "id" "builtin_iot"
	json_add_string "name" "IoT Devices"
	json_add_string "description" "Low bandwidth, network isolated"
	json_add_string "icon" "cpu"
	json_add_string "color" "#10b981"
	json_add_int "priority" 7
	json_add_int "limit_down" 1000
	json_add_int "limit_up" 500
	json_add_string "latency_mode" "normal"
	json_add_boolean "isolate" 1
	json_add_boolean "builtin" 1
	json_close_object

	# Work profile
	json_add_object ""
	json_add_string "id" "builtin_work"
	json_add_string "name" "Work"
	json_add_string "description" "Business-critical apps prioritized"
	json_add_string "icon" "briefcase"
	json_add_string "color" "#3b82f6"
	json_add_int "priority" 3
	json_add_int "limit_down" 0
	json_add_int "limit_up" 0
	json_add_string "latency_mode" "normal"
	json_add_boolean "builtin" 1
	json_close_object

	# Kids profile
	json_add_object ""
	json_add_string "id" "builtin_kids"
	json_add_string "name" "Kids"
	json_add_string "description" "Parental controls, content filtered"
	json_add_string "icon" "child"
	json_add_string "color" "#f59e0b"
	json_add_int "priority" 5
	json_add_int "limit_down" 10000
	json_add_int "limit_up" 2000
	json_add_string "latency_mode" "normal"
	json_add_string "content_filter" "kids_safe"
	json_add_boolean "builtin" 1
	json_close_object

	json_close_array
	json_dump
}

# List all profiles (custom + builtin)
list_profiles() {
	config_load bandwidth
	json_init
	json_add_array "profiles"

	_add_profile() {
		local name description icon color priority limit_down limit_up latency_mode content_filter isolate schedule enabled
		config_get name "$1" name ""
		config_get description "$1" description ""
		config_get icon "$1" icon "tag"
		config_get color "$1" color "#6366f1"
		config_get priority "$1" priority "5"
		config_get limit_down "$1" limit_down "0"
		config_get limit_up "$1" limit_up "0"
		config_get latency_mode "$1" latency_mode "normal"
		config_get content_filter "$1" content_filter ""
		config_get isolate "$1" isolate "0"
		config_get schedule "$1" schedule ""
		config_get enabled "$1" enabled "1"

		# Count assigned devices
		local assigned_count=0
		_count_assigned() {
			local profile
			config_get profile "$1" profile ""
			[ "$profile" = "$2" ] && assigned_count=$(( assigned_count + 1 ))
		}
		config_foreach _count_assigned device_profile "$1"

		json_add_object ""
		json_add_string "id" "$1"
		json_add_string "name" "$name"
		json_add_string "description" "$description"
		json_add_string "icon" "$icon"
		json_add_string "color" "$color"
		json_add_int "priority" "$priority"
		json_add_int "limit_down" "$limit_down"
		json_add_int "limit_up" "$limit_up"
		json_add_string "latency_mode" "$latency_mode"
		json_add_string "content_filter" "$content_filter"
		json_add_boolean "isolate" "$isolate"
		json_add_string "schedule" "$schedule"
		json_add_boolean "enabled" "$enabled"
		json_add_int "assigned_count" "$assigned_count"
		json_add_boolean "builtin" 0
		json_close_object
	}

	config_foreach _add_profile profile

	json_close_array
	json_dump
}

# Get single profile details
get_profile() {
	read -r input
	json_load "$input"

	local profile_id
	json_get_var profile_id profile_id
	json_cleanup

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

	config_load bandwidth

	local name description icon color priority limit_down limit_up latency_mode content_filter isolate schedule enabled
	config_get name "$profile_id" name ""
	config_get description "$profile_id" description ""
	config_get icon "$profile_id" icon "tag"
	config_get color "$profile_id" color "#6366f1"
	config_get priority "$profile_id" priority "5"
	config_get limit_down "$profile_id" limit_down "0"
	config_get limit_up "$profile_id" limit_up "0"
	config_get latency_mode "$profile_id" latency_mode "normal"
	config_get content_filter "$profile_id" content_filter ""
	config_get isolate "$profile_id" isolate "0"
	config_get schedule "$profile_id" schedule ""
	config_get enabled "$profile_id" enabled "1"

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

	json_init
	json_add_boolean "success" 1
	json_add_string "id" "$profile_id"
	json_add_string "name" "$name"
	json_add_string "description" "$description"
	json_add_string "icon" "$icon"
	json_add_string "color" "$color"
	json_add_int "priority" "$priority"
	json_add_int "limit_down" "$limit_down"
	json_add_int "limit_up" "$limit_up"
	json_add_string "latency_mode" "$latency_mode"
	json_add_string "content_filter" "$content_filter"
	json_add_boolean "isolate" "$isolate"
	json_add_string "schedule" "$schedule"
	json_add_boolean "enabled" "$enabled"

	# Get assigned devices
	json_add_array "assigned_devices"
	_add_assigned() {
		local mac profile custom_limit_down custom_limit_up
		config_get mac "$1" mac ""
		config_get profile "$1" profile ""
		config_get custom_limit_down "$1" custom_limit_down "0"
		config_get custom_limit_up "$1" custom_limit_up "0"

		if [ "$profile" = "$profile_id" ]; then
			local hostname ip
			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}')

			json_add_object ""
			json_add_string "mac" "$mac"
			json_add_string "hostname" "${hostname:-unknown}"
			json_add_string "ip" "${ip:-}"
			json_add_int "custom_limit_down" "$custom_limit_down"
			json_add_int "custom_limit_up" "$custom_limit_up"
			json_close_object
		fi
	}
	config_foreach _add_assigned device_profile
	json_close_array

	json_dump
}

# Create new profile
create_profile() {
	read -r input
	json_load "$input"

	local name description icon color priority limit_down limit_up latency_mode content_filter isolate schedule
	json_get_var name name
	json_get_var description description ""
	json_get_var icon icon "tag"
	json_get_var color color "#6366f1"
	json_get_var priority priority "5"
	json_get_var limit_down limit_down "0"
	json_get_var limit_up limit_up "0"
	json_get_var latency_mode latency_mode "normal"
	json_get_var content_filter content_filter ""
	json_get_var isolate isolate "0"
	json_get_var schedule schedule ""
	json_cleanup

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

	local profile_id="profile_$(date +%s)"

	uci -q batch << EOF
set bandwidth.$profile_id=profile
set bandwidth.$profile_id.name='$name'
set bandwidth.$profile_id.description='$description'
set bandwidth.$profile_id.icon='$icon'
set bandwidth.$profile_id.color='$color'
set bandwidth.$profile_id.priority='$priority'
set bandwidth.$profile_id.limit_down='$limit_down'
set bandwidth.$profile_id.limit_up='$limit_up'
set bandwidth.$profile_id.latency_mode='$latency_mode'
set bandwidth.$profile_id.content_filter='$content_filter'
set bandwidth.$profile_id.isolate='$isolate'
set bandwidth.$profile_id.schedule='$schedule'
set bandwidth.$profile_id.enabled='1'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "profile_id" "$profile_id"
	json_add_string "message" "Profile created successfully"
	json_dump
}

# Update profile
update_profile() {
	read -r input
	json_load "$input"

	local profile_id name description icon color priority limit_down limit_up latency_mode content_filter isolate schedule enabled
	json_get_var profile_id profile_id
	json_get_var name name
	json_get_var description description ""
	json_get_var icon icon "tag"
	json_get_var color color "#6366f1"
	json_get_var priority priority "5"
	json_get_var limit_down limit_down "0"
	json_get_var limit_up limit_up "0"
	json_get_var latency_mode latency_mode "normal"
	json_get_var content_filter content_filter ""
	json_get_var isolate isolate "0"
	json_get_var schedule schedule ""
	json_get_var enabled enabled "1"
	json_cleanup

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

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

	uci -q batch << EOF
set bandwidth.$profile_id.name='$name'
set bandwidth.$profile_id.description='$description'
set bandwidth.$profile_id.icon='$icon'
set bandwidth.$profile_id.color='$color'
set bandwidth.$profile_id.priority='$priority'
set bandwidth.$profile_id.limit_down='$limit_down'
set bandwidth.$profile_id.limit_up='$limit_up'
set bandwidth.$profile_id.latency_mode='$latency_mode'
set bandwidth.$profile_id.content_filter='$content_filter'
set bandwidth.$profile_id.isolate='$isolate'
set bandwidth.$profile_id.schedule='$schedule'
set bandwidth.$profile_id.enabled='$enabled'
commit bandwidth
EOF

	# Apply QoS rules for assigned devices
	apply_profile_qos "$profile_id"

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

# Delete profile
delete_profile() {
	read -r input
	json_load "$input"

	local profile_id
	json_get_var profile_id profile_id
	json_cleanup

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

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

	# Remove profile assignments first
	config_load bandwidth
	_remove_assignments() {
		local profile
		config_get profile "$1" profile ""
		[ "$profile" = "$profile_id" ] && uci -q delete bandwidth.$1
	}
	config_foreach _remove_assignments device_profile

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

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

# Clone an existing profile
clone_profile() {
	read -r input
	json_load "$input"

	local source_id new_name
	json_get_var source_id source_id
	json_get_var new_name new_name
	json_cleanup

	if [ -z "$source_id" ] || [ -z "$new_name" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Source profile ID and new name are required"
		json_dump
		return 1
	fi

	config_load bandwidth

	local description icon color priority limit_down limit_up latency_mode content_filter isolate schedule
	config_get description "$source_id" description ""
	config_get icon "$source_id" icon "tag"
	config_get color "$source_id" color "#6366f1"
	config_get priority "$source_id" priority "5"
	config_get limit_down "$source_id" limit_down "0"
	config_get limit_up "$source_id" limit_up "0"
	config_get latency_mode "$source_id" latency_mode "normal"
	config_get content_filter "$source_id" content_filter ""
	config_get isolate "$source_id" isolate "0"
	config_get schedule "$source_id" schedule ""

	local profile_id="profile_$(date +%s)"

	uci -q batch << EOF
set bandwidth.$profile_id=profile
set bandwidth.$profile_id.name='$new_name'
set bandwidth.$profile_id.description='$description (copy)'
set bandwidth.$profile_id.icon='$icon'
set bandwidth.$profile_id.color='$color'
set bandwidth.$profile_id.priority='$priority'
set bandwidth.$profile_id.limit_down='$limit_down'
set bandwidth.$profile_id.limit_up='$limit_up'
set bandwidth.$profile_id.latency_mode='$latency_mode'
set bandwidth.$profile_id.content_filter='$content_filter'
set bandwidth.$profile_id.isolate='$isolate'
set bandwidth.$profile_id.schedule='$schedule'
set bandwidth.$profile_id.enabled='1'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "profile_id" "$profile_id"
	json_add_string "message" "Profile cloned successfully"
	json_dump
}

# Assign profile to device
assign_profile_to_device() {
	read -r input
	json_load "$input"

	local mac profile_id custom_limit_down custom_limit_up
	json_get_var mac mac
	json_get_var profile_id profile_id
	json_get_var custom_limit_down custom_limit_down "0"
	json_get_var custom_limit_up custom_limit_up "0"
	json_cleanup

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

	# Check if device already has an assignment
	config_load bandwidth
	local existing_assignment=""
	_find_assignment() {
		local this_mac
		config_get this_mac "$1" mac ""
		[ "$this_mac" = "$mac" ] && existing_assignment="$1"
	}
	config_foreach _find_assignment device_profile

	if [ -n "$existing_assignment" ]; then
		# Update existing
		uci -q batch << EOF
set bandwidth.$existing_assignment.profile='$profile_id'
set bandwidth.$existing_assignment.custom_limit_down='$custom_limit_down'
set bandwidth.$existing_assignment.custom_limit_up='$custom_limit_up'
commit bandwidth
EOF
	else
		# Create new
		local dp_id="dp_$(date +%s)_$(echo "$mac" | tr ':' '' | tail -c 7)"
		uci -q batch << EOF
set bandwidth.$dp_id=device_profile
set bandwidth.$dp_id.mac='$mac'
set bandwidth.$dp_id.profile='$profile_id'
set bandwidth.$dp_id.custom_limit_down='$custom_limit_down'
set bandwidth.$dp_id.custom_limit_up='$custom_limit_up'
commit bandwidth
EOF
	fi

	# Apply QoS
	apply_device_qos "$mac" "$profile_id" "$custom_limit_down" "$custom_limit_up"

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Profile assigned to device"
	json_dump
}

# Assign profile to group
assign_profile_to_group() {
	read -r input
	json_load "$input"

	local group_id profile_id
	json_get_var group_id group_id
	json_get_var profile_id profile_id
	json_cleanup

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

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

	local assigned=0
	for mac in $members; do
		local dp_id="dp_$(date +%s)_$(echo "$mac" | tr ':' '' | tail -c 7)"

		# Check existing
		local existing=""
		_find_dev() {
			local this_mac
			config_get this_mac "$1" mac ""
			[ "$this_mac" = "$mac" ] && existing="$1"
		}
		config_foreach _find_dev device_profile

		if [ -n "$existing" ]; then
			uci -q set bandwidth.$existing.profile="$profile_id"
		else
			uci -q batch << EOF
set bandwidth.$dp_id=device_profile
set bandwidth.$dp_id.mac='$mac'
set bandwidth.$dp_id.profile='$profile_id'
set bandwidth.$dp_id.custom_limit_down='0'
set bandwidth.$dp_id.custom_limit_up='0'
EOF
		fi
		assigned=$(( assigned + 1 ))
	done

	uci -q commit bandwidth

	json_init
	json_add_boolean "success" 1
	json_add_int "assigned_count" "$assigned"
	json_add_string "message" "Profile assigned to $assigned devices in group"
	json_dump
}

# Remove profile assignment
remove_profile_assignment() {
	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=""
	_find_dp() {
		local this_mac
		config_get this_mac "$1" mac ""
		[ "$this_mac" = "$mac" ] && found="$1"
	}
	config_foreach _find_dp device_profile

	if [ -n "$found" ]; then
		uci -q delete bandwidth.$found
		uci -q commit bandwidth
		# Remove QoS rules for this device
		remove_device_qos "$mac"
	fi

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Profile assignment removed"
	json_dump
}

# List profile assignments
list_profile_assignments() {
	config_load bandwidth
	json_init
	json_add_array "assignments"

	_add_assignment() {
		local mac profile custom_limit_down custom_limit_up
		config_get mac "$1" mac ""
		config_get profile "$1" profile ""
		config_get custom_limit_down "$1" custom_limit_down "0"
		config_get custom_limit_up "$1" custom_limit_up "0"

		local hostname ip
		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}')

		# Get profile name
		local profile_name
		config_get profile_name "$profile" name ""

		json_add_object ""
		json_add_string "id" "$1"
		json_add_string "mac" "$mac"
		json_add_string "hostname" "${hostname:-unknown}"
		json_add_string "ip" "${ip:-}"
		json_add_string "profile" "$profile"
		json_add_string "profile_name" "$profile_name"
		json_add_int "custom_limit_down" "$custom_limit_down"
		json_add_int "custom_limit_up" "$custom_limit_up"
		json_close_object
	}

	config_foreach _add_assignment device_profile

	json_close_array
	json_dump
}

# Apply QoS for profile (internal)
apply_profile_qos() {
	local profile_id="$1"
	config_load bandwidth

	local priority limit_down limit_up
	config_get priority "$profile_id" priority "5"
	config_get limit_down "$profile_id" limit_down "0"
	config_get limit_up "$profile_id" limit_up "0"

	# Apply to all assigned devices
	_apply_device() {
		local mac profile custom_down custom_up
		config_get mac "$1" mac ""
		config_get profile "$1" profile ""
		config_get custom_down "$1" custom_limit_down "0"
		config_get custom_up "$1" custom_limit_up "0"

		if [ "$profile" = "$profile_id" ]; then
			local eff_down=$(( custom_down > 0 ? custom_down : limit_down ))
			local eff_up=$(( custom_up > 0 ? custom_up : limit_up ))
			apply_device_qos "$mac" "$profile_id" "$eff_down" "$eff_up"
		fi
	}
	config_foreach _apply_device device_profile
}

# Apply QoS for single device (internal)
apply_device_qos() {
	local mac="$1"
	local profile_id="$2"
	local limit_down="$3"
	local limit_up="$4"

	# Get profile priority
	config_load bandwidth
	local priority
	config_get priority "$profile_id" priority "5"

	local interface
	config_get interface global interface "br-lan"

	# Apply tc rules if limits are set
	if [ "$limit_down" -gt 0 ] || [ "$limit_up" -gt 0 ]; then
		# Mark packets from this MAC for QoS class
		iptables -t mangle -D PREROUTING -m mac --mac-source "$mac" -j MARK --set-mark "$priority" 2>/dev/null
		iptables -t mangle -A PREROUTING -m mac --mac-source "$mac" -j MARK --set-mark "$priority"

		# Could add tc filter here for rate limiting
		# tc filter add dev $interface parent 1: protocol ip prio $priority handle $priority fw flowid 1:$priority
	fi
}

# Remove QoS for device (internal)
remove_device_qos() {
	local mac="$1"
	# Remove iptables rules
	for i in 1 2 3 4 5 6 7 8; do
		iptables -t mangle -D PREROUTING -m mac --mac-source "$mac" -j MARK --set-mark "$i" 2>/dev/null
	done
}

# ============================================
# Parental Controls
# ============================================

# List parental schedules
list_parental_schedules() {
	config_load bandwidth
	json_init
	json_add_array "schedules"

	_add_parental_schedule() {
		local name enabled target_type target action start_time end_time days
		config_get name "$1" name ""
		config_get enabled "$1" enabled "1"
		config_get target_type "$1" target_type "device"
		config_get target "$1" target ""
		config_get action "$1" action "block"
		config_get start_time "$1" start_time "21:00"
		config_get end_time "$1" end_time "07:00"
		config_get days "$1" days ""

		# Check if currently active
		local active=0
		local current_day=$(date +%a | tr '[:upper:]' '[:lower:]')
		local current_time=$(date +%H:%M)
		if [ "$enabled" = "1" ] && echo "$days" | grep -qi "$current_day"; then
			if [ "$start_time" \< "$end_time" ]; then
				[ "$current_time" \> "$start_time" ] && [ "$current_time" \< "$end_time" ] && active=1
			else
				# Overnight schedule
				[ "$current_time" \> "$start_time" ] || [ "$current_time" \< "$end_time" ] && active=1
			fi
		fi

		json_add_object ""
		json_add_string "id" "$1"
		json_add_string "name" "$name"
		json_add_boolean "enabled" "$enabled"
		json_add_string "target_type" "$target_type"
		json_add_string "target" "$target"
		json_add_string "action" "$action"
		json_add_string "start_time" "$start_time"
		json_add_string "end_time" "$end_time"
		json_add_string "days" "$days"
		json_add_boolean "active" "$active"
		json_close_object
	}

	config_foreach _add_parental_schedule parental_schedule

	json_close_array
	json_dump
}

# Create parental schedule
create_parental_schedule() {
	read -r input
	json_load "$input"

	local name target_type target action start_time end_time days
	json_get_var name name
	json_get_var target_type target_type "device"
	json_get_var target target
	json_get_var action action "block"
	json_get_var start_time start_time "21:00"
	json_get_var end_time end_time "07:00"
	json_get_var days days "mon tue wed thu fri"
	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

	local schedule_id="ps_$(date +%s)"

	uci -q batch << EOF
set bandwidth.$schedule_id=parental_schedule
set bandwidth.$schedule_id.name='$name'
set bandwidth.$schedule_id.enabled='1'
set bandwidth.$schedule_id.target_type='$target_type'
set bandwidth.$schedule_id.target='$target'
set bandwidth.$schedule_id.action='$action'
set bandwidth.$schedule_id.start_time='$start_time'
set bandwidth.$schedule_id.end_time='$end_time'
set bandwidth.$schedule_id.days='$days'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "schedule_id" "$schedule_id"
	json_add_string "message" "Parental schedule created"
	json_dump
}

# Update parental schedule
update_parental_schedule() {
	read -r input
	json_load "$input"

	local schedule_id name target_type target action start_time end_time days enabled
	json_get_var schedule_id schedule_id
	json_get_var name name
	json_get_var target_type target_type "device"
	json_get_var target target
	json_get_var action action "block"
	json_get_var start_time start_time "21:00"
	json_get_var end_time end_time "07:00"
	json_get_var days days "mon tue wed thu fri"
	json_get_var enabled enabled "1"
	json_cleanup

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

	uci -q batch << EOF
set bandwidth.$schedule_id.name='$name'
set bandwidth.$schedule_id.enabled='$enabled'
set bandwidth.$schedule_id.target_type='$target_type'
set bandwidth.$schedule_id.target='$target'
set bandwidth.$schedule_id.action='$action'
set bandwidth.$schedule_id.start_time='$start_time'
set bandwidth.$schedule_id.end_time='$end_time'
set bandwidth.$schedule_id.days='$days'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Parental schedule updated"
	json_dump
}

# Delete parental schedule
delete_parental_schedule() {
	read -r input
	json_load "$input"

	local schedule_id
	json_get_var schedule_id schedule_id
	json_cleanup

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

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

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Parental schedule deleted"
	json_dump
}

# Toggle parental schedule
toggle_parental_schedule() {
	read -r input
	json_load "$input"

	local schedule_id enabled
	json_get_var schedule_id schedule_id
	json_get_var enabled enabled
	json_cleanup

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

	uci -q set bandwidth.$schedule_id.enabled="$enabled"
	uci -q commit bandwidth

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Schedule toggled"
	json_dump
}

# List preset modes
list_preset_modes() {
	config_load bandwidth
	json_init
	json_add_array "presets"

	_add_preset() {
		local name icon enabled action allowed_categories blocked_categories
		config_get name "$1" name ""
		config_get icon "$1" icon "clock"
		config_get enabled "$1" enabled "0"
		config_get action "$1" action "block"
		config_get allowed_categories "$1" allowed_categories ""
		config_get blocked_categories "$1" blocked_categories ""

		json_add_object ""
		json_add_string "id" "$1"
		json_add_string "name" "$name"
		json_add_string "icon" "$icon"
		json_add_boolean "enabled" "$enabled"
		json_add_string "action" "$action"
		json_add_string "allowed_categories" "$allowed_categories"
		json_add_string "blocked_categories" "$blocked_categories"
		json_close_object
	}

	config_foreach _add_preset preset_mode

	# Add built-in presets if none exist
	local preset_count=0
	config_foreach _count_preset preset_mode
	_count_preset() { preset_count=$(( preset_count + 1 )); }

	if [ "$preset_count" -eq 0 ]; then
		json_add_object ""
		json_add_string "id" "preset_bedtime"
		json_add_string "name" "Bedtime"
		json_add_string "icon" "moon"
		json_add_boolean "enabled" 0
		json_add_string "action" "block"
		json_add_string "blocked_categories" "all"
		json_add_boolean "builtin" 1
		json_close_object

		json_add_object ""
		json_add_string "id" "preset_homework"
		json_add_string "name" "Homework"
		json_add_string "icon" "book"
		json_add_boolean "enabled" 0
		json_add_string "action" "filter"
		json_add_string "allowed_categories" "education reference"
		json_add_string "blocked_categories" "gaming social streaming"
		json_add_boolean "builtin" 1
		json_close_object

		json_add_object ""
		json_add_string "id" "preset_family"
		json_add_string "name" "Family Time"
		json_add_string "icon" "users"
		json_add_boolean "enabled" 0
		json_add_string "action" "filter"
		json_add_string "allowed_categories" "streaming education"
		json_add_string "blocked_categories" "adult gambling"
		json_add_boolean "builtin" 1
		json_close_object
	fi

	json_close_array
	json_dump
}

# Activate preset mode
activate_preset_mode() {
	read -r input
	json_load "$input"

	local preset_id enabled
	json_get_var preset_id preset_id
	json_get_var enabled enabled "1"
	json_cleanup

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

	# Deactivate all other presets first
	config_load bandwidth
	_deactivate_other() {
		[ "$1" != "$preset_id" ] && uci -q set bandwidth.$1.enabled='0'
	}
	config_foreach _deactivate_other preset_mode

	# Activate this one
	uci -q set bandwidth.$preset_id.enabled="$enabled"
	uci -q commit bandwidth

	# Apply firewall rules based on preset
	apply_preset_rules "$preset_id" "$enabled"

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Preset mode activated"
	json_dump
}

# Apply preset firewall rules (internal)
apply_preset_rules() {
	local preset_id="$1"
	local enabled="$2"

	# Would add iptables/nftables rules here for content filtering
	# For now, just log the action
	logger -t bandwidth-manager "Preset $preset_id set to enabled=$enabled"
}

# Get filter categories
get_filter_categories() {
	json_init
	json_add_array "categories"

	# Standard categories
	local categories="adult gambling gaming social streaming education news shopping finance health travel technology sports entertainment reference downloads"

	for cat in $categories; do
		json_add_object ""
		json_add_string "id" "$cat"
		json_add_string "name" "$(echo "$cat" | sed 's/.*/\u&/')"
		json_close_object
	done

	json_close_array
	json_dump
}

# ============================================
# Bandwidth Alerts
# ============================================

ALERT_HISTORY="/tmp/bandwidth_alerts.log"
PENDING_ALERTS="/tmp/bandwidth_pending_alerts.json"

# Get alert settings
get_alert_settings() {
	config_load bandwidth
	json_init

	local enabled quota_threshold_80 quota_threshold_90 quota_threshold_100 new_device_alert high_bandwidth_alert high_bandwidth_threshold
	config_get enabled alerts enabled "0"
	config_get quota_threshold_80 alerts quota_threshold_80 "1"
	config_get quota_threshold_90 alerts quota_threshold_90 "1"
	config_get quota_threshold_100 alerts quota_threshold_100 "1"
	config_get new_device_alert alerts new_device_alert "0"
	config_get high_bandwidth_alert alerts high_bandwidth_alert "0"
	config_get high_bandwidth_threshold alerts high_bandwidth_threshold "100"

	json_add_boolean "enabled" "$enabled"
	json_add_boolean "quota_threshold_80" "$quota_threshold_80"
	json_add_boolean "quota_threshold_90" "$quota_threshold_90"
	json_add_boolean "quota_threshold_100" "$quota_threshold_100"
	json_add_boolean "new_device_alert" "$new_device_alert"
	json_add_boolean "high_bandwidth_alert" "$high_bandwidth_alert"
	json_add_int "high_bandwidth_threshold" "$high_bandwidth_threshold"

	# Email config
	local smtp_server smtp_port smtp_user smtp_tls recipient sender
	config_get smtp_server email smtp_server ""
	config_get smtp_port email smtp_port "587"
	config_get smtp_user email smtp_user ""
	config_get smtp_tls email smtp_tls "1"
	config_get recipient email recipient ""
	config_get sender email sender ""

	json_add_object "email"
	json_add_string "smtp_server" "$smtp_server"
	json_add_int "smtp_port" "$smtp_port"
	json_add_string "smtp_user" "$smtp_user"
	json_add_boolean "smtp_tls" "$smtp_tls"
	json_add_string "recipient" "$recipient"
	json_add_string "sender" "$sender"
	json_add_boolean "configured" "$([ -n "$smtp_server" ] && [ -n "$recipient" ] && echo 1 || echo 0)"
	json_close_object

	# SMS config
	local sms_provider account_sid from_number to_number
	config_get sms_provider sms provider ""
	config_get account_sid sms account_sid ""
	config_get from_number sms from_number ""
	config_get to_number sms to_number ""

	json_add_object "sms"
	json_add_string "provider" "$sms_provider"
	json_add_string "from_number" "$from_number"
	json_add_string "to_number" "$to_number"
	json_add_boolean "configured" "$([ -n "$sms_provider" ] && [ -n "$to_number" ] && echo 1 || echo 0)"
	json_close_object

	json_dump
}

# Update alert settings
update_alert_settings() {
	read -r input
	json_load "$input"

	local enabled quota_threshold_80 quota_threshold_90 quota_threshold_100 new_device_alert high_bandwidth_alert high_bandwidth_threshold
	json_get_var enabled enabled "0"
	json_get_var quota_threshold_80 quota_threshold_80 "1"
	json_get_var quota_threshold_90 quota_threshold_90 "1"
	json_get_var quota_threshold_100 quota_threshold_100 "1"
	json_get_var new_device_alert new_device_alert "0"
	json_get_var high_bandwidth_alert high_bandwidth_alert "0"
	json_get_var high_bandwidth_threshold high_bandwidth_threshold "100"
	json_cleanup

	uci -q batch << EOF
set bandwidth.alerts.enabled='$enabled'
set bandwidth.alerts.quota_threshold_80='$quota_threshold_80'
set bandwidth.alerts.quota_threshold_90='$quota_threshold_90'
set bandwidth.alerts.quota_threshold_100='$quota_threshold_100'
set bandwidth.alerts.new_device_alert='$new_device_alert'
set bandwidth.alerts.high_bandwidth_alert='$high_bandwidth_alert'
set bandwidth.alerts.high_bandwidth_threshold='$high_bandwidth_threshold'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Alert settings updated"
	json_dump
}

# Configure email settings
configure_email() {
	read -r input
	json_load "$input"

	local smtp_server smtp_port smtp_user smtp_password smtp_tls recipient sender
	json_get_var smtp_server smtp_server
	json_get_var smtp_port smtp_port "587"
	json_get_var smtp_user smtp_user ""
	json_get_var smtp_password smtp_password ""
	json_get_var smtp_tls smtp_tls "1"
	json_get_var recipient recipient
	json_get_var sender sender ""
	json_cleanup

	if [ -z "$smtp_server" ] || [ -z "$recipient" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "SMTP server and recipient are required"
		json_dump
		return 1
	fi

	uci -q batch << EOF
set bandwidth.email.smtp_server='$smtp_server'
set bandwidth.email.smtp_port='$smtp_port'
set bandwidth.email.smtp_user='$smtp_user'
set bandwidth.email.smtp_password='$smtp_password'
set bandwidth.email.smtp_tls='$smtp_tls'
set bandwidth.email.recipient='$recipient'
set bandwidth.email.sender='$sender'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Email settings configured"
	json_dump
}

# Configure SMS settings
configure_sms() {
	read -r input
	json_load "$input"

	local provider account_sid auth_token from_number to_number
	json_get_var provider provider
	json_get_var account_sid account_sid ""
	json_get_var auth_token auth_token ""
	json_get_var from_number from_number ""
	json_get_var to_number to_number
	json_cleanup

	if [ -z "$provider" ] || [ -z "$to_number" ]; then
		json_init
		json_add_boolean "success" 0
		json_add_string "message" "Provider and to_number are required"
		json_dump
		return 1
	fi

	uci -q batch << EOF
set bandwidth.sms.provider='$provider'
set bandwidth.sms.account_sid='$account_sid'
set bandwidth.sms.auth_token='$auth_token'
set bandwidth.sms.from_number='$from_number'
set bandwidth.sms.to_number='$to_number'
commit bandwidth
EOF

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "SMS settings configured"
	json_dump
}

# Test notification
test_notification() {
	read -r input
	json_load "$input"

	local type
	json_get_var type type "email"
	json_cleanup

	config_load bandwidth

	if [ "$type" = "email" ]; then
		local smtp_server recipient sender
		config_get smtp_server email smtp_server ""
		config_get recipient email recipient ""
		config_get sender email sender "secubox@router.local"

		if [ -z "$smtp_server" ] || [ -z "$recipient" ]; then
			json_init
			json_add_boolean "success" 0
			json_add_string "message" "Email not configured"
			json_dump
			return 1
		fi

		# Send test email using msmtp or sendmail
		if command -v msmtp >/dev/null 2>&1; then
			echo -e "Subject: SecuBox Test Alert\n\nThis is a test notification from SecuBox Bandwidth Manager." | \
				msmtp -a default "$recipient" 2>/dev/null
		elif command -v sendmail >/dev/null 2>&1; then
			echo -e "To: $recipient\nFrom: $sender\nSubject: SecuBox Test Alert\n\nThis is a test notification from SecuBox Bandwidth Manager." | \
				sendmail "$recipient" 2>/dev/null
		else
			json_init
			json_add_boolean "success" 0
			json_add_string "message" "No mail client available (msmtp or sendmail)"
			json_dump
			return 1
		fi
	elif [ "$type" = "sms" ]; then
		local provider to_number
		config_get provider sms provider ""
		config_get to_number sms to_number ""

		if [ -z "$provider" ] || [ -z "$to_number" ]; then
			json_init
			json_add_boolean "success" 0
			json_add_string "message" "SMS not configured"
			json_dump
			return 1
		fi

		# SMS sending would require provider-specific implementation
		# For now, just log
		logger -t bandwidth-manager "Test SMS would be sent to $to_number via $provider"
	fi

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Test notification sent"
	json_dump
}

# Get alert history
get_alert_history() {
	read -r input
	json_load "$input"

	local limit
	json_get_var limit limit "50"
	json_cleanup

	json_init
	json_add_array "alerts"

	if [ -f "$ALERT_HISTORY" ]; then
		tail -n "$limit" "$ALERT_HISTORY" | while IFS='|' read timestamp type severity message acknowledged; do
			json_add_object ""
			json_add_string "timestamp" "$timestamp"
			json_add_string "type" "$type"
			json_add_string "severity" "$severity"
			json_add_string "message" "$message"
			json_add_boolean "acknowledged" "${acknowledged:-0}"
			json_close_object
		done
	fi

	json_close_array
	json_dump
}

# Acknowledge alert
acknowledge_alert() {
	read -r input
	json_load "$input"

	local timestamp
	json_get_var timestamp timestamp
	json_cleanup

	if [ -f "$ALERT_HISTORY" ] && [ -n "$timestamp" ]; then
		sed -i "s/^${timestamp}|\\(.*\\)|0\$/\1|1/" "$ALERT_HISTORY"
	fi

	json_init
	json_add_boolean "success" 1
	json_add_string "message" "Alert acknowledged"
	json_dump
}

# Get pending (unacknowledged) alerts
get_pending_alerts() {
	json_init
	json_add_array "alerts"

	local count=0
	if [ -f "$ALERT_HISTORY" ]; then
		grep '|0$' "$ALERT_HISTORY" 2>/dev/null | while IFS='|' read timestamp type severity message acknowledged; do
			json_add_object ""
			json_add_string "timestamp" "$timestamp"
			json_add_string "type" "$type"
			json_add_string "severity" "$severity"
			json_add_string "message" "$message"
			json_close_object
			count=$(( count + 1 ))
		done
	fi

	json_close_array
	json_add_int "count" "$count"
	json_dump
}

# Check thresholds and create alerts (called by cron)
check_alert_thresholds() {
	config_load bandwidth

	local enabled
	config_get enabled alerts enabled "0"
	[ "$enabled" != "1" ] && return

	local quota_threshold_80 quota_threshold_90 quota_threshold_100
	config_get quota_threshold_80 alerts quota_threshold_80 "1"
	config_get quota_threshold_90 alerts quota_threshold_90 "1"
	config_get quota_threshold_100 alerts quota_threshold_100 "1"

	# Check each quota
	_check_quota() {
		local mac name limit_mb enabled
		config_get mac "$1" mac ""
		config_get name "$1" name ""
		config_get limit_mb "$1" limit_mb "0"
		config_get enabled "$1" enabled "1"

		[ "$enabled" != "1" ] || [ "$limit_mb" -eq 0 ] && return

		local used_mb=$(get_mac_usage "$mac")
		local percent=$(( (used_mb * 100) / limit_mb ))

		local alert_key="${mac}_${percent}"
		local sent_file="/tmp/bandwidth_alert_sent_$alert_key"

		if [ "$percent" -ge 100 ] && [ "$quota_threshold_100" = "1" ] && [ ! -f "$sent_file" ]; then
			create_alert "quota" "critical" "Device $name ($mac) has exceeded 100% of quota ($used_mb MB / $limit_mb MB)"
			touch "$sent_file"
		elif [ "$percent" -ge 90 ] && [ "$quota_threshold_90" = "1" ] && [ ! -f "/tmp/bandwidth_alert_sent_${mac}_90" ]; then
			create_alert "quota" "warning" "Device $name ($mac) has reached 90% of quota ($used_mb MB / $limit_mb MB)"
			touch "/tmp/bandwidth_alert_sent_${mac}_90"
		elif [ "$percent" -ge 80 ] && [ "$quota_threshold_80" = "1" ] && [ ! -f "/tmp/bandwidth_alert_sent_${mac}_80" ]; then
			create_alert "quota" "info" "Device $name ($mac) has reached 80% of quota ($used_mb MB / $limit_mb MB)"
			touch "/tmp/bandwidth_alert_sent_${mac}_80"
		fi
	}
	config_foreach _check_quota quota

	json_init
	json_add_boolean "success" 1
	json_dump
}

# Create alert (internal)
create_alert() {
	local type="$1"
	local severity="$2"
	local message="$3"

	local timestamp=$(date +%s)
	echo "${timestamp}|${type}|${severity}|${message}|0" >> "$ALERT_HISTORY"

	# Send notification if configured
	send_alert_notification "$type" "$severity" "$message"
}

# Send alert notification (internal)
send_alert_notification() {
	local type="$1"
	local severity="$2"
	local message="$3"

	config_load bandwidth

	# Check if email is configured and send
	local smtp_server recipient
	config_get smtp_server email smtp_server ""
	config_get recipient email recipient ""

	if [ -n "$smtp_server" ] && [ -n "$recipient" ]; then
		local sender
		config_get sender email sender "secubox@router.local"

		if command -v msmtp >/dev/null 2>&1; then
			echo -e "Subject: [SecuBox] ${severity}: ${type} alert\n\n${message}" | \
				msmtp -a default "$recipient" 2>/dev/null &
		fi
	fi

	# Log to syslog
	logger -t bandwidth-manager "ALERT [$severity] $type: $message"
}

# ============================================
# Traffic Graphs Data
# ============================================

# Get real-time bandwidth (current Mbps)
get_realtime_bandwidth() {
	local interface
	config_load bandwidth
	config_get interface global interface "br-lan"

	# Get current values
	local rx1=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0)
	local tx1=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0)

	# Wait 1 second
	sleep 1

	# Get new values
	local rx2=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0)
	local tx2=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0)

	# Calculate Mbps
	local rx_bps=$(( (rx2 - rx1) * 8 ))
	local tx_bps=$(( (tx2 - tx1) * 8 ))
	local rx_mbps=$(( rx_bps / 1000000 ))
	local tx_mbps=$(( tx_bps / 1000000 ))

	json_init
	json_add_int "rx_mbps" "$rx_mbps"
	json_add_int "tx_mbps" "$tx_mbps"
	json_add_int "rx_bps" "$rx_bps"
	json_add_int "tx_bps" "$tx_bps"
	json_add_int "timestamp" "$(date +%s)"
	json_dump
}

# Get historical traffic data
get_historical_traffic() {
	read -r input
	json_load "$input"

	local period granularity
	json_get_var period period "24h"
	json_get_var granularity granularity "hour"
	json_cleanup

	init_analytics_db

	json_init
	json_add_array "data"

	local now=$(date +%s)
	local threshold=0

	case "$period" in
		"1h") threshold=$(( now - 3600 )) ;;
		"6h") threshold=$(( now - 21600 )) ;;
		"24h") threshold=$(( now - 86400 )) ;;
		"7d") threshold=$(( now - 604800 )) ;;
		"30d") threshold=$(( now - 2592000 )) ;;
	esac

	if [ -f "$ANALYTICS_DB" ]; then
		while IFS='|' read hour date rx tx peak_rx peak_tx clients; do
			[ "$hour" = "# Hour" ] && continue

			# Convert date to timestamp for comparison
			local entry_ts=$(date -d "${date:0:4}-${date:4:2}-${date:6:2} ${hour}:00" +%s 2>/dev/null || echo 0)
			[ "$entry_ts" -lt "$threshold" ] && continue

			json_add_object ""
			json_add_int "timestamp" "$entry_ts"
			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 "clients" "$clients"
			json_close_object
		done < "$ANALYTICS_DB"
	fi

	json_close_array
	json_dump
}

# Get device-specific traffic
get_device_traffic() {
	read -r input
	json_load "$input"

	local mac period
	json_get_var mac mac
	json_get_var period period "24h"
	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

	json_init

	# Get current stats from iptables
	local rx_bytes=0 tx_bytes=0
	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 '{sum+=$2} END {print sum}')
	fi

	# Get hostname
	local hostname ip
	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}')

	json_add_string "mac" "$mac"
	json_add_string "hostname" "${hostname:-unknown}"
	json_add_string "ip" "${ip:-}"
	json_add_int "rx_bytes" "${rx_bytes:-0}"
	json_add_int "tx_bytes" "${tx_bytes:-0}"

	# Historical data from usage DB
	json_add_array "history"
	if [ -f "$USAGE_DB" ]; then
		local now=$(date +%s)
		local threshold=0
		case "$period" in
			"1h") threshold=$(( now - 3600 )) ;;
			"6h") threshold=$(( now - 21600 )) ;;
			"24h") threshold=$(( now - 86400 )) ;;
			"7d") threshold=$(( now - 604800 )) ;;
		esac

		grep -i "^$mac|" "$USAGE_DB" 2>/dev/null | while IFS='|' read db_mac timestamp rx tx; do
			[ "$timestamp" -lt "$threshold" ] && continue
			json_add_object ""
			json_add_int "timestamp" "$timestamp"
			json_add_int "rx_bytes" "$rx"
			json_add_int "tx_bytes" "$tx"
			json_close_object
		done
	fi
	json_close_array

	json_dump
}

# Get top bandwidth consumers
get_top_talkers() {
	read -r input
	json_load "$input"

	local period limit
	json_get_var period period "24h"
	json_get_var limit limit "10"
	json_cleanup

	json_init
	json_add_array "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 -n "$limit" | 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
	json_dump
}

# Get protocol breakdown
get_protocol_breakdown() {
	read -r input
	json_load "$input"

	local period
	json_get_var period period "24h"
	json_cleanup

	json_init
	json_add_array "protocols"

	if [ -f "$NDPID_FLOWS" ] && command -v jq >/dev/null 2>&1; 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 | .[:10][] | "\(.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
	else
		# Fallback: basic protocol info from conntrack
		if [ -f /proc/net/nf_conntrack ]; then
			for proto in tcp udp icmp; do
				local count=$(grep -c "$proto" /proc/net/nf_conntrack 2>/dev/null || echo 0)
				json_add_object ""
				json_add_string "protocol" "$proto"
				json_add_int "connections" "$count"
				json_close_object
			done
		fi
	fi

	json_close_array
	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": {},
	"get_builtin_profiles": {},
	"list_profiles": {},
	"get_profile": { "profile_id": "string" },
	"create_profile": { "name": "string", "description": "", "icon": "tag", "color": "#6366f1", "priority": 5, "limit_down": 0, "limit_up": 0, "latency_mode": "normal", "content_filter": "", "isolate": 0, "schedule": "" },
	"update_profile": { "profile_id": "string", "name": "string", "description": "", "icon": "tag", "color": "#6366f1", "priority": 5, "limit_down": 0, "limit_up": 0, "latency_mode": "normal", "content_filter": "", "isolate": 0, "schedule": "", "enabled": 1 },
	"delete_profile": { "profile_id": "string" },
	"clone_profile": { "source_id": "string", "new_name": "string" },
	"assign_profile_to_device": { "mac": "string", "profile_id": "string", "custom_limit_down": 0, "custom_limit_up": 0 },
	"assign_profile_to_group": { "group_id": "string", "profile_id": "string" },
	"remove_profile_assignment": { "mac": "string" },
	"list_profile_assignments": {},
	"list_parental_schedules": {},
	"create_parental_schedule": { "name": "string", "target_type": "device", "target": "string", "action": "block", "start_time": "21:00", "end_time": "07:00", "days": "mon tue wed thu fri" },
	"update_parental_schedule": { "schedule_id": "string", "name": "string", "target_type": "device", "target": "string", "action": "block", "start_time": "21:00", "end_time": "07:00", "days": "mon tue wed thu fri", "enabled": 1 },
	"delete_parental_schedule": { "schedule_id": "string" },
	"toggle_parental_schedule": { "schedule_id": "string", "enabled": 1 },
	"list_preset_modes": {},
	"activate_preset_mode": { "preset_id": "string", "enabled": 1 },
	"get_filter_categories": {},
	"get_alert_settings": {},
	"update_alert_settings": { "enabled": 0, "quota_threshold_80": 1, "quota_threshold_90": 1, "quota_threshold_100": 1, "new_device_alert": 0, "high_bandwidth_alert": 0, "high_bandwidth_threshold": 100 },
	"configure_email": { "smtp_server": "string", "smtp_port": 587, "smtp_user": "", "smtp_password": "", "smtp_tls": 1, "recipient": "string", "sender": "" },
	"configure_sms": { "provider": "string", "account_sid": "", "auth_token": "", "from_number": "", "to_number": "string" },
	"test_notification": { "type": "email" },
	"get_alert_history": { "limit": 50 },
	"acknowledge_alert": { "timestamp": "string" },
	"get_pending_alerts": {},
	"check_alert_thresholds": {},
	"get_realtime_bandwidth": {},
	"get_historical_traffic": { "period": "24h", "granularity": "hour" },
	"get_device_traffic": { "mac": "string", "period": "24h" },
	"get_top_talkers": { "period": "24h", "limit": 10 },
	"get_protocol_breakdown": { "period": "24h" }
}
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 ;;
			get_builtin_profiles) get_builtin_profiles ;;
			list_profiles) list_profiles ;;
			get_profile) get_profile ;;
			create_profile) create_profile ;;
			update_profile) update_profile ;;
			delete_profile) delete_profile ;;
			clone_profile) clone_profile ;;
			assign_profile_to_device) assign_profile_to_device ;;
			assign_profile_to_group) assign_profile_to_group ;;
			remove_profile_assignment) remove_profile_assignment ;;
			list_profile_assignments) list_profile_assignments ;;
			list_parental_schedules) list_parental_schedules ;;
			create_parental_schedule) create_parental_schedule ;;
			update_parental_schedule) update_parental_schedule ;;
			delete_parental_schedule) delete_parental_schedule ;;
			toggle_parental_schedule) toggle_parental_schedule ;;
			list_preset_modes) list_preset_modes ;;
			activate_preset_mode) activate_preset_mode ;;
			get_filter_categories) get_filter_categories ;;
			get_alert_settings) get_alert_settings ;;
			update_alert_settings) update_alert_settings ;;
			configure_email) configure_email ;;
			configure_sms) configure_sms ;;
			test_notification) test_notification ;;
			get_alert_history) get_alert_history ;;
			acknowledge_alert) acknowledge_alert ;;
			get_pending_alerts) get_pending_alerts ;;
			check_alert_thresholds) check_alert_thresholds ;;
			get_realtime_bandwidth) get_realtime_bandwidth ;;
			get_historical_traffic) get_historical_traffic ;;
			get_device_traffic) get_device_traffic ;;
			get_top_talkers) get_top_talkers ;;
			get_protocol_breakdown) get_protocol_breakdown ;;
			*)
				json_init
				json_add_boolean "success" 0
				json_add_string "error" "Unknown method: $2"
				json_dump
				;;
		esac
		;;
esac
