#!/bin/sh
# SPDX-License-Identifier: MIT
# SecuBox nDPId - RPCD Backend
# Complete interface for nDPId DPI daemon
# Copyright (C) 2025 CyberMind.fr

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

# Paths
CONFIG_FILE="/etc/config/ndpid"
STATUS_FILE="/var/run/netifyd/status.json"
DISTRIBUTOR_SOCK="/var/run/ndpid/distributor.sock"
COLLECTOR_SOCK="/var/run/ndpid/collector.sock"
FLOWS_CACHE="/tmp/ndpid-flows.json"
APPS_CACHE="/tmp/ndpid-apps.json"
STATS_CACHE="/tmp/ndpid-stats.json"
LOG_FILE="/var/log/ndpid.log"

# Logging
log_msg() {
	local level="$1"
	shift
	logger -t luci.ndpid "[$level] $*"
}

# Check if nDPId is running
check_ndpid_running() {
	pidof ndpid >/dev/null 2>&1
}

# Check if nDPIsrvd is running
check_ndpisrvd_running() {
	pidof ndpisrvd >/dev/null 2>&1
}

# Get service status
get_service_status() {
	json_init

	local running=0
	local distributor_running=0
	local pid=""
	local uptime=0
	local version=""

	if check_ndpid_running; then
		running=1
		pid=$(pidof ndpid | awk '{print $1}')

		# Get uptime from /proc
		if [ -n "$pid" ] && [ -d "/proc/$pid" ]; then
			local start_time=$(stat -c %Y /proc/$pid 2>/dev/null)
			local now=$(date +%s)
			[ -n "$start_time" ] && uptime=$((now - start_time))
		fi

		# Get version
		version=$(ndpid -v 2>&1 | head -1 | grep -oE '[0-9]+\.[0-9]+' || echo "1.7")
	fi

	check_ndpisrvd_running && distributor_running=1

	json_add_boolean "running" "$running"
	json_add_boolean "distributor_running" "$distributor_running"
	json_add_string "pid" "$pid"
	json_add_int "uptime" "$uptime"
	json_add_string "version" "$version"
	json_add_string "collector_socket" "$COLLECTOR_SOCK"
	json_add_string "distributor_socket" "$DISTRIBUTOR_SOCK"

	# Check if compat layer is running
	local compat_running=0
	pidof ndpid-compat >/dev/null 2>&1 && compat_running=1
	json_add_boolean "compat_running" "$compat_running"

	json_dump
}

# Get real-time flow statistics from status file
get_realtime_flows() {
	json_init

	if [ -f "$STATUS_FILE" ]; then
		local status_json=$(cat "$STATUS_FILE" 2>/dev/null)
		if [ -n "$status_json" ]; then
			# Parse values
			local flow_count=$(echo "$status_json" | jsonfilter -e '@.flow_count' 2>/dev/null || echo 0)
			local flows_active=$(echo "$status_json" | jsonfilter -e '@.flows_active' 2>/dev/null || echo 0)
			local uptime=$(echo "$status_json" | jsonfilter -e '@.uptime' 2>/dev/null || echo 0)

			json_add_int "flow_count" "$flow_count"
			json_add_int "flows_active" "$flows_active"
			json_add_int "uptime" "$uptime"
			json_add_boolean "available" 1
		else
			json_add_boolean "available" 0
		fi
	else
		json_add_boolean "available" 0
		json_add_int "flow_count" 0
		json_add_int "flows_active" 0
	fi

	json_dump
}

# Get interface statistics
get_interface_stats() {
	json_init
	json_add_array "interfaces"

	if [ -f "$STATUS_FILE" ]; then
		local stats=$(cat "$STATUS_FILE" | jsonfilter -e '@.stats' 2>/dev/null)
		if [ -n "$stats" ] && command -v jq >/dev/null 2>&1; then
			# Parse each interface
			for iface in $(echo "$stats" | jq -r 'keys[]' 2>/dev/null); do
				json_add_object
				json_add_string "name" "$iface"

				local ip_bytes=$(echo "$stats" | jq -r ".\"$iface\".ip_bytes // 0")
				local wire_bytes=$(echo "$stats" | jq -r ".\"$iface\".wire_bytes // 0")
				local tcp=$(echo "$stats" | jq -r ".\"$iface\".tcp // 0")
				local udp=$(echo "$stats" | jq -r ".\"$iface\".udp // 0")
				local icmp=$(echo "$stats" | jq -r ".\"$iface\".icmp // 0")

				json_add_int "ip_bytes" "$ip_bytes"
				json_add_int "wire_bytes" "$wire_bytes"
				json_add_int "tcp" "$tcp"
				json_add_int "udp" "$udp"
				json_add_int "icmp" "$icmp"

				json_close_object
			done
		fi
	fi

	json_close_array
	json_dump
}

# Get top applications (from aggregated apps file)
get_top_applications() {
	json_init
	json_add_array "applications"

	local app_data=""

	# Try apps cache first (pre-aggregated by ndpid-compat)
	if [ -f "$APPS_CACHE" ] && command -v jq >/dev/null 2>&1; then
		app_data=$(jq -r '.[0:15][] | "\(.name // "Unknown")|\(.category // "Unknown")|\(.flows // 0)|\(.bytes // 0)"' \
			"$APPS_CACHE" 2>/dev/null)
	# Fallback to flows cache
	elif [ -f "$FLOWS_CACHE" ] && command -v jq >/dev/null 2>&1; then
		app_data=$(jq -r '
			group_by(.app) |
			map({
				name: (.[0].app // "Unknown"),
				category: (.[0].category // "Unknown"),
				flows: length,
				bytes: ([.[].bytes_rx, .[].bytes_tx] | add // 0)
			}) |
			sort_by(-.bytes) |
			.[0:15][] |
			"\(.name)|\(.category)|\(.flows)|\(.bytes)"
		' "$FLOWS_CACHE" 2>/dev/null)
	fi

	if [ -n "$app_data" ]; then
		while IFS='|' read -r name category flows bytes; do
			[ -z "$name" ] && continue
			json_add_object
			json_add_string "name" "${name:-Unknown}"
			json_add_string "category" "${category:-Unknown}"
			json_add_int "flows" "${flows:-0}"
			json_add_int "bytes" "${bytes:-0}"
			json_close_object
		done <<EOF
$app_data
EOF
	fi

	json_close_array
	json_dump
}

# Get detailed flows with detection info
get_detailed_flows() {
	json_init
	json_add_array "flows"

	if [ -f "$FLOWS_CACHE" ] && command -v jq >/dev/null 2>&1; then
		# Get recent active flows sorted by bytes - use heredoc to avoid subshell
		local flow_data
		flow_data=$(jq -r '
			sort_by(-(.bytes_rx + .bytes_tx)) |
			.[0:100][] |
			"\(.id // 0)|\(.src_ip // "")|\(.src_port // 0)|\(.dst_ip // "")|\(.dst_port // 0)|\(.proto // "")|\(.app // "Unknown")|\(.category // "Unknown")|\(.hostname // "")|\(.confidence // "")|\(.bytes_rx // 0)|\(.bytes_tx // 0)|\(.packets // 0)|\(.state // "unknown")|\(.iface // "")"
		' "$FLOWS_CACHE" 2>/dev/null)

		while IFS='|' read -r id src_ip src_port dst_ip dst_port proto app category hostname confidence bytes_rx bytes_tx packets state iface; do
			[ -z "$id" ] && continue
			json_add_object
			json_add_int "id" "${id:-0}"
			json_add_string "src_ip" "${src_ip}"
			json_add_int "src_port" "${src_port:-0}"
			json_add_string "dst_ip" "${dst_ip}"
			json_add_int "dst_port" "${dst_port:-0}"
			json_add_string "proto" "${proto}"
			json_add_string "app" "${app:-Unknown}"
			json_add_string "category" "${category:-Unknown}"
			json_add_string "hostname" "${hostname}"
			json_add_string "confidence" "${confidence}"
			json_add_int "bytes_rx" "${bytes_rx:-0}"
			json_add_int "bytes_tx" "${bytes_tx:-0}"
			json_add_int "packets" "${packets:-0}"
			json_add_string "state" "${state:-unknown}"
			json_add_string "iface" "${iface}"
			json_close_object
		done <<EOF
$flow_data
EOF
	fi

	json_close_array
	json_dump
}

# Get category breakdown
get_categories() {
	json_init
	json_add_array "categories"

	if [ -f "$APPS_CACHE" ] && command -v jq >/dev/null 2>&1; then
		local cat_data
		cat_data=$(jq -r '
			group_by(.category) |
			map({
				name: (.[0].category // "Unknown"),
				apps: length,
				flows: ([.[].flows] | add),
				bytes: ([.[].bytes] | add)
			}) |
			sort_by(-.bytes) |
			.[] |
			"\(.name)|\(.apps)|\(.flows)|\(.bytes)"
		' "$APPS_CACHE" 2>/dev/null)

		while IFS='|' read -r name apps flows bytes; do
			[ -z "$name" ] && continue
			json_add_object
			json_add_string "name" "${name:-Unknown}"
			json_add_int "apps" "${apps:-0}"
			json_add_int "flows" "${flows:-0}"
			json_add_int "bytes" "${bytes:-0}"
			json_close_object
		done <<EOF
$cat_data
EOF
	fi

	json_close_array
	json_dump
}

# Get top protocols
get_top_protocols() {
	json_init
	json_add_array "protocols"

	if [ -f "$STATUS_FILE" ]; then
		local stats=$(cat "$STATUS_FILE" | jsonfilter -e '@.stats' 2>/dev/null)
		if [ -n "$stats" ] && command -v jq >/dev/null 2>&1; then
			# Aggregate protocol counts across interfaces
			local tcp=$(echo "$stats" | jq '[.[].tcp] | add // 0')
			local udp=$(echo "$stats" | jq '[.[].udp] | add // 0')
			local icmp=$(echo "$stats" | jq '[.[].icmp] | add // 0')

			json_add_object
			json_add_string "name" "TCP"
			json_add_int "count" "$tcp"
			json_close_object

			json_add_object
			json_add_string "name" "UDP"
			json_add_int "count" "$udp"
			json_close_object

			json_add_object
			json_add_string "name" "ICMP"
			json_add_int "count" "$icmp"
			json_close_object
		fi
	fi

	json_close_array
	json_dump
}

# Get configuration
get_config() {
	json_init

	config_load ndpid

	# Main settings
	local enabled interfaces collector_socket max_flows
	config_get_bool enabled main enabled 0
	config_get interfaces main interface ""
	config_get collector_socket main collector_socket "/var/run/ndpid/collector.sock"
	config_get max_flows main max_flows 100000

	json_add_boolean "enabled" "$enabled"
	json_add_string "interfaces" "$interfaces"
	json_add_string "collector_socket" "$collector_socket"
	json_add_int "max_flows" "$max_flows"

	# Distributor settings
	local dist_enabled tcp_port tcp_address
	config_get_bool dist_enabled distributor enabled 1
	config_get tcp_port distributor tcp_port 7000
	config_get tcp_address distributor tcp_address "127.0.0.1"

	json_add_object "distributor"
	json_add_boolean "enabled" "$dist_enabled"
	json_add_int "tcp_port" "$tcp_port"
	json_add_string "tcp_address" "$tcp_address"
	json_close_object

	# Compat settings
	local compat_enabled
	config_get_bool compat_enabled compat enabled 1
	json_add_object "compat"
	json_add_boolean "enabled" "$compat_enabled"
	json_close_object

	# Actions settings
	local actions_enabled
	config_get_bool actions_enabled actions enabled 0
	json_add_object "actions"
	json_add_boolean "enabled" "$actions_enabled"
	json_close_object

	json_dump
}

# Get dashboard summary
get_dashboard() {
	json_init

	# Service status
	json_add_object "service"
	local running=0
	check_ndpid_running && running=1
	json_add_boolean "running" "$running"

	local distributor_running=0
	check_ndpisrvd_running && distributor_running=1
	json_add_boolean "distributor_running" "$distributor_running"

	local compat_running=0
	pidof ndpid-compat >/dev/null 2>&1 && compat_running=1
	json_add_boolean "compat_running" "$compat_running"

	json_add_string "version" "$(ndpid -v 2>&1 | head -1 | grep -oE '[0-9]+\.[0-9]+' || echo '1.7')"
	json_close_object

	# Flow stats
	json_add_object "flows"
	if [ -f "$STATUS_FILE" ]; then
		local flow_count=$(cat "$STATUS_FILE" | jsonfilter -e '@.flow_count' 2>/dev/null || echo 0)
		local flows_active=$(cat "$STATUS_FILE" | jsonfilter -e '@.flows_active' 2>/dev/null || echo 0)
		json_add_int "total" "$flow_count"
		json_add_int "active" "$flows_active"
	else
		json_add_int "total" 0
		json_add_int "active" 0
	fi
	json_close_object

	# System stats
	json_add_object "system"
	local pid=$(pidof ndpid | awk '{print $1}')
	if [ -n "$pid" ] && [ -d "/proc/$pid" ]; then
		# Memory usage
		local mem_kb=$(awk '/VmRSS/{print $2}' /proc/$pid/status 2>/dev/null || echo 0)
		json_add_int "memory_kb" "$mem_kb"

		# CPU (simplified)
		json_add_string "cpu" "0%"
	else
		json_add_int "memory_kb" 0
		json_add_string "cpu" "0%"
	fi
	json_close_object

	# Interfaces
	json_add_array "interfaces"
	config_load ndpid
	config_get interfaces main interface ""
	for iface in $interfaces; do
		json_add_string "" "$iface"
	done
	json_close_array

	json_dump
}

# Service control: start
service_start() {
	json_init

	/etc/init.d/ndpisrvd start 2>&1
	sleep 1
	/etc/init.d/ndpid start 2>&1

	# Start compat layer
	local compat_enabled
	config_load ndpid
	config_get_bool compat_enabled compat enabled 1
	if [ "$compat_enabled" -eq 1 ]; then
		/usr/bin/ndpid-compat -d 2>&1
	fi

	sleep 2

	if check_ndpid_running; then
		json_add_boolean "success" 1
		json_add_string "message" "nDPId started successfully"
		log_msg "INFO" "Service started"
	else
		json_add_boolean "success" 0
		json_add_string "message" "Failed to start nDPId"
		log_msg "ERROR" "Service failed to start"
	fi

	json_dump
}

# Service control: stop
service_stop() {
	json_init

	# Stop compat layer
	if [ -f /var/run/ndpid-compat.pid ]; then
		kill $(cat /var/run/ndpid-compat.pid) 2>/dev/null
		rm -f /var/run/ndpid-compat.pid
	fi

	/etc/init.d/ndpid stop 2>&1
	/etc/init.d/ndpisrvd stop 2>&1

	sleep 1

	if ! check_ndpid_running; then
		json_add_boolean "success" 1
		json_add_string "message" "nDPId stopped successfully"
		log_msg "INFO" "Service stopped"
	else
		json_add_boolean "success" 0
		json_add_string "message" "Failed to stop nDPId"
		log_msg "ERROR" "Service failed to stop"
	fi

	json_dump
}

# Service control: restart
service_restart() {
	json_init

	service_stop >/dev/null 2>&1
	sleep 2
	service_start >/dev/null 2>&1

	if check_ndpid_running; then
		json_add_boolean "success" 1
		json_add_string "message" "nDPId restarted successfully"
	else
		json_add_boolean "success" 0
		json_add_string "message" "Failed to restart nDPId"
	fi

	json_dump
}

# Service control: enable
service_enable() {
	json_init

	uci set ndpid.main.enabled=1
	uci commit ndpid

	/etc/init.d/ndpid enable
	/etc/init.d/ndpisrvd enable

	json_add_boolean "success" 1
	json_add_string "message" "nDPId enabled"

	json_dump
}

# Service control: disable
service_disable() {
	json_init

	uci set ndpid.main.enabled=0
	uci commit ndpid

	/etc/init.d/ndpid disable
	/etc/init.d/ndpisrvd disable

	json_add_boolean "success" 1
	json_add_string "message" "nDPId disabled"

	json_dump
}

# Update configuration
update_config() {
	local data="$1"
	json_init

	if [ -z "$data" ]; then
		json_add_boolean "success" 0
		json_add_string "error" "No configuration data provided"
		json_dump
		return
	fi

	# Parse and apply configuration
	local enabled=$(echo "$data" | jsonfilter -e '@.enabled' 2>/dev/null)
	local interfaces=$(echo "$data" | jsonfilter -e '@.interfaces' 2>/dev/null)
	local max_flows=$(echo "$data" | jsonfilter -e '@.max_flows' 2>/dev/null)

	[ -n "$enabled" ] && uci set ndpid.main.enabled="$enabled"
	[ -n "$max_flows" ] && uci set ndpid.main.max_flows="$max_flows"

	# Handle interfaces (clear and re-add)
	if [ -n "$interfaces" ]; then
		uci -q delete ndpid.main.interface
		for iface in $interfaces; do
			uci add_list ndpid.main.interface="$iface"
		done
	fi

	uci commit ndpid

	json_add_boolean "success" 1
	json_add_string "message" "Configuration updated"
	log_msg "INFO" "Configuration updated"

	json_dump
}

# Clear statistics cache
clear_cache() {
	json_init

	rm -f "$FLOWS_CACHE" "$STATS_CACHE"
	rm -rf /tmp/ndpid-state

	json_add_boolean "success" 1
	json_add_string "message" "Cache cleared"

	json_dump
}

# Get monitored interfaces list
get_interfaces() {
	json_init
	json_add_array "interfaces"

	# Get configured interfaces
	config_load ndpid
	config_get interfaces main interface ""

	for iface in $interfaces; do
		json_add_object
		json_add_string "name" "$iface"

		# Check if interface exists
		if [ -d "/sys/class/net/$iface" ]; then
			json_add_boolean "exists" 1

			# Get interface state
			local state=$(cat /sys/class/net/$iface/operstate 2>/dev/null || echo "unknown")
			json_add_string "state" "$state"

			# Get MAC address
			local mac=$(cat /sys/class/net/$iface/address 2>/dev/null || echo "")
			json_add_string "mac" "$mac"
		else
			json_add_boolean "exists" 0
		fi

		json_close_object
	done

	json_close_array

	# Scan available interfaces from network config and system
	json_add_array "available"

	# Get bridges and network interfaces from UCI
	local net_ifaces=""
	config_load network

	# Get all defined interfaces (br-lan, br-wan, etc)
	for iface in $(ls /sys/class/net/ 2>/dev/null); do
		case "$iface" in
			lo|sit*|ip6*|gre*|ifb*|teql*)
				# Skip loopback and virtual tunnel interfaces
				;;
			br-*|eth*|wlan*|lan*|wan*)
				# Include bridges, ethernet, wifi, and named interfaces
				local state=$(cat /sys/class/net/$iface/operstate 2>/dev/null || echo "unknown")
				local type=$(cat /sys/class/net/$iface/type 2>/dev/null || echo "0")
				# Only include if it's a real interface (type 1 = ethernet)
				if [ "$type" = "1" ] || [ -d "/sys/class/net/$iface/bridge" ]; then
					json_add_object
					json_add_string "name" "$iface"
					json_add_string "state" "$state"
					# Check if it's a bridge
					if [ -d "/sys/class/net/$iface/bridge" ]; then
						json_add_string "type" "bridge"
					else
						json_add_string "type" "interface"
					fi
					json_close_object
				fi
				;;
		esac
	done

	json_close_array

	json_dump
}

# RPC method dispatcher
case "$1" in
	list)
		cat << 'EOF'
{
	"get_service_status": {},
	"get_realtime_flows": {},
	"get_detailed_flows": {},
	"get_interface_stats": {},
	"get_top_applications": {},
	"get_top_protocols": {},
	"get_categories": {},
	"get_config": {},
	"get_dashboard": {},
	"get_interfaces": {},
	"service_start": {},
	"service_stop": {},
	"service_restart": {},
	"service_enable": {},
	"service_disable": {},
	"update_config": { "data": "object" },
	"clear_cache": {}
}
EOF
		;;
	call)
		case "$2" in
			get_service_status)
				get_service_status
				;;
			get_realtime_flows)
				get_realtime_flows
				;;
			get_detailed_flows)
				get_detailed_flows
				;;
			get_interface_stats)
				get_interface_stats
				;;
			get_top_applications)
				get_top_applications
				;;
			get_top_protocols)
				get_top_protocols
				;;
			get_categories)
				get_categories
				;;
			get_config)
				get_config
				;;
			get_dashboard)
				get_dashboard
				;;
			get_interfaces)
				get_interfaces
				;;
			service_start)
				service_start
				;;
			service_stop)
				service_stop
				;;
			service_restart)
				service_restart
				;;
			service_enable)
				service_enable
				;;
			service_disable)
				service_disable
				;;
			update_config)
				read -r input
				data=$(echo "$input" | jsonfilter -e '@.data' 2>/dev/null)
				update_config "$data"
				;;
			clear_cache)
				clear_cache
				;;
			*)
				echo '{"error": "Unknown method"}'
				;;
		esac
		;;
	*)
		echo '{"error": "Invalid action"}'
		;;
esac
