#!/bin/sh /etc/rc.common

START=21
STOP=79
USE_PROCD=1

CLASH_DIR="/opt/clash"
CLASH_BIN="${CLASH_DIR}/bin/clash"
CLASH_RULES="${CLASH_DIR}/bin/clash-rules"
RULESET_TMPFS="/tmp/clash/ruleset"
RULESET_LINK="${CLASH_DIR}/ruleset"
PROXY_PROVIDERS_TMPFS="/tmp/clash/proxy_providers"
PROXY_PROVIDERS_LINK="${CLASH_DIR}/proxy_providers"
DNS_BACKUP_FILE="/opt/clash/.dns_backup"
CONFIG_UPGRADE_BACKUP="${CLASH_DIR}/.config.yaml.upgrade.bak"
SETTINGS_FILE="${CLASH_DIR}/settings"
USE_TMPFS_RULES="true"

if [ -f "$SETTINGS_FILE" ]; then
	tmpfs_val="$(grep -m1 '^USE_TMPFS_RULES=' "$SETTINGS_FILE" 2>/dev/null | cut -d'=' -f2)"
	[ "$tmpfs_val" = "false" ] && USE_TMPFS_RULES="false"
fi

# Function to log messages
msg() {
	logger -p daemon.info -st "clash[$$]" "$*"
}

# Function to check the existence of files/directories
check_files() {
	if [ ! -f "$CLASH_BIN" ]; then
		msg "ERROR: Clash binary not found at $CLASH_BIN"
		return 1
	fi

	if [ ! -f "$CLASH_RULES" ]; then
		msg "WARNING: Clash rules script not found at $CLASH_RULES"
	fi

	return 0
}

restore_upgrade_config_backup() {
	if [ -f "$CONFIG_UPGRADE_BACKUP" ]; then
		if mv "$CONFIG_UPGRADE_BACKUP" "$CLASH_DIR/config.yaml"; then
			msg "Restored preserved config.yaml from package upgrade backup"
		else
			msg "ERROR: Failed to restore preserved config.yaml from package upgrade backup"
			return 1
		fi
	fi

	return 0
}

# Function to set up clash directories
setup_clash_directories() {
	if [ "$USE_TMPFS_RULES" = "true" ]; then
		# Setup ruleset in tmpfs with symlink from CLASH_DIR
		if [ ! -d "$RULESET_TMPFS" ]; then
			if ! mkdir -p "$RULESET_TMPFS"; then
				msg "ERROR: Failed to create directory $RULESET_TMPFS"
				return 1
			fi
			msg "Created directory '$RULESET_TMPFS'"
		fi

		if [ ! -L "$RULESET_LINK" ] || [ "$(readlink "$RULESET_LINK")" != "$RULESET_TMPFS" ]; then
			rm -rf "$RULESET_LINK"
			if ! ln -s "$RULESET_TMPFS" "$RULESET_LINK"; then
				msg "ERROR: Failed to create symlink from '$RULESET_TMPFS' to '$RULESET_LINK'"
				return 1
			fi
			msg "Created symlink from '$RULESET_TMPFS' to '$RULESET_LINK'"
		fi

		# Setup proxy_providers in tmpfs with symlink from CLASH_DIR
		if [ ! -d "$PROXY_PROVIDERS_TMPFS" ]; then
			if ! mkdir -p "$PROXY_PROVIDERS_TMPFS"; then
				msg "ERROR: Failed to create directory $PROXY_PROVIDERS_TMPFS"
				return 1
			fi
			msg "Created directory '$PROXY_PROVIDERS_TMPFS'"
		fi

		if [ ! -L "$PROXY_PROVIDERS_LINK" ] || [ "$(readlink "$PROXY_PROVIDERS_LINK")" != "$PROXY_PROVIDERS_TMPFS" ]; then
			rm -rf "$PROXY_PROVIDERS_LINK"
			if ! ln -s "$PROXY_PROVIDERS_TMPFS" "$PROXY_PROVIDERS_LINK"; then
				msg "ERROR: Failed to create symlink from '$PROXY_PROVIDERS_TMPFS' to '$PROXY_PROVIDERS_LINK'"
				return 1
			fi
			msg "Created symlink from '$PROXY_PROVIDERS_TMPFS' to '$PROXY_PROVIDERS_LINK'"
		fi
	else
		# Use persistent storage under CLASH_DIR without tmpfs or symlinks
		if [ -L "$RULESET_LINK" ]; then
			rm -f "$RULESET_LINK"
		fi
		if [ ! -d "$RULESET_LINK" ]; then
			if ! mkdir -p "$RULESET_LINK"; then
				msg "ERROR: Failed to create persistent ruleset directory $RULESET_LINK"
				return 1
			fi
			msg "Created persistent ruleset directory '$RULESET_LINK'"
		fi

		if [ -L "$PROXY_PROVIDERS_LINK" ]; then
			rm -f "$PROXY_PROVIDERS_LINK"
		fi
		if [ ! -d "$PROXY_PROVIDERS_LINK" ]; then
			if ! mkdir -p "$PROXY_PROVIDERS_LINK"; then
				msg "ERROR: Failed to create persistent proxy_providers directory $PROXY_PROVIDERS_LINK"
				return 1
			fi
			msg "Created persistent proxy_providers directory '$PROXY_PROVIDERS_LINK'"
		fi
	fi

	return 0
}

# Function to configure DNS
setup_dns() {
	local dns_server="127.0.0.1#7874"

	# Check if the server is already added
	local existing_server
	existing_server=$(uci get dhcp.@dnsmasq[0].server 2>/dev/null | grep "$dns_server")

	# Save original cachesize and noresolv values so restore_dns can put them back
	# exactly as they were, rather than simply deleting them (which would lose any
	# value the admin had explicitly configured before Clash was started).
	if [ ! -f "$DNS_BACKUP_FILE" ]; then
		mkdir -p "$(dirname "$DNS_BACKUP_FILE")" 2>/dev/null
		if [ -n "$existing_server" ]; then
			printf 'CACHESIZE=\nNORESOLV=\n' > "$DNS_BACKUP_FILE"
			msg "Detected stale DNS state from prior unclean shutdown; using safe defaults for restore"
		else
			local orig_cachesize orig_noresolv
			orig_cachesize=$(uci get dhcp.@dnsmasq[0].cachesize 2>/dev/null)
			orig_noresolv=$(uci get dhcp.@dnsmasq[0].noresolv 2>/dev/null)
			printf 'CACHESIZE=%s\nNORESOLV=%s\n' "$orig_cachesize" "$orig_noresolv" > "$DNS_BACKUP_FILE"
		fi
	fi

	if [ -z "$existing_server" ]; then
		uci add_list dhcp.@dnsmasq[0].server="$dns_server" || {
			msg "ERROR: Failed to add DNS server"
			return 1
		}
	fi

	uci set dhcp.@dnsmasq[0].cachesize='0' || {
		msg "ERROR: Failed to set cachesize"
		return 1
	}

	uci set dhcp.@dnsmasq[0].noresolv='1' || {
		msg "ERROR: Failed to set noresolv"
		return 1
	}

	if ! uci commit dhcp; then
		msg "ERROR: Failed to commit DNS changes"
		return 1
	fi

	msg "DNS settings configured"
	return 0
}

# Function to restore DNS settings
restore_dns() {
	local dns_server="127.0.0.1#7874"

	# Get the list of servers and remove only ours
	local servers
	servers=$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)
	if echo "$servers" | grep -q "$dns_server"; then
		uci del_list dhcp.@dnsmasq[0].server="$dns_server" 2>/dev/null
	fi

	# Restore the exact original values we saved before modifying them.
	# If no backup file exists (e.g. service stopped without having been fully
	# started), fall back to simply deleting our settings.
	local orig_cachesize=""
	local orig_noresolv=""
	if [ -f "$DNS_BACKUP_FILE" ]; then
		orig_cachesize=$(grep '^CACHESIZE=' "$DNS_BACKUP_FILE" | cut -d= -f2)
		orig_noresolv=$(grep '^NORESOLV=' "$DNS_BACKUP_FILE" | cut -d= -f2)
		rm -f "$DNS_BACKUP_FILE"
	fi

	if [ -n "$orig_cachesize" ]; then
		uci set dhcp.@dnsmasq[0].cachesize="$orig_cachesize" 2>/dev/null
	else
		uci delete dhcp.@dnsmasq[0].cachesize 2>/dev/null
	fi

	if [ -n "$orig_noresolv" ]; then
		uci set dhcp.@dnsmasq[0].noresolv="$orig_noresolv" 2>/dev/null
	else
		uci delete dhcp.@dnsmasq[0].noresolv 2>/dev/null
	fi

	if uci commit dhcp; then
		msg "DNS settings restored"
	else
		msg "WARNING: Failed to restore DNS settings"
	fi
}

# Function to apply firewall rules
apply_firewall_rules() {
	if [ -f "$CLASH_RULES" ]; then
		if "$CLASH_RULES" start; then
			msg "Firewall rules applied"
			return 0
		else
			msg "ERROR: Failed to apply firewall rules"
			return 1
		fi
	else
		msg "WARNING: Clash rules script not found, skipping firewall configuration"
		return 0
	fi
}

# Function to remove firewall rules
remove_firewall_rules() {
	if [ -f "$CLASH_RULES" ]; then
		if "$CLASH_RULES" stop; then
			msg "Firewall rules removed"
		else
			msg "WARNING: Failed to remove firewall rules"
		fi
	fi
}

# Function to restart dnsmasq
restart_dnsmasq() {
	if /etc/init.d/dnsmasq restart >/dev/null 2>&1; then
		msg "dnsmasq restarted successfully"
		return 0
	else
		msg "ERROR: Failed to restart dnsmasq"
		return 1
	fi
}

start_service() {
	msg "Starting Clash service..."

	restore_upgrade_config_backup || return 1

	# Check the required files
	check_files || return 1

	# Set up the directories (ruleset and proxy_providers)
	setup_clash_directories || return 1

	# Check Clash configuration
	if ! "$CLASH_BIN" -d "$CLASH_DIR" -t; then
		msg "ERROR: Clash configuration test failed"
		return 1
	fi

	# Apply firewall rules — failure is fatal: without redirect rules traffic
	# bypasses the proxy entirely, which makes the DNS-to-Clash setup incoherent.
	apply_firewall_rules || {
		msg "ERROR: Failed to apply firewall rules, aborting start"
		return 1
	}

	# Set up DNS — on failure, roll back the firewall rules we just applied.
	setup_dns || {
		msg "ERROR: Failed to configure DNS, rolling back firewall rules"
		remove_firewall_rules
		return 1
	}

	# Restart dnsmasq to apply the new DNS settings — on failure, restore DNS
	# config and roll back firewall rules so the system is left consistent.
	restart_dnsmasq || {
		msg "ERROR: Failed to restart dnsmasq, rolling back DNS and firewall rules"
		restore_dns
		/etc/init.d/dnsmasq restart >/dev/null 2>&1 || true
		remove_firewall_rules
		return 1
	}

	# Start Clash via procd
	procd_open_instance
	procd_set_param command "$CLASH_BIN" -d "$CLASH_DIR"
	procd_set_param stdout 1
	procd_set_param stderr 1
	procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
	procd_set_param file "$CLASH_DIR/config.yaml"
	procd_close_instance

	msg "Clash service started successfully"
}

stop_service() {
	msg "Stopping Clash service..."

	# Remove firewall rules
	remove_firewall_rules

	# Restore DNS settings
	restore_dns

	# Restart dnsmasq
	restart_dnsmasq || {
		msg "WARNING: Failed to restart dnsmasq during stop"
	}

	msg "Clash service stopped"
}

reload_service() {
	msg "Reloading Clash service..."

	stop
	start

	msg "Clash service reloaded"
}

service_started() {
	# Called by procd every time the service process (re)starts, including respawns.
	# The package-installed /etc/hotplug.d/net/99-clash-tun handles the normal
	# case: when mihomo creates clash-tun, the kernel event fires the hotplug script
	# which calls `clash-rules tun_route_setup`.
	# tun_route_watch covers the fast-respawn edge case where clash-tun is still up
	# when procd fires this callback (interface did not disappear between respawns).
	local proxy_mode=""
	[ -f "$SETTINGS_FILE" ] && proxy_mode=$(grep '^PROXY_MODE=' "$SETTINGS_FILE" | cut -d= -f2 | tr -d '[:space:]')
	case "$proxy_mode" in
		mixed|tun)
			if [ -f "$CLASH_RULES" ]; then
				"$CLASH_RULES" tun_route_watch
				local i=0
				while [ "$i" -lt 30 ]; do
					[ -d /sys/class/net/clash-tun ] && break
					sleep 1
					i=$((i + 1))
				done
				"$CLASH_RULES" tun_route_setup
				"$CLASH_RULES" repair_policy
				"$CLASH_RULES" repair_forward_rules
			fi
			;;
	esac
}

service_triggers() {
	procd_add_reload_trigger "clash"
}

boot() {
	# Wait for system initialization
	sleep 10
	start
}
