#!/bin/sh

NAME=devgate
LOG_FILE="/var/log/devgate.log"
DEBUG=1

IDLIST="/var/$NAME.idlist"
RELOAD_FLAG="/var/$NAME.reload"
FIREWALL_DIRTY=0
NO_ENABLED_CLEARED=0

# 日志函数
dbg() {
	[ "$DEBUG" -eq 1 ] && {
		local d="$(date '+%Y-%m-%d %H:%M:%S')"
		echo "[$d] CTRL-DEBUG: $@" >> "$LOG_FILE"
	}
}

info() {
	local d="$(date '+%Y-%m-%d %H:%M:%S')"
	echo "[$d] CTRL-INFO: $@" >> "$LOG_FILE"
}

# 配置文件读取
config_t_get() {
	local index=${3:-0}
	local ret=$(uci -q get "${NAME}.@${1}[${index}].${2}")
	echo "${ret:=${4}}"
}

normalize_mac() {
	local mac
	mac=$(echo "${1:-}" | xargs | tr 'A-F' 'a-f')

	if echo "$mac" | grep -qE '^([0-9a-f]{2}:){5}[0-9a-f]{2}$'; then
		echo "$mac"
		return 0
	fi

	return 1
}

# 获取启用设备
get_enabled_devices() {
	uci show "$NAME" 2>/dev/null | sed -n "s/^${NAME}\.@device\[\([0-9][0-9]*\)\]\.enable='1'.*/\1/p" | sort -n
}

get_enabled_device_count() {
	get_enabled_devices | wc -l
}

is_enabled_device() {
	local id="$1"

	get_enabled_devices | grep -qx "$id"
}

get_device_uid() {
	local id="$1"
	local uid

	uid="$(devgate uid "$id" 2>/dev/null)" || return 1
	printf '%s\n' "$uid"
}

firewall_rule_section() {
	local uid="$1"

	echo "devgate_$uid"
}

firewall_rule_exists() {
	local uid="$1"

	[ -n "$uid" ] && uci -q show "firewall.$(firewall_rule_section "$uid")" >/dev/null 2>&1
}

get_firewall_rule_enabled() {
	local uid="$1"
	local value

	value="$(uci -q get "firewall.$(firewall_rule_section "$uid").enabled")"
	echo "${value:-0}"
}

remember_managed_uid() {
	local uid="$1"

	[ -n "$uid" ] || return 0
	mkdir -p "$(dirname "$IDLIST")" 2>/dev/null
	touch "$IDLIST" 2>/dev/null || return 1
	if ! grep -q "!$uid!" "$IDLIST" 2>/dev/null; then
		echo "!$uid!" >> "$IDLIST"
	fi
	sort -u "$IDLIST" -o "$IDLIST" 2>/dev/null || true
}

forget_managed_uid() {
	local uid="$1"

	[ -n "$uid" ] || return 0
	sed -i "/!$uid!/d" "$IDLIST" 2>/dev/null
}

is_managed_uid() {
	local uid="$1"

	[ -f "$IDLIST" ] && grep -q "!${uid}!" "$IDLIST" 2>/dev/null
}

disable_device_rule() {
	local id="$1"
	local uid="$2"
	local target=$(config_t_get device mac "$id")
	local comment=$(config_t_get device comment "$id" "设备$id")

	info "停用DevGate规则: $comment ($target, uid=$uid)"
	uci -q set "${NAME}.@device[$id].enable=0" || return 1
	uci -q commit "$NAME" || return 1
	forget_managed_uid "$uid"
	return 0
}

disable_device_without_mac() {
	local id="$1"
	local uid="$2"
	local target=$(config_t_get device mac "$id")
	local comment=$(config_t_get device comment "$id" "设备$id")

	info "目标设备为空或无效，强制未启用: $comment ($target, uid=$uid)"
	run_devgate_change del "$uid"
	disable_device_rule "$id" "$uid"
}

is_enabled_uid() {
	local uid="$1"
	local id

	for id in $(get_enabled_devices); do
		[ "$(get_device_uid "$id")" = "$uid" ] && return 0
	done

	return 1
}

run_devgate_change() {
	local rc

	DEVGATE_DEFER_FIREWALL_RELOAD=1 devgate "$@"
	rc=$?

	[ "$rc" -eq 0 ] && FIREWALL_DIRTY=1
	return "$rc"
}

flush_firewall_changes() {
	[ "$FIREWALL_DIRTY" -eq 1 ] || return 0

	if devgate reload; then
		FIREWALL_DIRTY=0
		return 0
	fi

	info "firewall重载失败"
	return 1
}

# 时间检查函数
time_to_minutes() {
	local value="$1"
	local hour="${value%:*}"
	local minute="${value#*:}"

	hour="$(echo "$hour" | sed 's/^0*//')"
	minute="$(echo "$minute" | sed 's/^0*//')"
	[ -n "$hour" ] || hour=0
	[ -n "$minute" ] || minute=0

	echo $((hour * 60 + minute))
}

is_time_value() {
	local value="${1:-}"
	local hour="${value%:*}"
	local minute="${value#*:}"

	[ "$hour:$minute" = "$value" ] || return 1
	echo "$hour" | grep -qE '^[0-9]{1,2}$' || return 1
	echo "$minute" | grep -qE '^[0-9]{1,2}$' || return 1

	hour="$(echo "$hour" | sed 's/^0*//')"
	minute="$(echo "$minute" | sed 's/^0*//')"
	[ -n "$hour" ] || hour=0
	[ -n "$minute" ] || minute=0

	[ "$hour" -ge 0 ] && [ "$hour" -le 24 ] || return 1
	[ "$minute" -ge 0 ] && [ "$minute" -le 59 ] || return 1
	[ "$hour" -lt 24 ] || [ "$minute" -eq 0 ]
}

is_time_in_range() {
	local start_time=$1
	local end_time=$2
	local current_time=$(date +%H:%M)
	local start_min
	local end_min
	local current_min

	start_min=$(time_to_minutes "$start_time")
	end_min=$(time_to_minutes "$end_time")
	current_min=$(time_to_minutes "$current_time")

	if [ "$start_min" -eq "$end_min" ]; then
		return 0
	elif [ "$start_min" -lt "$end_min" ]; then
		[ "$current_min" -ge "$start_min" ] && [ "$current_min" -lt "$end_min" ] && return 0
	else
		[ "$current_min" -ge "$start_min" ] || [ "$current_min" -lt "$end_min" ] && return 0
	fi

	return 1
}

is_time_ranges_active() {
	local time_ranges="${1:-00:00-00:00}"
	local range
	local start_time
	local end_time

	for range in $(printf '%s' "$time_ranges" | sed 's/,/ /g'); do
		range=$(echo "$range" | xargs)
		[ -n "$range" ] || continue

		start_time="${range%-*}"
		end_time="${range#*-}"

		if [ "$start_time" = "$range" ] || [ "$end_time" = "$range" ]; then
			dbg "跳过无效禁用时段: $range"
			continue
		fi

		if ! is_time_value "$start_time" || ! is_time_value "$end_time"; then
			dbg "跳过无效禁用时段: $range"
			continue
		fi

		is_time_in_range "$start_time" "$end_time" && return 0
	done

	return 1
}

is_weekday_in_range() {
	local configured_weekdays=$1
	local current_weekday=$(date +%u)

	[ "$configured_weekdays" = "0" ] && return 0

	for ww in $(echo "$configured_weekdays" | sed 's/,/ /g'); do
		[ "$current_weekday" = "$ww" ] && return 0
	done
	return 1
}

# 主检查函数
check_device_control() {
	local id="$1"
	local target=$(config_t_get device mac "$id")
	local weekdays=$(config_t_get device week "$id" "0")
	local time_ranges="$(config_t_get device time_ranges "$id" "00:00-00:00")"

	is_weekday_in_range "$weekdays" || {
		dbg "生效日期外: $target"
		return 0
	}

	if is_time_ranges_active "$time_ranges"; then
		dbg "禁用时段内: $target ($time_ranges)"
		return 1
	fi

	dbg "禁用时段外: $target ($time_ranges)"
	return 0
}

# 同步设备状态
sync_device_status() {
	local id="$1"
	local force_refresh="${2:-0}"
	local uid
	local target=$(config_t_get device mac "$id")
	local comment=$(config_t_get device comment "$id" "设备$id")
	local mac
	local should_block=0
	local firewall_exists=0
	local firewall_enabled

	uid="$(get_device_uid "$id")" || {
		info "规则唯一识别码缺失，停用DevGate规则: 设备$id"
		uci -q set "${NAME}.@device[$id].enable=0" || return 1
		uci -q commit "$NAME" || return 1
		return 1
	}

	mac="$(normalize_mac "$target")" || {
		disable_device_without_mac "$id" "$uid"
		return 0
	}

	if firewall_rule_exists "$uid"; then
		firewall_exists=1
	elif is_managed_uid "$uid"; then
		disable_device_rule "$id" "$uid"
		return 0
	fi

	if ! check_device_control "$id"; then
		should_block=1
	fi

	dbg "设备状态: $mac, uid=$uid, 防火墙规则存在=$firewall_exists, 应阻止=$should_block"

	if [ "$firewall_exists" -eq 0 ]; then
		dbg "创建托管防火墙规则: $target enabled=$should_block"
		if run_devgate_change add "$id" "$should_block"; then
			remember_managed_uid "$uid"
			if [ "$should_block" -eq 1 ]; then
				info "阻止上网: $comment ($target)"
			else
				info "规则待命: $comment ($target)"
			fi
		else
			info "创建防火墙规则失败: $comment ($target)"
		fi
		return 0
	fi

	firewall_enabled="$(get_firewall_rule_enabled "$uid")"
	if [ "$firewall_enabled" != "$should_block" ] || [ "$force_refresh" -eq 1 ]; then
		if [ "$should_block" -eq 1 ]; then
			dbg "启用防火墙阻止: $target"
		else
			dbg "停用防火墙阻止: $target"
		fi
		run_devgate_change add "$id" "$should_block"
	fi
	remember_managed_uid "$uid"

	if [ "$should_block" -eq 1 ]; then
		info "阻止上网: $comment ($target)"
	else
		info "允许上网: $comment ($target)"
	fi
}

sync_removed_devices() {
	local ids id tmp

	[ -f "$IDLIST" ] || return 0

	tmp="${IDLIST}.$$"
	: > "$tmp"

	ids=$(sed -n 's/^!\([A-Za-z0-9][A-Za-z0-9]*\)!$/\1/p' "$IDLIST" 2>/dev/null | sort | uniq)
	for id in $ids; do
		if is_enabled_uid "$id"; then
			echo "!$id!" >> "$tmp"
		else
			dbg "清理已删除设备规则: $id"
			run_devgate_change del "$id"
			forget_managed_uid "$id"
			info "移除已删除规则: $id"
		fi
	done

	mv "$tmp" "$IDLIST"
}

wait_next_check() {
	local seconds="${1:-10}"

	while [ "$seconds" -gt 0 ]; do
		[ -f "$RELOAD_FLAG" ] && return 0
		sleep 1
		seconds=$((seconds - 1))
	done
}

# 主处理循环
main_loop() {
	info "设备门禁守护进程启动"
	run_devgate_change start

	while :; do
		local force_refresh=0

		dbg "开始检查设备"
		if [ -f "$RELOAD_FLAG" ]; then
			force_refresh=1
			rm -f "$RELOAD_FLAG" 2>/dev/null
			dbg "检测到配置重载标记"
		fi

		if [ "$(get_enabled_device_count)" -eq 0 ]; then
			if [ "$NO_ENABLED_CLEARED" -eq 0 ]; then
				run_devgate_change stop
				flush_firewall_changes
				NO_ENABLED_CLEARED=1
			fi
			wait_next_check 10
			continue
		fi

		NO_ENABLED_CLEARED=0
		sync_removed_devices
		for id in $(get_enabled_devices); do
			sync_device_status "$id" "$force_refresh"
		done
		flush_firewall_changes

		wait_next_check 10
	done
}

# 启动
main_loop
