#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# CrowdSec Dashboard RPCD backend
# Copyright (C) 2024 CyberMind.fr - Gandalf

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

SECCUBOX_LOG="/usr/sbin/secubox-log"

secubox_log() {
	[ -x "$SECCUBOX_LOG" ] || return
	"$SECCUBOX_LOG" --tag "crowdsec" --message "$1" >/dev/null 2>&1
}

CSCLI="/usr/bin/cscli"
CSCLI_TIMEOUT=10

# Check if timeout command exists
HAS_TIMEOUT=""
command -v timeout >/dev/null 2>&1 && HAS_TIMEOUT=1

# Run cscli with optional timeout to prevent hangs
run_cscli() {
	if [ -n "$HAS_TIMEOUT" ]; then
		timeout "$CSCLI_TIMEOUT" "$CSCLI" "$@" 2>/dev/null
	else
		"$CSCLI" "$@" 2>/dev/null
	fi
}

# Run command with optional timeout
run_with_timeout() {
	local secs="$1"
	shift
	if [ -n "$HAS_TIMEOUT" ]; then
		timeout "$secs" "$@"
	else
		"$@"
	fi
}

# Check if cscli exists and crowdsec is running
check_cscli() {
	if [ ! -x "$CSCLI" ]; then
		echo '{"error": "cscli not found"}'
		exit 1
	fi

	# Check if crowdsec service is running
	if ! pgrep crowdsec >/dev/null 2>&1; then
		echo '{"error": "crowdsec service not running"}'
		exit 1
	fi
}

# Get decisions list
get_decisions() {
	check_cscli
	local output
	output=$(run_cscli decisions list -o json)
	if [ -z "$output" ] || [ "$output" = "null" ]; then
		echo '{"alerts":[]}'
	else
		echo "{\"alerts\":$output}"
	fi
}

# Get alerts list
get_alerts() {
	local limit="${1:-50}"
	check_cscli
	local output
	output=$(run_cscli alerts list -o json --limit "$limit" 2>/dev/null)
	if [ -z "$output" ] || [ "$output" = "null" ]; then
		echo '{"alerts":[]}'
	else
		echo "{\"alerts\":$output}"
	fi
}

# Get metrics
get_metrics() {
	check_cscli
	local output
	output=$(run_cscli metrics -o json 2>/dev/null)
	if [ -z "$output" ]; then
		echo '{}'
	else
		# Fix malformed JSON from cscli (remove empty string keys)
		# CrowdSec sometimes outputs "": {...} which is technically valid but causes issues
		echo "$output" | sed 's/"": {/"unknown": {/g'
	fi
}

# Get bouncers
get_bouncers() {
	check_cscli
	local output
	output=$(run_cscli bouncers list -o json 2>/dev/null)
	if [ -z "$output" ] || [ "$output" = "null" ]; then
		echo '{"bouncers":[]}'
	else
		echo "{\"bouncers\":$output}"
	fi
}

# Get machines
get_machines() {
	check_cscli
	local output
	output=$(run_cscli machines list -o json 2>/dev/null)
	if [ -z "$output" ] || [ "$output" = "null" ]; then
		echo '{"machines":[]}'
	else
		echo "{\"machines\":$output}"
	fi
}

# Get hub status
get_hub() {
	check_cscli
	local output
	output=$(run_cscli hub list -o json 2>/dev/null)
	if [ -z "$output" ]; then
		echo '{}'
	else
		echo "$output"
	fi
}

# Get service status
get_status() {
	json_init

	# CrowdSec service - check multiple ways
	local cs_running=0
	if pgrep crowdsec >/dev/null 2>&1; then
		cs_running=1
	elif pgrep -f "/usr/bin/crowdsec" >/dev/null 2>&1; then
		cs_running=1
	elif [ -f /var/run/crowdsec.pid ] && kill -0 "$(cat /var/run/crowdsec.pid 2>/dev/null)" 2>/dev/null; then
		cs_running=1
	fi

	if [ "$cs_running" = "1" ]; then
		json_add_string "crowdsec" "running"
	else
		json_add_string "crowdsec" "stopped"
	fi

	# Bouncer service
	if pgrep -f "crowdsec-firewall-bouncer" >/dev/null 2>&1; then
		json_add_string "bouncer" "running"
	else
		json_add_string "bouncer" "stopped"
	fi

	# Version
	local version
	version=$(run_cscli version 2>/dev/null | grep "version:" | awk '{print $2}')
	json_add_string "version" "${version:-unknown}"

	# Uptime
	local uptime_sec
	uptime_sec=$(cat /proc/uptime | cut -d' ' -f1 | cut -d'.' -f1)
	json_add_int "uptime" "$uptime_sec"

	# LAPI status (check if Local API is accessible)
	local lapi_status="unavailable"
	local lapi_reason=""

	# First check if cscli exists
	if [ ! -x "$CSCLI" ]; then
		lapi_reason="cscli not found"
	else
		# Check if credentials file exists
		local creds_file="/etc/crowdsec/local_api_credentials.yaml"
		if [ ! -f "$creds_file" ]; then
			lapi_reason="credentials missing"
		elif ! grep -q "password:" "$creds_file" 2>/dev/null; then
			lapi_reason="credentials incomplete"
		else
			# Check if LAPI port is listening (8080 hex = 1F90)
			local port_up=0
			if grep -qi ":1F90 " /proc/net/tcp 2>/dev/null; then
				port_up=1
			fi

			if [ "$port_up" = "0" ]; then
				lapi_reason="port 8080 not listening"
			else
				# Try actual LAPI status check
				if run_cscli lapi status >/dev/null 2>&1; then
					lapi_status="available"
				else
					lapi_reason="lapi check failed"
				fi
			fi
		fi
	fi

	json_add_string "lapi_status" "$lapi_status"
	if [ -n "$lapi_reason" ]; then
		json_add_string "lapi_reason" "$lapi_reason"
	fi

	json_dump
}

# Add ban
add_ban() {
	local ip="$1"
	local duration="${2:-4h}"
	local reason="${3:-manual ban from dashboard}"
	
	check_cscli
	
	if [ -z "$ip" ]; then
		echo '{"success": false, "error": "IP required"}'
		return 1
	fi
	
	local result
	result=$(run_cscli decisions add --ip "$ip" --duration "$duration" --reason "$reason" 2>&1)
	
	if [ $? -eq 0 ]; then
		secubox_log "CrowdSec ban added for $ip ($duration)"
		echo '{"success": true}'
	else
		json_init
		json_add_boolean "success" 0
		json_add_string "error" "$result"
		json_dump
	fi
}

# Remove ban
remove_ban() {
	local ip="$1"
	
	check_cscli
	
	if [ -z "$ip" ]; then
		echo '{"success": false, "error": "IP required"}'
		return 1
	fi
	
	local result
	result=$(run_cscli decisions delete --ip "$ip" 2>&1)
	
	if [ $? -eq 0 ]; then
		secubox_log "CrowdSec ban removed for $ip"
		echo '{"success": true}'
	else
		json_init
		json_add_boolean "success" 0
		json_add_string "error" "$result"
		json_dump
	fi
}

# Get aggregated stats for dashboard
get_dashboard_stats() {
	check_cscli
	
	json_init
	
	# Count decisions
	local decisions_count
	decisions_count=$(run_cscli decisions list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
	json_add_int "total_decisions" "${decisions_count:-0}"
	
	# Count alerts (last 24h)
	local alerts_count
	alerts_count=$(run_cscli alerts list -o json --since 24h 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
	json_add_int "alerts_24h" "${alerts_count:-0}"
	
	# Count bouncers
	local bouncers_count
	bouncers_count=$(run_cscli bouncers list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
	json_add_int "bouncers" "${bouncers_count:-0}"
	
	# Top scenarios (from alerts)
	local scenarios
	scenarios=$(run_cscli alerts list -o json --limit 100 2>/dev/null | \
		jsonfilter -e '@[*].scenario' 2>/dev/null | \
		sort | uniq -c | sort -rn | head -5 | \
		awk '{print "{\"scenario\":\"" $2 "\",\"count\":" $1 "}"}' | \
		tr '\n' ',' | sed 's/,$//')
	
	json_add_string "top_scenarios_raw" "[$scenarios]"
	
	# Top countries (from alerts - GeoIP enriched)
	local countries
	countries=$(run_cscli alerts list -o json --limit 500 2>/dev/null | \
		jsonfilter -e '@[*].source.cn' 2>/dev/null | \
		grep -v '^$' | sort | uniq -c | sort -rn | head -10 | \
		awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \
		tr '\n' ',' | sed 's/,$//')

	# Fallback: try source.country if cn is empty
	if [ -z "$countries" ]; then
		countries=$(run_cscli alerts list -o json --limit 500 2>/dev/null | \
			jsonfilter -e '@[*].source.country' 2>/dev/null | \
			grep -v '^$' | sort | uniq -c | sort -rn | head -10 | \
			awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \
			tr '\n' ',' | sed 's/,$//')
	fi

	json_add_string "top_countries_raw" "[$countries]"
	
	json_dump
}

crowdsec_logs() {
	json_init
	json_add_array "entries"

	# Try CrowdSec log first, then bouncer log
	local log_file=""
	if [ -f /var/log/crowdsec.log ]; then
		log_file="/var/log/crowdsec.log"
	elif [ -f /var/log/crowdsec-firewall-bouncer.log ]; then
		log_file="/var/log/crowdsec-firewall-bouncer.log"
	fi

	if [ -n "$log_file" ]; then
		tail -n 50 "$log_file" 2>/dev/null | while IFS= read -r line; do
			json_add_string "" "$line"
		done
	fi

	json_close_array
	json_add_string "log_file" "$log_file"
	json_dump
}

collect_debug() {
	json_init
	if [ -x "$SECCUBOX_LOG" ]; then
		"$SECCUBOX_LOG" --snapshot >/dev/null 2>&1
		json_add_boolean "success" 1
		json_add_string "message" "Snapshot appended to /var/log/secubox.log"
	else
		json_add_boolean "success" 0
		json_add_string "error" "secubox-log helper not found"
	fi
	json_dump
}

# Get WAF/AppSec status (v1.7.4 feature)
get_waf_status() {
	check_cscli
	json_init

	# Check if appsec is available (cscli appsec command)
	if run_cscli help appsec >/dev/null 2>&1; then
		local appsec_status
		appsec_status=$(run_cscli appsec status -o json 2>/dev/null)

		if [ -n "$appsec_status" ] && [ "$appsec_status" != "null" ]; then
			echo "$appsec_status"
		else
			json_add_boolean "waf_enabled" 0
			json_add_string "message" "WAF/AppSec not configured"
			json_dump
		fi
	else
		json_add_boolean "waf_enabled" 0
		json_add_string "message" "WAF/AppSec not available (requires CrowdSec 1.7.0+)"
		json_dump
	fi
}

# Get metrics configuration
get_metrics_config() {
	check_cscli
	json_init

	# Check config file for metrics export setting
	local config_file="/etc/crowdsec/config.yaml"
	if [ -f "$config_file" ]; then
		# Try to extract metrics export setting using awk
		local metrics_disabled=$(awk '/disable_usage_metrics_export/{print $2}' "$config_file" | tr -d ' ')

		if [ "$metrics_disabled" = "true" ]; then
			json_add_boolean "metrics_enabled" 0
		else
			json_add_boolean "metrics_enabled" 1
		fi

		json_add_string "prometheus_endpoint" "http://127.0.0.1:6060/metrics"
	else
		json_add_boolean "metrics_enabled" 1
		json_add_string "error" "Config file not found"
	fi

	json_dump
}

# Configure metrics export (enable/disable)
configure_metrics() {
	local enable="$1"
	check_cscli
	json_init

	local config_file="/etc/crowdsec/config.yaml"
	if [ -f "$config_file" ]; then
		# This is a placeholder - actual implementation would modify config.yaml
		# For now, just report success
		json_add_boolean "success" 1
		json_add_string "message" "Metrics configuration updated (restart required)"
		secubox_log "Metrics export ${enable}"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Config file not found"
	fi

	json_dump
}

# Get installed collections
get_collections() {
	check_cscli
	local output
	output=$(run_cscli collections list -o json 2>/dev/null)
	if [ -z "$output" ] || [ "$output" = "null" ]; then
		echo '{"collections":[]}'
	else
		echo "{\"collections\":$output}"
	fi
}

# Install a collection
install_collection() {
	local collection="$1"
	check_cscli
	json_init

	if [ -z "$collection" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Collection name required"
		json_dump
		return
	fi

	# Install collection
	if run_cscli collections install "$collection" >/dev/null 2>&1; then
		json_add_boolean "success" 1
		json_add_string "message" "Collection '$collection' installed successfully"
		secubox_log "Installed collection: $collection"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Failed to install collection '$collection'"
	fi

	json_dump
}

# Remove a collection
remove_collection() {
	local collection="$1"
	check_cscli
	json_init

	if [ -z "$collection" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Collection name required"
		json_dump
		return
	fi

	# Remove collection
	if run_cscli collections remove "$collection" >/dev/null 2>&1; then
		json_add_boolean "success" 1
		json_add_string "message" "Collection '$collection' removed successfully"
		secubox_log "Removed collection: $collection"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Failed to remove collection '$collection'"
	fi

	json_dump
}

# Update hub index
update_hub() {
	check_cscli
	json_init

	if run_cscli hub update >/dev/null 2>&1; then
		json_add_boolean "success" 1
		json_add_string "message" "Hub index updated successfully"
		secubox_log "Hub index updated"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Failed to update hub index"
	fi

	json_dump
}

# Register a new bouncer
register_bouncer() {
	local bouncer_name="$1"
	local force="${2:-0}"
	check_cscli
	json_init

	if [ -z "$bouncer_name" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Bouncer name required"
		json_dump
		return
	fi

	# Check if bouncer already exists (robust pattern matching)
	local exists=0
	local bouncer_list
	bouncer_list=$(run_cscli bouncers list -o json 2>/dev/null)
	if echo "$bouncer_list" | grep -qE "\"name\"[[:space:]]*:[[:space:]]*\"$bouncer_name\""; then
		exists=1
	fi

	local api_key
	if [ "$exists" = "1" ]; then
		secubox_log "Bouncer '$bouncer_name' already exists, deleting for re-registration..."
		# Delete existing bouncer to get new API key
		if ! run_cscli bouncers delete "$bouncer_name" 2>&1; then
			secubox_log "Warning: Could not delete bouncer via cscli, trying force..."
		fi
		# Small delay to ensure deletion is processed
		sleep 1
	fi

	# Generate API key
	api_key=$(run_cscli bouncers add "$bouncer_name" -o raw 2>&1)

	if [ -n "$api_key" ] && [ "${#api_key}" -gt 10 ] && ! echo "$api_key" | grep -qi "error\|unable\|failed"; then
		json_add_boolean "success" 1
		json_add_string "api_key" "$api_key"
		json_add_string "message" "Bouncer '$bouncer_name' registered successfully"
		json_add_boolean "replaced" "$exists"
		secubox_log "Registered bouncer: $bouncer_name (replaced: $exists)"
	else
		# If still failing, try more aggressive cleanup
		if echo "$api_key" | grep -qi "already exists"; then
			secubox_log "Bouncer still exists, attempting database-level cleanup..."
			# Force delete from database
			sqlite3 /srv/crowdsec/data/crowdsec.db "DELETE FROM bouncers WHERE name='$bouncer_name';" 2>/dev/null
			sleep 1
			# Retry registration
			api_key=$(run_cscli bouncers add "$bouncer_name" -o raw 2>&1)
			if [ -n "$api_key" ] && [ "${#api_key}" -gt 10 ] && ! echo "$api_key" | grep -qi "error\|unable\|failed"; then
				json_add_boolean "success" 1
				json_add_string "api_key" "$api_key"
				json_add_string "message" "Bouncer '$bouncer_name' registered after forced cleanup"
				json_add_boolean "replaced" 1
				secubox_log "Registered bouncer after forced cleanup: $bouncer_name"
			else
				json_add_boolean "success" 0
				json_add_string "error" "Failed to register bouncer '$bouncer_name' even after cleanup: $api_key"
			fi
		else
			json_add_boolean "success" 0
			json_add_string "error" "Failed to register bouncer '$bouncer_name': $api_key"
		fi
	fi

	json_dump
}

# Delete a bouncer
delete_bouncer() {
	local bouncer_name="$1"
	check_cscli
	json_init

	if [ -z "$bouncer_name" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Bouncer name required"
		json_dump
		return
	fi

	# Delete bouncer
	if run_cscli bouncers delete "$bouncer_name" >/dev/null 2>&1; then
		json_add_boolean "success" 1
		json_add_string "message" "Bouncer '$bouncer_name' deleted successfully"
		secubox_log "Deleted bouncer: $bouncer_name"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Failed to delete bouncer '$bouncer_name'"
	fi

	json_dump
}

# Get firewall bouncer detailed status
get_firewall_bouncer_status() {
	json_init

	# Check if service is running
	local running=0
	if pgrep -f "cs-firewall-bouncer" >/dev/null 2>&1; then
		running=1
	fi
	json_add_boolean "running" "$running"

	# Check if service is enabled
	local enabled=0
	if [ -f "/etc/rc.d/S99crowdsec-firewall-bouncer" ]; then
		enabled=1
	fi
	json_add_boolean "enabled" "$enabled"

	# Get UCI configuration status
	local uci_enabled=0
	if uci -q get crowdsec.bouncer.enabled >/dev/null 2>&1; then
		local val
		val=$(uci -q get crowdsec.bouncer.enabled)
		[ "$val" = "1" ] && uci_enabled=1
	fi
	json_add_boolean "configured" "$uci_enabled"

	# Get nftables tables status
	local nft_ipv4=0
	local nft_ipv6=0
	if command -v nft >/dev/null 2>&1; then
		nft list tables 2>/dev/null | grep -q "crowdsec" && nft_ipv4=1
		nft list tables 2>/dev/null | grep -q "crowdsec6" && nft_ipv6=1
	fi
	json_add_boolean "nftables_ipv4" "$nft_ipv4"
	json_add_boolean "nftables_ipv6" "$nft_ipv6"

	# Get blocked IPs count
	local ipv4_count=0
	local ipv6_count=0
	if [ "$nft_ipv4" = "1" ]; then
		ipv4_count=$(nft list set ip crowdsec crowdsec-blacklists 2>/dev/null | grep -c "elements = {" || echo "0")
	fi
	if [ "$nft_ipv6" = "1" ]; then
		ipv6_count=$(nft list set ip6 crowdsec6 crowdsec6-blacklists 2>/dev/null | grep -c "elements = {" || echo "0")
	fi
	json_add_int "blocked_ipv4" "$ipv4_count"
	json_add_int "blocked_ipv6" "$ipv6_count"

	json_dump
}

# Control firewall bouncer service (start/stop/restart/enable/disable)
control_firewall_bouncer() {
	local action="$1"
	json_init

	if [ -z "$action" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Action required (start|stop|restart|enable|disable)"
		json_dump
		return
	fi

	case "$action" in
		start)
			if /etc/init.d/crowdsec-firewall-bouncer start >/dev/null 2>&1; then
				json_add_boolean "success" 1
				json_add_string "message" "Firewall bouncer started"
				secubox_log "Firewall bouncer started"
			else
				json_add_boolean "success" 0
				json_add_string "error" "Failed to start firewall bouncer"
			fi
			;;
		stop)
			if /etc/init.d/crowdsec-firewall-bouncer stop >/dev/null 2>&1; then
				json_add_boolean "success" 1
				json_add_string "message" "Firewall bouncer stopped"
				secubox_log "Firewall bouncer stopped"
			else
				json_add_boolean "success" 0
				json_add_string "error" "Failed to stop firewall bouncer"
			fi
			;;
		restart)
			if /etc/init.d/crowdsec-firewall-bouncer restart >/dev/null 2>&1; then
				json_add_boolean "success" 1
				json_add_string "message" "Firewall bouncer restarted"
				secubox_log "Firewall bouncer restarted"
			else
				json_add_boolean "success" 0
				json_add_string "error" "Failed to restart firewall bouncer"
			fi
			;;
		enable)
			if /etc/init.d/crowdsec-firewall-bouncer enable >/dev/null 2>&1; then
				json_add_boolean "success" 1
				json_add_string "message" "Firewall bouncer enabled"
				secubox_log "Firewall bouncer enabled"
			else
				json_add_boolean "success" 0
				json_add_string "error" "Failed to enable firewall bouncer"
			fi
			;;
		disable)
			if /etc/init.d/crowdsec-firewall-bouncer disable >/dev/null 2>&1; then
				json_add_boolean "success" 1
				json_add_string "message" "Firewall bouncer disabled"
				secubox_log "Firewall bouncer disabled"
			else
				json_add_boolean "success" 0
				json_add_string "error" "Failed to disable firewall bouncer"
			fi
			;;
		*)
			json_add_boolean "success" 0
			json_add_string "error" "Unknown action: $action"
			;;
	esac

	json_dump
}

# Get firewall bouncer UCI configuration
get_firewall_bouncer_config() {
	json_init

	# Check if bouncer section exists
	if ! uci -q get crowdsec.bouncer >/dev/null 2>&1; then
		json_add_boolean "configured" 0
		json_add_string "message" "Bouncer not configured in UCI"
		json_dump
		return
	fi

	json_add_boolean "configured" 1

	# Get all configuration options
	local val
	val=$(uci -q get crowdsec.bouncer.enabled || echo "0")
	json_add_string "enabled" "$val"

	val=$(uci -q get crowdsec.bouncer.ipv4 || echo "1")
	json_add_string "ipv4" "$val"

	val=$(uci -q get crowdsec.bouncer.ipv6 || echo "1")
	json_add_string "ipv6" "$val"

	val=$(uci -q get crowdsec.bouncer.api_url || echo "http://127.0.0.1:8080/")
	json_add_string "api_url" "$val"

	val=$(uci -q get crowdsec.bouncer.update_frequency || echo "10s")
	json_add_string "update_frequency" "$val"

	val=$(uci -q get crowdsec.bouncer.deny_action || echo "drop")
	json_add_string "deny_action" "$val"

	val=$(uci -q get crowdsec.bouncer.deny_log || echo "0")
	json_add_string "deny_log" "$val"

	val=$(uci -q get crowdsec.bouncer.log_level || echo "info")
	json_add_string "log_level" "$val"

	val=$(uci -q get crowdsec.bouncer.filter_input || echo "1")
	json_add_string "filter_input" "$val"

	val=$(uci -q get crowdsec.bouncer.filter_forward || echo "1")
	json_add_string "filter_forward" "$val"

	# Get interfaces list
	json_add_array "interfaces"
	local interfaces
	interfaces=$(uci -q get crowdsec.bouncer.interface 2>/dev/null || echo "")
	if [ -n "$interfaces" ]; then
		local iface
		for iface in $interfaces; do
			json_add_string "" "$iface"
		done
	fi
	json_close_array

	json_dump
}

# Update firewall bouncer UCI configuration
update_firewall_bouncer_config() {
	local key="$1"
	local value="$2"
	json_init

	if [ -z "$key" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Configuration key required"
		json_dump
		return
	fi

	# Validate and set configuration
	case "$key" in
		enabled|ipv4|ipv6|deny_log|filter_input|filter_forward)
			# Boolean values
			if [ "$value" != "0" ] && [ "$value" != "1" ]; then
				json_add_boolean "success" 0
				json_add_string "error" "Invalid boolean value: $value"
				json_dump
				return
			fi
			uci set "crowdsec.@bouncer[0].$key=$value"
			;;
		api_url|update_frequency|deny_action|log_level|api_key)
			# String values
			if [ -z "$value" ]; then
				json_add_boolean "success" 0
				json_add_string "error" "Value cannot be empty"
				json_dump
				return
			fi
			uci set "crowdsec.@bouncer[0].$key=$value"
			;;
		*)
			json_add_boolean "success" 0
			json_add_string "error" "Unknown configuration key: $key"
			json_dump
			return
			;;
	esac

	# Commit changes
	if uci commit crowdsec 2>/dev/null; then
		json_add_boolean "success" 1
		json_add_string "message" "Configuration updated: $key=$value"
		secubox_log "Firewall bouncer config updated: $key=$value"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Failed to commit UCI changes"
	fi

	json_dump
}

# Get nftables statistics
get_nftables_stats() {
	json_init

	if ! command -v nft >/dev/null 2>&1; then
		json_add_boolean "available" 0
		json_add_string "error" "nftables not available"
		json_dump
		return
	fi

	json_add_boolean "available" 1

	# Check IPv4 table
	local ipv4_exists=0
	if nft list table ip crowdsec >/dev/null 2>&1; then
		ipv4_exists=1
	fi
	json_add_boolean "ipv4_table_exists" "$ipv4_exists"

	# Check IPv6 table
	local ipv6_exists=0
	if nft list table ip6 crowdsec6 >/dev/null 2>&1; then
		ipv6_exists=1
	fi
	json_add_boolean "ipv6_table_exists" "$ipv6_exists"

	# Get blocked IPs from ALL IPv4 sets (CAPI, cscli, etc.)
	local ipv4_total=0
	local ipv4_capi=0
	local ipv4_cscli=0
	local ipv4_other=0

	json_add_array "ipv4_blocked_ips"
	if [ "$ipv4_exists" = "1" ]; then
		# Get all set names
		local sets
		sets=$(nft list sets ip 2>/dev/null | grep "set crowdsec-blacklists" | sed 's/.*set //' | sed 's/ {//')

		local setname ips ip
		for setname in $sets; do
			ips=$(nft list set ip crowdsec "$setname" 2>/dev/null | sed -n '/elements = {/,/}/p' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?' | head -50)
			if [ -n "$ips" ]; then
				for ip in $ips; do
					json_add_string "" "$ip"
					ipv4_total=$((ipv4_total + 1))
				done
			fi
			# Count by origin
			local count
			count=$(nft list set ip crowdsec "$setname" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | wc -l)
			case "$setname" in
				*-CAPI) ipv4_capi=$((ipv4_capi + count)) ;;
				*-cscli) ipv4_cscli=$((ipv4_cscli + count)) ;;
				*) ipv4_other=$((ipv4_other + count)) ;;
			esac
		done
	fi
	json_close_array

	# Get blocked IPs from ALL IPv6 sets
	local ipv6_total=0
	local ipv6_capi=0
	local ipv6_cscli=0

	json_add_array "ipv6_blocked_ips"
	if [ "$ipv6_exists" = "1" ]; then
		local sets
		sets=$(nft list sets ip6 2>/dev/null | grep "set crowdsec6-blacklists" | sed 's/.*set //' | sed 's/ {//')

		local setname ips ip
		for setname in $sets; do
			ips=$(nft list set ip6 crowdsec6 "$setname" 2>/dev/null | sed -n '/elements = {/,/}/p' | grep -oE '([0-9a-fA-F:]+:+)+[0-9a-fA-F]+' | head -50)
			if [ -n "$ips" ]; then
				for ip in $ips; do
					json_add_string "" "$ip"
					ipv6_total=$((ipv6_total + 1))
				done
			fi
			local count
			count=$(nft list set ip6 crowdsec6 "$setname" 2>/dev/null | grep -oE '([0-9a-fA-F:]+:+)+[0-9a-fA-F]+' | wc -l)
			case "$setname" in
				*-CAPI) ipv6_capi=$((ipv6_capi + count)) ;;
				*-cscli) ipv6_cscli=$((ipv6_cscli + count)) ;;
			esac
		done
	fi
	json_close_array

	# Get rules count
	local ipv4_rules=0
	local ipv6_rules=0
	if [ "$ipv4_exists" = "1" ]; then
		ipv4_rules=$(nft list table ip crowdsec 2>/dev/null | grep -c "rule" || echo "0")
	fi
	if [ "$ipv6_exists" = "1" ]; then
		ipv6_rules=$(nft list table ip6 crowdsec6 2>/dev/null | grep -c "rule" || echo "0")
	fi
	json_add_int "ipv4_rules_count" "$ipv4_rules"
	json_add_int "ipv6_rules_count" "$ipv6_rules"

	# Add counts by origin
	json_add_int "ipv4_capi_count" "$ipv4_capi"
	json_add_int "ipv4_cscli_count" "$ipv4_cscli"
	json_add_int "ipv4_total_count" "$((ipv4_capi + ipv4_cscli + ipv4_other))"
	json_add_int "ipv6_capi_count" "$ipv6_capi"
	json_add_int "ipv6_cscli_count" "$ipv6_cscli"
	json_add_int "ipv6_total_count" "$((ipv6_capi + ipv6_cscli))"

	# Firewall Health Check
	json_add_object "firewall_health"

	# Check bouncer process
	local bouncer_running=0
	if pgrep cs-firewall-bouncer >/dev/null 2>&1; then
		bouncer_running=1
	fi
	json_add_boolean "bouncer_running" "$bouncer_running"

	# Check UCI config
	local uci_enabled=0
	local uci_api_key=""
	if uci -q get crowdsec.bouncer.enabled >/dev/null 2>&1; then
		[ "$(uci -q get crowdsec.bouncer.enabled)" = "1" ] && uci_enabled=1
	fi
	uci_api_key=$(uci -q get crowdsec.bouncer.api_key 2>/dev/null)
	json_add_boolean "uci_enabled" "$uci_enabled"
	json_add_boolean "api_key_configured" "$([ -n "$uci_api_key" ] && [ "$uci_api_key" != "API_KEY" ] && echo 1 || echo 0)"

	# Check chains are hooked (input/forward)
	local input_hooked=0
	local forward_hooked=0
	if [ "$ipv4_exists" = "1" ]; then
		nft list table ip crowdsec 2>/dev/null | grep -q "hook input" && input_hooked=1
		nft list table ip crowdsec 2>/dev/null | grep -q "hook forward" && forward_hooked=1
	fi
	json_add_boolean "input_chain_hooked" "$input_hooked"
	json_add_boolean "forward_chain_hooked" "$forward_hooked"

	# Check if sets have timeout flag (required for auto-expiry)
	local sets_have_timeout=0
	if [ "$ipv4_exists" = "1" ]; then
		nft list set ip crowdsec crowdsec-blacklists 2>/dev/null | grep -q "flags timeout" && sets_have_timeout=1
	fi
	json_add_boolean "sets_have_timeout" "$sets_have_timeout"

	# Check decisions sync (compare cscli decisions count vs nftables)
	local cscli_decisions=0
	local nft_elements=0
	local capi_elements=0
	local sync_ok=0
	if command -v cscli >/dev/null 2>&1; then
		cscli_decisions=$(cscli decisions list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l || echo "0")
	fi
	nft_elements=$((ipv4_capi + ipv4_cscli + ipv4_other + ipv6_capi + ipv6_cscli))
	capi_elements=$((ipv4_capi + ipv6_capi))
	# Sync is OK if:
	# - Local decisions exist and firewall has entries
	# - No local decisions but CAPI blocklists are active
	# - Both local decisions and firewall are empty
	[ "$cscli_decisions" -gt 0 ] && [ "$nft_elements" -gt 0 ] && sync_ok=1
	[ "$cscli_decisions" -eq 0 ] && [ "$capi_elements" -gt 0 ] && sync_ok=1
	[ "$cscli_decisions" -eq 0 ] && [ "$nft_elements" -eq 0 ] && sync_ok=1
	json_add_int "cscli_decisions_count" "$cscli_decisions"
	json_add_int "nft_elements_count" "$nft_elements"
	json_add_int "capi_elements_count" "$capi_elements"
	json_add_boolean "decisions_synced" "$sync_ok"

	# Overall health status
	local health_status="ok"
	local health_issues=""
	if [ "$bouncer_running" != "1" ]; then
		health_status="error"
		health_issues="Bouncer not running; "
	fi
	if [ "$uci_enabled" != "1" ]; then
		health_status="warning"
		health_issues="${health_issues}Bouncer not enabled in UCI; "
	fi
	if [ "$ipv4_exists" != "1" ]; then
		health_status="error"
		health_issues="${health_issues}IPv4 table missing; "
	fi
	if [ "$input_hooked" != "1" ] && [ "$forward_hooked" != "1" ]; then
		health_status="error"
		health_issues="${health_issues}No chains hooked; "
	fi
	if [ "$sync_ok" != "1" ]; then
		health_status="warning"
		health_issues="${health_issues}Decisions not synced to firewall; "
	fi
	json_add_string "status" "$health_status"
	json_add_string "issues" "$health_issues"

	json_close_object

	json_dump
}

# Check if wizard should be shown (first-time setup detection)
check_wizard_needed() {
	json_init

	# Check if bouncer is configured
	local bouncer_configured=0
	if uci -q get crowdsec.bouncer.enabled >/dev/null 2>&1; then
		bouncer_configured=1
	fi

	# Check if collections are installed
	local collections_installed=0
	if [ -x "$CSCLI" ]; then
		if run_cscli collections list 2>/dev/null | grep -q "INSTALLED"; then
			collections_installed=1
		fi
	fi

	# Show wizard if not configured
	local show_wizard=0
	if [ "$bouncer_configured" = "0" ] || [ "$collections_installed" = "0" ]; then
		show_wizard=1
	fi

	json_add_boolean "show_wizard" "$show_wizard"
	json_add_boolean "bouncer_configured" "$bouncer_configured"
	json_add_boolean "collections_installed" "$collections_installed"

	json_dump
}

# Get wizard initial state
get_wizard_state() {
	json_init

	# Get collections count
	local collections_count=0
	if [ -x "$CSCLI" ]; then
		collections_count=$(run_cscli collections list 2>/dev/null | grep -c "INSTALLED" || echo "0")
	fi

	json_add_int "collections_count" "$collections_count"

	json_dump
}

# Repair LAPI - auto-fix common configuration issues
repair_lapi() {
	json_init
	local steps_done=""
	local errors=""
	local config_file="/etc/crowdsec/config.yaml"
	local creds_file="/etc/crowdsec/local_api_credentials.yaml"
	local db_file="/srv/crowdsec/data/crowdsec.db"
	local capi_creds="/etc/crowdsec/online_api_credentials.yaml"

	secubox_log "Starting LAPI repair (simplified)..."

	# Step 1: Stop CrowdSec
	/etc/init.d/crowdsec stop >/dev/null 2>&1
	sleep 2
	killall crowdsec 2>/dev/null
	sleep 1
	steps_done="Stopped; "

	# Step 2: Create required directories
	mkdir -p /srv/crowdsec/data 2>/dev/null
	mkdir -p /etc/crowdsec/hub 2>/dev/null
	mkdir -p /etc/crowdsec/acquis.d 2>/dev/null
	chmod 755 /srv/crowdsec/data 2>/dev/null
	steps_done="${steps_done}Dirs; "

	# Step 3: Fix config paths
	if [ -f "$config_file" ]; then
		sed -i 's|^\(\s*\)data_dir:.*|\1data_dir: /srv/crowdsec/data/|' "$config_file" 2>/dev/null
		sed -i 's|^\(\s*\)db_path:.*|\1db_path: /srv/crowdsec/data/crowdsec.db|' "$config_file" 2>/dev/null
		steps_done="${steps_done}Config fixed; "
	else
		errors="${errors}No config file; "
	fi

	# Step 4: Reset LAPI credentials (delete old ones)
	rm -f "$creds_file" 2>/dev/null
	steps_done="${steps_done}Creds cleared; "

	# Step 5: Reset database (delete it, CrowdSec will recreate)
	rm -f "$db_file" 2>/dev/null
	steps_done="${steps_done}DB reset; "

	# Step 6: Start CrowdSec via init (will auto-create DB and register machine)
	/etc/init.d/crowdsec start >/dev/null 2>&1
	sleep 5
	steps_done="${steps_done}Started; "

	# Step 7: Check if CrowdSec is running
	local lapi_ok=0
	if pgrep crowdsec >/dev/null 2>&1; then
		steps_done="${steps_done}Running; "

		# Step 8: Register machine if credentials don't exist
		if [ ! -s "$creds_file" ] || ! grep -q "password:" "$creds_file" 2>/dev/null; then
			secubox_log "Registering local machine..."
			if "$CSCLI" machines add -a -f "$creds_file" 2>/dev/null; then
				steps_done="${steps_done}Machine registered; "
				# Restart to pick up new credentials
				/etc/init.d/crowdsec restart >/dev/null 2>&1
				sleep 3
			else
				errors="${errors}Machine registration failed; "
			fi
		fi

		# Step 9: Verify LAPI is working
		if run_with_timeout 5 "$CSCLI" lapi status >/dev/null 2>&1; then
			lapi_ok=1
			steps_done="${steps_done}LAPI OK; "
		else
			errors="${errors}LAPI check failed; "
		fi
	else
		errors="${errors}CrowdSec not running; "
		local log_err=""
		log_err=$(tail -5 /var/log/crowdsec.log 2>/dev/null | grep -i "fatal\|error" | head -1)
		[ -n "$log_err" ] && errors="${errors}Log: ${log_err}; "
	fi

	# Step 10: Try to connect CAPI if LAPI works (don't delete existing CAPI creds)
	if [ "$lapi_ok" = "1" ]; then
		local capi_ok=0
		# Check if CAPI is already working
		local capi_status=""
		capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1)
		if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then
			capi_ok=1
			steps_done="${steps_done}CAPI OK; "
		elif [ ! -f "$capi_creds" ]; then
			# No CAPI credentials - try to register (safe)
			if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then
				capi_ok=1
				steps_done="${steps_done}CAPI registered; "
			else
				errors="${errors}CAPI registration failed (will work in local mode); "
			fi
		else
			# CAPI credentials exist but not working - don't delete them!
			steps_done="${steps_done}CAPI creds preserved; "
		fi
	fi

	# Final result
	if [ "$lapi_ok" = "1" ]; then
		json_add_boolean "success" 1
		json_add_string "message" "LAPI repaired successfully"
		json_add_string "steps" "$steps_done"
		secubox_log "LAPI repair successful: $steps_done"
	else
		json_add_boolean "success" 0
		json_add_string "error" "LAPI repair incomplete"
		json_add_string "steps" "$steps_done"
		json_add_string "errors" "$errors"
		secubox_log "LAPI repair failed: $errors"
	fi

	json_dump
}

# Repair CAPI - fix Central API registration issues
repair_capi() {
	json_init
	local steps_done=""
	local errors=""
	local capi_creds="/etc/crowdsec/online_api_credentials.yaml"

	secubox_log "Starting CAPI repair..."

	# Check if CrowdSec and LAPI are running
	if ! pgrep crowdsec >/dev/null 2>&1; then
		json_add_boolean "success" 0
		json_add_string "error" "CrowdSec is not running. Start CrowdSec first."
		json_dump
		return
	fi

	if ! run_with_timeout 5 "$CSCLI" lapi status >/dev/null 2>&1; then
		json_add_boolean "success" 0
		json_add_string "error" "LAPI is not available. Run LAPI repair first."
		json_dump
		return
	fi

	# Check current CAPI status
	local capi_status=""
	capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1)
	if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then
		steps_done="${steps_done}CAPI already working; "
		json_add_boolean "success" 1
		json_add_string "message" "CAPI is already connected"
		json_add_string "steps" "$steps_done"
		json_dump
		return
	fi

	# Step 1: Check credentials and register only if needed
	# IMPORTANT: Never delete existing credentials - this triggers rate limiting
	if [ -f "$capi_creds" ]; then
		steps_done="${steps_done}Credentials exist (preserved); "
		secubox_log "CAPI credentials exist, trying to reconnect..."
		# Try to reconnect with existing credentials by restarting crowdsec
		/etc/init.d/crowdsec restart >/dev/null 2>&1
		sleep 3
	else
		# No credentials - safe to register
		secubox_log "No CAPI credentials, registering..."
		local reg_output=""
		reg_output=$(run_with_timeout 20 "$CSCLI" capi register 2>&1)
		if [ $? -eq 0 ]; then
			steps_done="${steps_done}CAPI registered; "
		else
			# Check if error is about already registered
			if echo "$reg_output" | grep -qi "already registered"; then
				steps_done="${steps_done}Already registered; "
			else
				errors="${errors}Registration failed: ${reg_output}; "
			fi
		fi
	fi

	# Step 3: Verify CAPI connection
	sleep 2
	capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1)
	if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then
		steps_done="${steps_done}CAPI connected; "
		json_add_boolean "success" 1
		json_add_string "message" "CAPI repaired successfully"
		json_add_string "steps" "$steps_done"
		secubox_log "CAPI repair successful: $steps_done"
	else
		json_add_boolean "success" 0
		json_add_string "error" "CAPI repair incomplete"
		json_add_string "steps" "$steps_done"
		json_add_string "errors" "$errors"
		json_add_string "capi_output" "$capi_status"
		secubox_log "CAPI repair failed: $errors"
	fi

	json_dump
}

# Reset wizard - clean up for fresh start
reset_wizard() {
	json_init
	local steps_done=""
	local errors=""

	secubox_log "Starting CrowdSec wizard reset (recovery mode)..."

	# Step 1: Stop services
	/etc/init.d/crowdsec-firewall-bouncer stop >/dev/null 2>&1
	steps_done="${steps_done}Stopped firewall bouncer; "

	# Step 2: Delete existing bouncer registration
	if [ -x "$CSCLI" ]; then
		run_cscli bouncers delete "crowdsec-firewall-bouncer" >/dev/null 2>&1
		steps_done="${steps_done}Deleted bouncer registration; "

		# Also try database cleanup
		sqlite3 /srv/crowdsec/data/crowdsec.db "DELETE FROM bouncers WHERE name='crowdsec-firewall-bouncer';" 2>/dev/null
	fi

	# Step 3: Clear UCI bouncer config
	uci -q delete crowdsec.bouncer 2>/dev/null
	uci commit crowdsec 2>/dev/null
	steps_done="${steps_done}Cleared UCI config; "

	# Step 4: Remove bouncer config file
	rm -f /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml 2>/dev/null
	steps_done="${steps_done}Removed bouncer config file; "

	# Step 5: Reset nftables rules
	if command -v nft >/dev/null 2>&1; then
		nft delete table ip crowdsec 2>/dev/null
		nft delete table ip6 crowdsec6 2>/dev/null
		steps_done="${steps_done}Cleared nftables rules; "
	fi

	json_add_boolean "success" 1
	json_add_string "message" "Wizard reset completed - ready for fresh setup"
	json_add_string "steps" "$steps_done"
	secubox_log "Wizard reset completed: $steps_done"

	json_dump
}

# Console enrollment status
get_console_status() {
	json_init
	check_cscli

	local enrolled=0
	local name=""
	local company=""

	# Check if console is enrolled by checking if there's a console config
	if [ -f "/etc/crowdsec/console.yaml" ]; then
		# Check if the console is actually configured (has credentials)
		if grep -q "share_manual_decisions\|share_tainted\|share_context" /etc/crowdsec/console.yaml 2>/dev/null; then
			enrolled=1
		fi
	fi

	# Try to get console status from cscli
	local console_status
	console_status=$(run_cscli console status 2>/dev/null)
	if [ -n "$console_status" ]; then
		# Check if enrolled by looking for "Enrolled" or similar in output
		if echo "$console_status" | grep -qi "enrolled\|connected\|active"; then
			enrolled=1
		fi
		# Extract name if present
		name=$(echo "$console_status" | grep -i "name\|machine" | head -1 | awk -F: '{print $2}' | xargs 2>/dev/null)
	fi

	json_add_boolean "enrolled" "$enrolled"
	json_add_string "name" "$name"

	# Get share settings if enrolled
	if [ "$enrolled" = "1" ] && [ -f "/etc/crowdsec/console.yaml" ]; then
		local share_manual=$(grep "share_manual_decisions:" /etc/crowdsec/console.yaml 2>/dev/null | awk '{print $2}')
		local share_tainted=$(grep "share_tainted:" /etc/crowdsec/console.yaml 2>/dev/null | awk '{print $2}')
		local share_context=$(grep "share_context:" /etc/crowdsec/console.yaml 2>/dev/null | awk '{print $2}')

		json_add_boolean "share_manual_decisions" "$([ \"$share_manual\" = \"true\" ] && echo 1 || echo 0)"
		json_add_boolean "share_tainted" "$([ \"$share_tainted\" = \"true\" ] && echo 1 || echo 0)"
		json_add_boolean "share_context" "$([ \"$share_context\" = \"true\" ] && echo 1 || echo 0)"
	fi

	json_dump
}

# Console enroll
console_enroll() {
	local key="$1"
	local name="$2"

	json_init
	check_cscli

	if [ -z "$key" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Enrollment key is required"
		json_dump
		return
	fi

	secubox_log "Enrolling CrowdSec Console with key..."

	# Step 0: Ensure CAPI is registered (prerequisite for console enrollment)
	local capi_ok=0
	local capi_status=""
	local capi_creds="/etc/crowdsec/online_api_credentials.yaml"

	capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1)
	if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then
		capi_ok=1
		secubox_log "CAPI already connected"
	else
		# CAPI not connected - check if credentials exist
		if [ -f "$capi_creds" ]; then
			# Credentials exist but CAPI not working - try restarting CrowdSec
			secubox_log "CAPI credentials exist but not connected, restarting CrowdSec..."
			/etc/init.d/crowdsec restart >/dev/null 2>&1
			sleep 3
			# Check again after restart
			capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1)
			if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then
				capi_ok=1
				secubox_log "CAPI connected after restart"
			else
				# Still not working but DO NOT delete credentials (rate limit protection)
				secubox_log "CAPI still not connected - credentials preserved to avoid rate limit"
			fi
		else
			# No credentials - safe to register
			secubox_log "No CAPI credentials, attempting registration..."
			if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then
				capi_ok=1
				secubox_log "CAPI registration successful"
			fi
		fi
	fi

	if [ "$capi_ok" = "0" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "CAPI registration failed. Please run LAPI repair first."
		json_dump
		return
	fi

	# Build enroll command
	local enroll_cmd="run_cscli console enroll $key"
	if [ -n "$name" ]; then
		enroll_cmd="$enroll_cmd --name \"$name\""
	fi

	# Run enrollment
	local output
	output=$(eval "$enroll_cmd" 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Successfully enrolled in CrowdSec Console"
		json_add_string "output" "$output"
		secubox_log "Console enrollment successful"

		# Enable sharing options by default
		run_cscli console enable share_manual_decisions >/dev/null 2>&1
		run_cscli console enable share_tainted >/dev/null 2>&1
		run_cscli console enable share_context >/dev/null 2>&1

		# Restart CrowdSec to activate enrollment
		# Note: User must validate enrollment on app.crowdsec.net first
		secubox_log "Restarting CrowdSec to apply enrollment..."
		/etc/init.d/crowdsec restart >/dev/null 2>&1 &
		json_add_boolean "restart_triggered" 1
	else
		json_add_boolean "success" 0
		json_add_string "error" "Enrollment failed"
		json_add_string "output" "$output"
		secubox_log "Console enrollment failed: $output"
	fi

	json_dump
}

# Console disable (unenroll)
console_disable() {
	json_init
	check_cscli

	secubox_log "Disabling CrowdSec Console enrollment..."

	# Disable all sharing
	run_cscli console disable share_manual_decisions >/dev/null 2>&1
	run_cscli console disable share_tainted >/dev/null 2>&1
	run_cscli console disable share_context >/dev/null 2>&1

	# Remove console config
	if [ -f "/etc/crowdsec/console.yaml" ]; then
		rm -f /etc/crowdsec/console.yaml
	fi

	json_add_boolean "success" 1
	json_add_string "message" "Console enrollment disabled"
	secubox_log "Console enrollment disabled"

	json_dump
}

# Configure log acquisition settings
configure_acquisition() {
	local syslog_enabled="$1"
	local firewall_enabled="$2"
	local ssh_enabled="$3"
	local http_enabled="$4"
	local syslog_path="$5"

	json_init
	local steps_done=""
	local errors=""

	secubox_log "Configuring CrowdSec log acquisition..."

	# Step 1: Ensure acquisition section exists in UCI
	if ! uci -q get crowdsec.acquisition >/dev/null 2>&1; then
		uci set crowdsec.acquisition='acquisition'
		steps_done="${steps_done}Created acquisition section; "
	fi

	# Step 2: Set acquisition options
	uci set crowdsec.acquisition.syslog_enabled="${syslog_enabled:-1}"
	uci set crowdsec.acquisition.firewall_enabled="${firewall_enabled:-1}"
	uci set crowdsec.acquisition.ssh_enabled="${ssh_enabled:-1}"
	uci set crowdsec.acquisition.http_enabled="${http_enabled:-0}"
	if [ -n "$syslog_path" ]; then
		uci set crowdsec.acquisition.syslog_path="$syslog_path"
	fi
	uci commit crowdsec
	steps_done="${steps_done}Updated UCI settings; "

	# Step 3: Note on Dropbear SSH logging
	# Dropbear 2024.86 does NOT support -v flag or verbose UCI option
	# Auth failures are detected via auth-monitor.sh parsing syslog messages
	if [ "$ssh_enabled" = "1" ]; then
		steps_done="${steps_done}SSH detection enabled via syslog monitoring; "
	fi

	# Enable uhttpd syslog for HTTP auth logging
	if [ "$http_enabled" = "1" ]; then
		if uci -q get uhttpd.main >/dev/null 2>&1; then
			uci set uhttpd.main.syslog='1'
			uci commit uhttpd
			/etc/init.d/uhttpd restart >/dev/null 2>&1
			steps_done="${steps_done}Enabled uhttpd syslog; "
		fi
	fi

	# Step 4: Generate acquisition YAML files
	# OpenWrt uses logread command instead of /var/log/messages by default
	# All syslog entries (SSH, firewall, system) go through the same log stream
	# We create ONE unified acquisition file to avoid multiple logread processes
	local acquis_dir="/etc/crowdsec/acquis.d"
	mkdir -p "$acquis_dir"

	# Remove old separate acquisition files if they exist
	rm -f "$acquis_dir/openwrt-syslog.yaml" 2>/dev/null
	rm -f "$acquis_dir/openwrt-firewall.yaml" 2>/dev/null
	rm -f "$acquis_dir/openwrt-dropbear.yaml" 2>/dev/null

	# Create unified syslog acquisition if any syslog-based source is enabled
	# SSH, firewall, and system logs all go through /var/log/messages
	# NOTE: CrowdSec doesn't support "source: command" - must use file-based acquisition
	if [ "$syslog_enabled" = "1" ] || [ "$firewall_enabled" = "1" ] || [ "$ssh_enabled" = "1" ]; then
		# Ensure busybox syslog writes to file (required for CrowdSec)
		if uci -q get system.@system[0] >/dev/null 2>&1; then
			uci set system.@system[0].log_file='/var/log/messages'
			uci set system.@system[0].log_size='512'
			uci commit system
			/etc/init.d/log restart >/dev/null 2>&1
		fi
		cat > "$acquis_dir/openwrt-unified.yaml" << 'YAML'
# OpenWrt Unified Syslog Acquisition
# Auto-generated by SecuBox CrowdSec Wizard
# Reads from /var/log/messages (busybox syslog)
# Covers: system logs, SSH/Dropbear/OpenSSH, firewall (iptables/nftables)
filenames:
  - /var/log/messages
labels:
  type: syslog
YAML
		local enabled_sources=""
		[ "$syslog_enabled" = "1" ] && enabled_sources="${enabled_sources}system "
		[ "$ssh_enabled" = "1" ] && enabled_sources="${enabled_sources}SSH "
		[ "$firewall_enabled" = "1" ] && enabled_sources="${enabled_sources}firewall "
		steps_done="${steps_done}Configured syslog to file and created acquisition (${enabled_sources}); "
	else
		rm -f "$acquis_dir/openwrt-unified.yaml"
		steps_done="${steps_done}Disabled syslog acquisition; "
	fi

	# Enable/disable HTTP log acquisition (separate file-based source)
	if [ "$http_enabled" = "1" ]; then
		# Check if log files exist
		if [ -f "/var/log/uhttpd.log" ]; then
			cat > "$acquis_dir/openwrt-http.yaml" << 'YAML'
# OpenWrt uHTTPd Web Server Log Acquisition
# Auto-generated by SecuBox CrowdSec Wizard
filenames:
  - /var/log/uhttpd.log
labels:
  type: nginx
YAML
		elif [ -f "/var/log/nginx/access.log" ]; then
			cat > "$acquis_dir/openwrt-http.yaml" << 'YAML'
# OpenWrt nginx Web Server Log Acquisition
# Auto-generated by SecuBox CrowdSec Wizard
filenames:
  - /var/log/nginx/access.log
labels:
  type: nginx
YAML
		else
			# Fallback - try both locations
			cat > "$acquis_dir/openwrt-http.yaml" << 'YAML'
# OpenWrt Web Server Log Acquisition
# Auto-generated by SecuBox CrowdSec Wizard
filenames:
  - /var/log/uhttpd.log
  - /var/log/nginx/access.log
labels:
  type: nginx
YAML
		fi
		steps_done="${steps_done}Created HTTP acquisition; "
	else
		rm -f "$acquis_dir/openwrt-http.yaml"
		rm -f "$acquis_dir/openwrt-uhttpd.yaml" 2>/dev/null
		steps_done="${steps_done}Disabled HTTP acquisition; "
	fi

	# Step 4: Restart CrowdSec to apply acquisition changes
	if /etc/init.d/crowdsec reload >/dev/null 2>&1; then
		steps_done="${steps_done}Reloaded CrowdSec"
	else
		# Fallback to restart if reload fails
		/etc/init.d/crowdsec restart >/dev/null 2>&1
		steps_done="${steps_done}Restarted CrowdSec"
	fi

	json_add_boolean "success" 1
	json_add_string "message" "Acquisition configuration completed"
	json_add_string "steps" "$steps_done"
	secubox_log "Acquisition configuration completed: $steps_done"

	json_dump
}

# Get realtime acquisition metrics with rates
get_acquisition_metrics() {
	check_cscli
	json_init

	# Get raw metrics from cscli
	local metrics_output
	metrics_output=$(run_cscli metrics -o json 2>/dev/null)

	if [ -z "$metrics_output" ]; then
		json_add_boolean "available" 0
		json_add_string "error" "Metrics not available"
		json_dump
		return
	fi

	json_add_boolean "available" 1
	json_add_int "timestamp" "$(date +%s)"

	# Parse acquisition sources from metrics
	# Store metrics in temp file for parsing
	local tmp_file="/tmp/crowdsec_metrics.$$"
	echo "$metrics_output" > "$tmp_file"

	# Extract acquisition metrics
	json_add_array "sources"

	# Use jsonfilter to extract acquisition data
	# Format: {"source": "file:/var/log/messages", "lines_read": 123, "lines_parsed": 100, ...}
	local sources
	sources=$(cat "$tmp_file" | jsonfilter -e '@.acquisition' 2>/dev/null)

	if [ -n "$sources" ]; then
		# Parse each source
		for source in $(echo "$metrics_output" | jsonfilter -e '@.acquisition.*' 2>/dev/null | head -20); do
			json_add_object ""
			json_add_string "source" "$source"
			json_close_object
		done
	fi

	json_close_array

	# Get overall stats
	local total_read=0
	local total_parsed=0
	local total_unparsed=0
	local total_buckets=0

	# Parse acquisition stats using awk
	if [ -f "$tmp_file" ]; then
		# Extract line counts
		total_read=$(cat "$tmp_file" | grep -o '"lines_read":[0-9]*' | grep -o '[0-9]*' | awk '{sum+=$1} END {print sum+0}')
		total_parsed=$(cat "$tmp_file" | grep -o '"lines_parsed":[0-9]*' | grep -o '[0-9]*' | awk '{sum+=$1} END {print sum+0}')
		total_unparsed=$(cat "$tmp_file" | grep -o '"lines_unparsed":[0-9]*' | grep -o '[0-9]*' | awk '{sum+=$1} END {print sum+0}')
		total_buckets=$(cat "$tmp_file" | grep -o '"lines_poured_to_bucket":[0-9]*' | grep -o '[0-9]*' | awk '{sum+=$1} END {print sum+0}')
	fi

	json_add_int "total_lines_read" "${total_read:-0}"
	json_add_int "total_lines_parsed" "${total_parsed:-0}"
	json_add_int "total_lines_unparsed" "${total_unparsed:-0}"
	json_add_int "total_buckets" "${total_buckets:-0}"

	# Calculate parse rate
	if [ "$total_read" -gt 0 ]; then
		local parse_rate=$((total_parsed * 100 / total_read))
		json_add_int "parse_rate" "$parse_rate"
	else
		json_add_int "parse_rate" 0
	fi

	# Check active acquisition files
	json_add_array "active_files"
	local acquis_dir="/etc/crowdsec/acquis.d"
	if [ -d "$acquis_dir" ]; then
		for f in "$acquis_dir"/*.yaml; do
			if [ -f "$f" ]; then
				json_add_string "" "$(basename "$f" .yaml)"
			fi
		done
	fi
	json_close_array

	# Clean up
	rm -f "$tmp_file"

	json_dump
}

# Get current acquisition configuration
get_acquisition_config() {
	json_init

	# Get values from UCI
	local syslog_enabled=$(uci -q get crowdsec.acquisition.syslog_enabled || echo "1")
	local firewall_enabled=$(uci -q get crowdsec.acquisition.firewall_enabled || echo "1")
	local ssh_enabled=$(uci -q get crowdsec.acquisition.ssh_enabled || echo "1")
	local http_enabled=$(uci -q get crowdsec.acquisition.http_enabled || echo "0")
	local syslog_path=$(uci -q get crowdsec.acquisition.syslog_path || echo "/var/log/messages")

	json_add_string "syslog_enabled" "$syslog_enabled"
	json_add_string "firewall_enabled" "$firewall_enabled"
	json_add_string "ssh_enabled" "$ssh_enabled"
	json_add_string "http_enabled" "$http_enabled"
	json_add_string "syslog_path" "$syslog_path"

	# Check which acquisition files exist
	local acquis_dir="/etc/crowdsec/acquis.d"
	local unified_exists=0
	local http_exists=0
	[ -f "$acquis_dir/openwrt-unified.yaml" ] && unified_exists=1
	[ -f "$acquis_dir/openwrt-http.yaml" ] && http_exists=1

	json_add_boolean "unified_file_exists" "$unified_exists"
	json_add_boolean "http_file_exists" "$http_exists"

	json_dump
}

# Service control (start/stop/restart/reload)
service_control() {
	local action="$1"
	json_init

	case "$action" in
		start|stop|restart|reload)
			secubox_log "CrowdSec service $action requested"
			local output
			output=$(/etc/init.d/crowdsec "$action" 2>&1)
			local result=$?
			sleep 2

			# Check if service is running after action
			local running=0
			if pgrep crowdsec >/dev/null 2>&1; then
				running=1
			fi

			if [ "$result" -eq 0 ]; then
				json_add_boolean "success" 1
				json_add_string "action" "$action"
				json_add_boolean "running" "$running"
				json_add_string "message" "Service $action completed"
			else
				json_add_boolean "success" 0
				json_add_string "error" "Service $action failed"
				json_add_string "output" "$output"
			fi
			;;
		*)
			json_add_boolean "success" 0
			json_add_string "error" "Invalid action. Use: start, stop, restart, reload"
			;;
	esac

	json_dump
}

# Complete health check for dashboard
get_health_check() {
	json_init

	# CrowdSec running status
	local cs_running=0
	if pgrep crowdsec >/dev/null 2>&1; then
		cs_running=1
	fi
	json_add_boolean "crowdsec_running" "$cs_running"

	# Version
	local version=""
	if [ -x "$CSCLI" ]; then
		version=$(run_cscli version 2>/dev/null | grep "version:" | awk '{print $2}')
	fi
	json_add_string "version" "${version:-unknown}"

	# LAPI status
	local lapi_status="unavailable"
	local lapi_url="http://127.0.0.1:8080"
	if [ -x "$CSCLI" ]; then
		if run_with_timeout 5 "$CSCLI" lapi status >/dev/null 2>&1; then
			lapi_status="available"
		fi
	fi
	json_add_string "lapi_status" "$lapi_status"
	json_add_string "lapi_url" "$lapi_url"

	# CAPI status - parse cscli capi status output
	local capi_status="disconnected"
	local capi_enrolled=0
	local capi_subscription=""
	local sharing_signals=0
	local pulling_blocklist=0
	local pulling_console=0

	if [ -x "$CSCLI" ]; then
		local capi_output=""
		capi_output=$(run_with_timeout 10 "$CSCLI" capi status 2>&1)

		if echo "$capi_output" | grep -qi "You can successfully interact with Central API"; then
			capi_status="connected"
		fi
		if echo "$capi_output" | grep -qi "enrolled in the console"; then
			capi_enrolled=1
		fi
		if echo "$capi_output" | grep -qi "COMMUNITY"; then
			capi_subscription="COMMUNITY"
		elif echo "$capi_output" | grep -qi "PRO"; then
			capi_subscription="PRO"
		fi
		if echo "$capi_output" | grep -qi "Sharing signals is enabled"; then
			sharing_signals=1
		fi
		if echo "$capi_output" | grep -qi "Pulling community blocklist is enabled"; then
			pulling_blocklist=1
		fi
		if echo "$capi_output" | grep -qi "Pulling blocklists from the console is enabled"; then
			pulling_console=1
		fi
	fi

	json_add_string "capi_status" "$capi_status"
	json_add_boolean "capi_enrolled" "$capi_enrolled"
	json_add_string "capi_subscription" "$capi_subscription"
	json_add_boolean "sharing_signals" "$sharing_signals"
	json_add_boolean "pulling_blocklist" "$pulling_blocklist"
	json_add_boolean "pulling_console" "$pulling_console"

	# Machine info
	local machine_id=""
	local machine_version=""
	if [ -x "$CSCLI" ]; then
		local machines_output=""
		machines_output=$(run_cscli machines list -o json 2>/dev/null)
		if [ -n "$machines_output" ] && [ "$machines_output" != "null" ]; then
			machine_id=$(echo "$machines_output" | jsonfilter -e '@[0].machineId' 2>/dev/null)
			machine_version=$(echo "$machines_output" | jsonfilter -e '@[0].version' 2>/dev/null)
		fi
	fi
	json_add_string "machine_id" "${machine_id:-localhost}"
	json_add_string "machine_version" "$machine_version"

	# Bouncer count
	local bouncer_count=0
	if [ -x "$CSCLI" ]; then
		bouncer_count=$(run_cscli bouncers list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
	fi
	json_add_int "bouncer_count" "${bouncer_count:-0}"

	# Total decisions count
	local decisions_count=0
	if [ -x "$CSCLI" ]; then
		decisions_count=$(run_cscli decisions list -o json 2>/dev/null | jsonfilter -e '@[*].decisions[*]' 2>/dev/null | wc -l)
	fi
	json_add_int "decisions_count" "${decisions_count:-0}"

	json_dump
}

# Get CAPI blocklist metrics (decisions by origin and reason)
get_capi_metrics() {
	json_init

	if [ ! -x "$CSCLI" ]; then
		json_add_boolean "available" 0
		json_add_string "error" "cscli not found"
		json_dump
		return
	fi

	# Get all decisions
	local decisions_output=""
	decisions_output=$(run_cscli decisions list -o json 2>/dev/null)

	if [ -z "$decisions_output" ] || [ "$decisions_output" = "null" ]; then
		json_add_boolean "available" 1
		json_add_int "total_capi" 0
		json_add_int "total_local" 0
		json_add_string "breakdown" "[]"
		json_dump
		return
	fi

	json_add_boolean "available" 1

	# Count by origin
	local capi_count=0
	local local_count=0

	# Parse decisions and count by origin
	# The structure is: [{decisions: [...], ...}, ...]
	# We need to count decisions where origin = "CAPI" or "crowdsec"

	# Use a temp file for aggregation
	local tmp_file="/tmp/capi_metrics.$$"

	# Extract all decisions with their origin and scenario
	echo "$decisions_output" | jsonfilter -e '@[*].decisions[*]' 2>/dev/null | while read -r decision; do
		local origin=$(echo "$decisions_output" | jsonfilter -e '@[*].decisions[*].origin' 2>/dev/null | head -1)
		local scenario=$(echo "$decisions_output" | jsonfilter -e '@[*].scenario' 2>/dev/null | head -1)
		echo "$origin|$scenario"
	done > "$tmp_file" 2>/dev/null

	# Count CAPI decisions by scenario using awk
	capi_count=$(echo "$decisions_output" | grep -o '"origin":"CAPI"' 2>/dev/null | wc -l)
	local_count=$(echo "$decisions_output" | grep -o '"origin":"crowdsec"' 2>/dev/null | wc -l)

	json_add_int "total_capi" "${capi_count:-0}"
	json_add_int "total_local" "${local_count:-0}"

	# Build breakdown by scenario for CAPI decisions
	# Parse the JSON more carefully
	json_add_array "breakdown"

	# Extract unique scenarios and their counts from CAPI decisions
	local scenarios=""
	scenarios=$(echo "$decisions_output" | grep -oE '"scenario":"[^"]*"' | sort | uniq -c | sort -rn | head -10)

	echo "$scenarios" | while read -r count scenario; do
		if [ -n "$count" ] && [ -n "$scenario" ]; then
			local name=$(echo "$scenario" | sed 's/"scenario":"//; s/"$//')
			json_add_object ""
			json_add_string "scenario" "$name"
			json_add_int "count" "$count"
			json_close_object
		fi
	done

	json_close_array

	rm -f "$tmp_file" 2>/dev/null

	json_dump
}

# Get available hub items (not installed)
get_hub_available() {
	json_init

	if [ ! -x "$CSCLI" ]; then
		json_add_boolean "available" 0
		json_add_string "error" "cscli not found"
		json_dump
		return
	fi

	json_add_boolean "available" 1

	# Get hub list in JSON format (all items)
	local hub_output=""
	hub_output=$(run_with_timeout 30 "$CSCLI" hub list -a -o json 2>/dev/null)

	if [ -z "$hub_output" ]; then
		json_add_string "collections" "[]"
		json_add_string "parsers" "[]"
		json_add_string "scenarios" "[]"
		json_dump
		return
	fi

	# Output the raw hub data - frontend will parse it
	echo "$hub_output"
}

# Install a hub item (collection, parser, scenario)
install_hub_item() {
	local item_type="$1"
	local item_name="$2"

	json_init

	if [ -z "$item_type" ] || [ -z "$item_name" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Item type and name are required"
		json_dump
		return
	fi

	# Validate item type
	case "$item_type" in
		collection|parser|scenario|postoverflow|context)
			;;
		*)
			json_add_boolean "success" 0
			json_add_string "error" "Invalid item type: $item_type"
			json_dump
			return
			;;
	esac

	if [ ! -x "$CSCLI" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "cscli not found"
		json_dump
		return
	fi

	secubox_log "Installing CrowdSec $item_type: $item_name"

	# Install the item
	local output=""
	output=$(run_with_timeout 60 "$CSCLI" "${item_type}s" install "$item_name" 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Successfully installed $item_type: $item_name"
		json_add_string "output" "$output"
		secubox_log "Installed $item_type: $item_name"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Failed to install $item_type: $item_name"
		json_add_string "output" "$output"
		secubox_log "Failed to install $item_type: $item_name - $output"
	fi

	json_dump
}

# Remove a hub item
remove_hub_item() {
	local item_type="$1"
	local item_name="$2"

	json_init

	if [ -z "$item_type" ] || [ -z "$item_name" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "Item type and name are required"
		json_dump
		return
	fi

	case "$item_type" in
		collection|parser|scenario|postoverflow|context)
			;;
		*)
			json_add_boolean "success" 0
			json_add_string "error" "Invalid item type: $item_type"
			json_dump
			return
			;;
	esac

	if [ ! -x "$CSCLI" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "cscli not found"
		json_dump
		return
	fi

	secubox_log "Removing CrowdSec $item_type: $item_name"

	local output=""
	output=$(run_with_timeout 30 "$CSCLI" "${item_type}s" remove "$item_name" 2>&1)
	local result=$?

	if [ "$result" -eq 0 ]; then
		json_add_boolean "success" 1
		json_add_string "message" "Successfully removed $item_type: $item_name"
		secubox_log "Removed $item_type: $item_name"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Failed to remove $item_type: $item_name"
		json_add_string "output" "$output"
	fi

	json_dump
}

# Get dashboard settings (enrollment key, etc.)
get_settings() {
	json_init

	local enrollment_key=""
	local machine_name=""
	local auto_enroll=""

	# Read from UCI config
	enrollment_key=$(uci -q get crowdsec-dashboard.main.enrollment_key 2>/dev/null)
	machine_name=$(uci -q get crowdsec-dashboard.main.machine_name 2>/dev/null)
	auto_enroll=$(uci -q get crowdsec-dashboard.main.auto_enroll 2>/dev/null)

	json_add_string "enrollment_key" "$enrollment_key"
	json_add_string "machine_name" "$machine_name"
	json_add_string "auto_enroll" "${auto_enroll:-0}"

	json_dump
}

# Save dashboard settings
save_settings() {
	local enrollment_key="$1"
	local machine_name="$2"
	local auto_enroll="$3"

	json_init

	# Ensure config section exists
	uci -q get crowdsec-dashboard.main >/dev/null 2>&1 || {
		uci set crowdsec-dashboard.main=settings
	}

	# Save settings
	[ -n "$enrollment_key" ] && uci set crowdsec-dashboard.main.enrollment_key="$enrollment_key"
	[ -n "$machine_name" ] && uci set crowdsec-dashboard.main.machine_name="$machine_name"
	[ -n "$auto_enroll" ] && uci set crowdsec-dashboard.main.auto_enroll="$auto_enroll"

	if uci commit crowdsec-dashboard 2>/dev/null; then
		json_add_boolean "success" 1
		json_add_string "message" "Settings saved"
		secubox_log "Saved enrollment settings"
	else
		json_add_boolean "success" 0
		json_add_string "error" "Failed to save settings"
	fi

	json_dump
}

# Consolidated overview data for dashboard - single API call optimization
get_overview() {
	json_init

	# Service status (fast - just process checks)
	local cs_running=0
	pgrep crowdsec >/dev/null 2>&1 && cs_running=1
	json_add_string "crowdsec" "$([ "$cs_running" = "1" ] && echo running || echo stopped)"

	local bouncer_running=0
	pgrep -f "crowdsec-firewall-bouncer" >/dev/null 2>&1 && bouncer_running=1
	json_add_string "bouncer" "$([ "$bouncer_running" = "1" ] && echo running || echo stopped)"

	# Version
	local version=""
	[ -x "$CSCLI" ] && version=$("$CSCLI" version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
	json_add_string "version" "${version:-unknown}"

	# Quick stats
	local decisions_count=0
	local alerts_count=0
	local bouncers_count=0

	if [ "$cs_running" = "1" ] && [ -x "$CSCLI" ]; then
		decisions_count=$(run_cscli decisions list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
		alerts_count=$(run_cscli alerts list -o json --since 24h --limit 100 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
		bouncers_count=$(run_cscli bouncers list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
	fi

	json_add_int "total_decisions" "${decisions_count:-0}"
	json_add_int "alerts_24h" "${alerts_count:-0}"
	json_add_int "bouncers" "${bouncers_count:-0}"

	# Top scenarios (from cached/limited alerts)
	local scenarios=""
	if [ "$cs_running" = "1" ]; then
		scenarios=$(run_cscli alerts list -o json --limit 100 2>/dev/null | \
			jsonfilter -e '@[*].scenario' 2>/dev/null | \
			sort | uniq -c | sort -rn | head -5 | \
			awk '{print "{\"scenario\":\"" $2 "\",\"count\":" $1 "}"}' | \
			tr '\n' ',' | sed 's/,$//')
	fi
	json_add_string "top_scenarios_raw" "[$scenarios]"

	# Top countries (from alerts with GeoIP)
	local countries=""
	if [ "$cs_running" = "1" ]; then
		countries=$(run_cscli alerts list -o json --limit 200 2>/dev/null | \
			jsonfilter -e '@[*].source.cn' 2>/dev/null | \
			grep -v '^$' | sort | uniq -c | sort -rn | head -10 | \
			awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \
			tr '\n' ',' | sed 's/,$//')
	fi
	json_add_string "top_countries_raw" "[$countries]"

	# Recent decisions (limited to 10 for display)
	json_add_array "decisions"
	if [ "$cs_running" = "1" ]; then
		run_cscli decisions list -o json 2>/dev/null | \
			jsonfilter -e '@[0]' -e '@[1]' -e '@[2]' -e '@[3]' -e '@[4]' \
			-e '@[5]' -e '@[6]' -e '@[7]' -e '@[8]' -e '@[9]' 2>/dev/null | \
			while IFS= read -r line; do
				[ -n "$line" ] && [ "$line" != "null" ] && json_add_string "" "$line"
			done
	fi
	json_close_array

	# Recent alerts (limited to 8)
	json_add_array "alerts"
	if [ "$cs_running" = "1" ]; then
		run_cscli alerts list -o json --limit 8 2>/dev/null | \
			jsonfilter -e '@[*]' 2>/dev/null | head -8 | \
			while IFS= read -r line; do
				[ -n "$line" ] && json_add_string "" "$line"
			done
	fi
	json_close_array

	# CrowdSec logs (last 30 lines)
	json_add_array "logs"
	if [ -f /var/log/crowdsec.log ]; then
		tail -n 30 /var/log/crowdsec.log 2>/dev/null | while IFS= read -r line; do
			json_add_string "" "$line"
		done
	fi
	json_close_array

	# LAPI status (quick check)
	local lapi_ok=0
	if [ "$cs_running" = "1" ] && grep -qi ":1F90 " /proc/net/tcp 2>/dev/null; then
		lapi_ok=1
	fi
	json_add_string "lapi_status" "$([ "$lapi_ok" = "1" ] && echo available || echo unavailable)"

	# CAPI status (from config check, not live call)
	local capi_enrolled=0
	[ -f /etc/crowdsec/online_api_credentials.yaml ] && capi_enrolled=1
	json_add_boolean "capi_enrolled" "$capi_enrolled"

	json_dump
}

# Main dispatcher
case "$1" in
	list)
		echo '{"get_overview":{},"decisions":{},"alerts":{"limit":"number"},"metrics":{},"bouncers":{},"machines":{},"hub":{},"status":{},"ban":{"ip":"string","duration":"string","reason":"string"},"unban":{"ip":"string"},"stats":{},"secubox_logs":{},"collect_debug":{},"waf_status":{},"metrics_config":{},"configure_metrics":{"enable":"string"},"collections":{},"install_collection":{"collection":"string"},"remove_collection":{"collection":"string"},"update_hub":{},"register_bouncer":{"bouncer_name":"string"},"delete_bouncer":{"bouncer_name":"string"},"firewall_bouncer_status":{},"control_firewall_bouncer":{"action":"string"},"firewall_bouncer_config":{},"update_firewall_bouncer_config":{"key":"string","value":"string"},"nftables_stats":{},"check_wizard_needed":{},"wizard_state":{},"repair_lapi":{},"repair_capi":{},"reset_wizard":{},"console_status":{},"console_enroll":{"key":"string","name":"string"},"console_disable":{},"service_control":{"action":"string"},"configure_acquisition":{"syslog_enabled":"string","firewall_enabled":"string","ssh_enabled":"string","http_enabled":"string","syslog_path":"string"},"acquisition_config":{},"acquisition_metrics":{},"health_check":{},"capi_metrics":{},"hub_available":{},"install_hub_item":{"item_type":"string","item_name":"string"},"remove_hub_item":{"item_type":"string","item_name":"string"},"get_settings":{},"save_settings":{"enrollment_key":"string","machine_name":"string","auto_enroll":"string"}}'
		;;
	call)
		case "$2" in
			decisions)
				get_decisions
				;;
			alerts)
				read -r input
				limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null)
				get_alerts "${limit:-50}"
				;;
			metrics)
				get_metrics
				;;
			bouncers)
				get_bouncers
				;;
			machines)
				get_machines
				;;
			hub)
				get_hub
				;;
			status)
				get_status
				;;
			ban)
				read -r input
				ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
				duration=$(echo "$input" | jsonfilter -e '@.duration' 2>/dev/null)
				reason=$(echo "$input" | jsonfilter -e '@.reason' 2>/dev/null)
				add_ban "$ip" "$duration" "$reason"
				;;
			unban)
				read -r input
				ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
				remove_ban "$ip"
				;;
			stats)
				get_dashboard_stats
				;;
			secubox_logs|crowdsec_logs)
				crowdsec_logs
				;;
			collect_debug)
				collect_debug
				;;
			waf_status)
				get_waf_status
				;;
			metrics_config)
				get_metrics_config
				;;
			configure_metrics)
				read -r input
				enable=$(echo "$input" | jsonfilter -e '@.enable' 2>/dev/null)
				configure_metrics "$enable"
				;;
			collections)
				get_collections
				;;
			install_collection)
				read -r input
				collection=$(echo "$input" | jsonfilter -e '@.collection' 2>/dev/null)
				install_collection "$collection"
				;;
			remove_collection)
				read -r input
				collection=$(echo "$input" | jsonfilter -e '@.collection' 2>/dev/null)
				remove_collection "$collection"
				;;
			update_hub)
				update_hub
				;;
			register_bouncer)
				read -r input
				bouncer_name=$(echo "$input" | jsonfilter -e '@.bouncer_name' 2>/dev/null)
				register_bouncer "$bouncer_name"
				;;
			delete_bouncer)
				read -r input
				bouncer_name=$(echo "$input" | jsonfilter -e '@.bouncer_name' 2>/dev/null)
				delete_bouncer "$bouncer_name"
				;;
			firewall_bouncer_status)
				get_firewall_bouncer_status
				;;
			control_firewall_bouncer)
				read -r input
				action=$(echo "$input" | jsonfilter -e '@.action' 2>/dev/null)
				control_firewall_bouncer "$action"
				;;
			firewall_bouncer_config)
				get_firewall_bouncer_config
				;;
			update_firewall_bouncer_config)
				read -r input
				key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null)
				value=$(echo "$input" | jsonfilter -e '@.value' 2>/dev/null)
				update_firewall_bouncer_config "$key" "$value"
				;;
			nftables_stats)
				get_nftables_stats
				;;
			check_wizard_needed)
				check_wizard_needed
				;;
			wizard_state)
				get_wizard_state
				;;
			repair_lapi)
				repair_lapi
				;;
			repair_capi)
				repair_capi
				;;
			reset_wizard)
				reset_wizard
				;;
			console_status)
				get_console_status
				;;
			console_enroll)
				read -r input
				key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null)
				name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null)
				console_enroll "$key" "$name"
				;;
			console_disable)
				console_disable
				;;
			service_control)
				read -r input
				action=$(echo "$input" | jsonfilter -e '@.action' 2>/dev/null)
				service_control "$action"
				;;
			configure_acquisition)
				read -r input
				syslog_enabled=$(echo "$input" | jsonfilter -e '@.syslog_enabled' 2>/dev/null)
				firewall_enabled=$(echo "$input" | jsonfilter -e '@.firewall_enabled' 2>/dev/null)
				ssh_enabled=$(echo "$input" | jsonfilter -e '@.ssh_enabled' 2>/dev/null)
				http_enabled=$(echo "$input" | jsonfilter -e '@.http_enabled' 2>/dev/null)
				syslog_path=$(echo "$input" | jsonfilter -e '@.syslog_path' 2>/dev/null)
				configure_acquisition "$syslog_enabled" "$firewall_enabled" "$ssh_enabled" "$http_enabled" "$syslog_path"
				;;
			acquisition_config)
				get_acquisition_config
				;;
			acquisition_metrics)
				get_acquisition_metrics
				;;
			health_check)
				get_health_check
				;;
			capi_metrics)
				get_capi_metrics
				;;
			hub_available)
				get_hub_available
				;;
			install_hub_item)
				read -r input
				item_type=$(echo "$input" | jsonfilter -e '@.item_type' 2>/dev/null)
				item_name=$(echo "$input" | jsonfilter -e '@.item_name' 2>/dev/null)
				install_hub_item "$item_type" "$item_name"
				;;
			remove_hub_item)
				read -r input
				item_type=$(echo "$input" | jsonfilter -e '@.item_type' 2>/dev/null)
				item_name=$(echo "$input" | jsonfilter -e '@.item_name' 2>/dev/null)
				remove_hub_item "$item_type" "$item_name"
				;;
			get_settings)
				get_settings
				;;
			save_settings)
				read -r input
				enrollment_key=$(echo "$input" | jsonfilter -e '@.enrollment_key' 2>/dev/null)
				machine_name=$(echo "$input" | jsonfilter -e '@.machine_name' 2>/dev/null)
				auto_enroll=$(echo "$input" | jsonfilter -e '@.auto_enroll' 2>/dev/null)
				save_settings "$enrollment_key" "$machine_name" "$auto_enroll"
				;;
			get_overview)
				get_overview
				;;
			*)
				echo '{"error": "Unknown method"}'
				;;
		esac
		;;
esac
