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

USE_PROCD=1
START=99
STOP=10

NAME=tailscale-settings
EXTRA_COMMANDS="clean_firewall clean_firewall_rules"

# Compare version strings: return 0 (true) if $1 >= $2
version_gte() {
	awk -v a="${1%%-*}" -v b="${2%%-*}" 'BEGIN {
		split(a, av, ".")
		split(b, bv, ".")
		for (i=1; i<=3; i++) {
			ai = int(av[i]+0); bi = int(bv[i]+0)
			if (ai > bi) { exit 0 }
			if (ai < bi) { exit 1 }
		}
		exit 0
	}'
	return $?
}

# Add/remove DNS forward entry in dnsmasq for MagicDNS via OpenWrt
setup_tailscale_dns_forward() {
	if [ ! -x /usr/sbin/tailscale ] && [ ! -x /usr/bin/tailscale ]; then
		return 0
	fi

	local entry="/.ts.net/100.100.100.100"
	local count
	count=$(uci -q get dhcp.@dnsmasq[0].server 2>/dev/null | tr ' ' '\n' | grep -Fxc "$entry" 2>/dev/null)

	if [ "$count" -gt 1 ]; then
		uci -q del_list dhcp.@dnsmasq[0].server="$entry"
		uci add_list dhcp.@dnsmasq[0].server="$entry"
		uci commit dhcp
		/etc/init.d/dnsmasq reload 2>/dev/null || /etc/init.d/dnsmasq restart 2>/dev/null
		logger -t "$NAME" "Collapsed duplicate DNS forward entries"
	elif [ "$count" -eq 0 ]; then
		uci add_list dhcp.@dnsmasq[0].server="$entry"
		uci commit dhcp
		/etc/init.d/dnsmasq reload 2>/dev/null || /etc/init.d/dnsmasq restart 2>/dev/null
		logger -t "$NAME" "Added DNS forward: $entry"
	fi
}

remove_tailscale_dns_forward() {
	local entry="/.ts.net/100.100.100.100"
	local idx=0
	local removed=0
	while true; do
		local val
		val=$(uci -q get dhcp.@dnsmasq[0].server."${idx}" 2>/dev/null)
		[ -z "$val" ] && break
		if [ "$val" = "$entry" ]; then
			uci -q del_list dhcp.@dnsmasq[0].server="$entry"
			removed=1
			break
		fi
		idx=$((idx + 1))
	done

	if [ "$removed" = "1" ]; then
		uci commit dhcp
		/etc/init.d/dnsmasq reload 2>/dev/null || /etc/init.d/dnsmasq restart 2>/dev/null
		logger -t "$NAME" "Removed Tailscale DNS forward entry: $entry"
	fi
}

check_zone() {
	local cfg="$1"
	local name
	config_get name "$cfg" name
	if [ "$name" = "tailscale" ]; then
		FOUND_TS_ZONE="$cfg"
	fi
}

check_forwarding() {
	local cfg="$1"
	local src dst
	config_get src "$cfg" src
	config_get dst "$cfg" dest
	[ "$src" = "lan" ] && [ "$dst" = "tailscale" ] && FWD_LAN_TS=1
	[ "$src" = "tailscale" ] && [ "$dst" = "lan" ] && FWD_TS_LAN=1
	[ "$src" = "tailscale" ] && [ "$dst" = "wan" ] && FWD_TS_WAN=1
}

setup_firewall_rules() {
	local changed_net=0
	local changed_fw=0

	# 1. Interface (set both device and ifname for broad OpenWrt version compatibility)
	local dev ifname
	dev=$(uci -q get network.tailscale.device)
	ifname=$(uci -q get network.tailscale.device)

	if [ -z "$dev" ] && [ -z "$ifname" ]; then
		uci -q set network.tailscale=interface
		uci -q set network.tailscale.proto='none'
		uci -q set network.tailscale.device='tailscale0'
		uci -q set network.tailscale.device='tailscale0'
		changed_net=1
	else
		if [ -z "$dev" ]; then
			uci -q set network.tailscale.device='tailscale0'
			changed_net=1
		fi
		if [ -z "$ifname" ]; then
			uci -q set network.tailscale.device='tailscale0'
			changed_net=1
		fi
	fi

	# 2. Zone
	local tunnel_snat target_masq="0"
	tunnel_snat=$(uci -q get tailscale.settings.tunnel_snat)
	[ "$tunnel_snat" = "1" ] && target_masq="1"

	config_load firewall
	FOUND_TS_ZONE=""
	config_foreach check_zone zone

	if [ -z "$FOUND_TS_ZONE" ]; then
		local zid
		zid=$(uci -q add firewall zone)
		uci -q set firewall."$zid".name='tailscale'
		uci -q set firewall."$zid".input='ACCEPT'
		uci -q set firewall."$zid".output='ACCEPT'
		uci -q set firewall."$zid".forward='ACCEPT'
		uci -q set firewall."$zid".masq="$target_masq"
		uci -q set firewall."$zid".mtu_fix='1'
		uci -q add_list firewall."$zid".network='tailscale'
		changed_fw=1
	else
		local cur_masq
		cur_masq=$(uci -q get firewall."$FOUND_TS_ZONE".masq)
		if [ "$cur_masq" != "$target_masq" ]; then
			uci -q set firewall."$FOUND_TS_ZONE".masq="$target_masq"
			changed_fw=1
		fi

		local has_net=0
		local net
		for net in $(uci -q get firewall."$FOUND_TS_ZONE".network); do
			[ "$net" = "tailscale" ] && has_net=1
		done
		if [ "$has_net" = "0" ]; then
			uci -q add_list firewall."$FOUND_TS_ZONE".network='tailscale'
			changed_fw=1
		fi
	fi

	# 3. Forwardings
	FWD_LAN_TS=0
	FWD_TS_LAN=0
	FWD_TS_WAN=0
	config_foreach check_forwarding forwarding

	if [ "$FWD_LAN_TS" = "0" ]; then
		local fid
		fid=$(uci -q add firewall forwarding)
		uci -q set firewall."$fid".src='lan'
		uci -q set firewall."$fid".dest='tailscale'
		changed_fw=1
	fi
	if [ "$FWD_TS_LAN" = "0" ]; then
		local fid=$(uci -q add firewall forwarding)
		uci -q set firewall."$fid".src='tailscale'
		uci -q set firewall."$fid".dest='lan'
		changed_fw=1
	fi
	if [ "$FWD_TS_WAN" = "0" ]; then
		local fid=$(uci -q add firewall forwarding)
		uci -q set firewall."$fid".src='tailscale'
		uci -q set firewall."$fid".dest='wan'
		changed_fw=1
	fi

	if [ "$changed_net" = "1" ]; then
		uci -q commit network
		logger -t "$NAME" "Auto configured tailscale network interface (tailscale0)"
	fi

	if [ "$changed_fw" = "1" ]; then
		uci -q commit firewall
		/etc/init.d/firewall reload 2>/dev/null
		logger -t "$NAME" "Auto configured tailscale firewall zone and forwarding rules"
	fi
}

clean_firewall_rules() {
	local changed_net=0
	local changed_fw=0

	# 1. Remove forwarding rules referencing tailscale
	config_load firewall
	local fwds_to_del=""
	delete_ts_fwd() {
		local cfg="$1"
		local src dst
		config_get src "$cfg" src
		config_get dst "$cfg" dest
		if [ "$src" = "tailscale" ] || [ "$dst" = "tailscale" ]; then
			fwds_to_del="$fwds_to_del $cfg"
		fi
	}
	config_foreach delete_ts_fwd forwarding

	for fwd in $fwds_to_del; do
		uci -q delete firewall."$fwd"
		changed_fw=1
	done

	# 2. Remove tailscale zone
	local zones_to_del=""
	find_ts_zone() {
		local cfg="$1"
		local name
		config_get name "$cfg" name
		if [ "$name" = "tailscale" ]; then
			zones_to_del="$zones_to_del $cfg"
		fi
	}
	config_foreach find_ts_zone zone

	for z in $zones_to_del; do
		uci -q delete firewall."$z"
		changed_fw=1
	done

	# 3. Remove network interface
	if uci -q get network.tailscale >/dev/null; then
		uci -q delete network.tailscale
		changed_net=1
	fi

	# 4. Remove DNS forward entry in dnsmasq
	remove_tailscale_dns_forward

	if [ "$changed_net" = "1" ]; then
		uci -q commit network
		logger -t "$NAME" "Cleaned tailscale network interface"
	fi

	if [ "$changed_fw" = "1" ]; then
		uci -q commit firewall
		/etc/init.d/firewall reload 2>/dev/null
		logger -t "$NAME" "Cleaned tailscale firewall zone and forwarding rules"
	fi
}

clean_firewall() {
	clean_firewall_rules
}

apply_settings() {
	setup_firewall_rules

	local ts_bin
	if [ -x /usr/sbin/tailscale ]; then
		ts_bin=/usr/sbin/tailscale
	elif [ -x /usr/bin/tailscale ]; then
		ts_bin=/usr/bin/tailscale
	else
		logger -t "$NAME" "tailscale binary not found, skipping settings apply"
		return 0
	fi

	config_load tailscale

	local accept_routes advertise_exit_node exit_node exit_node_allow_lan_access
	local ssh shields_up runwebclient tunnel_snat hostname dns_mode
	local enable_relay relay_server_port

	config_get accept_routes              settings accept_routes              '0'
	config_get advertise_exit_node        settings advertise_exit_node        '0'
	config_get exit_node                  settings exit_node                  ''
	config_get exit_node_allow_lan_access settings exit_node_allow_lan_access '0'
	config_get ssh                        settings ssh                        '0'
	config_get dns_mode                   settings dns_mode                   'disabled'
	config_get shields_up                 settings shields_up                 '0'
	config_get runwebclient               settings runwebclient               '0'
	config_get tunnel_snat                settings tunnel_snat                '0'
	config_get hostname                   settings hostname                   ''
	config_get enable_relay               settings enable_relay               '0'
	config_get relay_server_port          settings relay_server_port          '40000'

	# Collect advertise_routes UCI list into comma-separated string
	local routes=""
	append_route() {
		if [ -z "$routes" ]; then
			routes="$1"
		else
			routes="$routes,$1"
		fi
	}
	config_list_foreach settings advertise_routes append_route

	# Build argument list for 'tailscale set'
	set -- set

	# --accept-routes
	[ "$accept_routes" = "1" ] \
		&& set -- "$@" --accept-routes=true \
		|| set -- "$@" --accept-routes=false

	# --advertise-exit-node (only when no exit node is selected)
	[ "$advertise_exit_node" = "1" ] && [ -z "$exit_node" ] \
		&& set -- "$@" --advertise-exit-node=true \
		|| set -- "$@" --advertise-exit-node=false

	# --exit-node-allow-lan-access (only when no exit node is selected)
	if [ -z "$exit_node" ]; then
		[ "$exit_node_allow_lan_access" = "1" ] \
			&& set -- "$@" --exit-node-allow-lan-access=true \
			|| set -- "$@" --exit-node-allow-lan-access=false
	fi

	# --ssh
	[ "$ssh" = "1" ] \
		&& set -- "$@" --ssh=true \
		|| set -- "$@" --ssh=false

	# --accept-dns based on dns_mode
	case "$dns_mode" in
		magicdns)
			set -- "$@" --accept-dns=true
			remove_tailscale_dns_forward
			;;
		openwrt_forward)
			set -- "$@" --accept-dns=false
			setup_tailscale_dns_forward
			;;
		*)
			# disabled (default)
			set -- "$@" --accept-dns=false
			remove_tailscale_dns_forward
			;;
	esac

	# --shields-up
	[ "$shields_up" = "1" ] \
		&& set -- "$@" --shields-up=true \
		|| set -- "$@" --shields-up=false

	# --webclient
	[ "$runwebclient" = "1" ] \
		&& set -- "$@" --webclient=true \
		|| set -- "$@" --webclient=false

	# --snat-subnet-routes (aligned with tunnel_snat)
	[ "$tunnel_snat" = "1" ] \
		&& set -- "$@" --snat-subnet-routes=true \
		|| set -- "$@" --snat-subnet-routes=false

	# --advertise-routes (comma-separated, empty clears routes)
	set -- "$@" "--advertise-routes=$routes"

	# --exit-node (empty clears the exit node)
	set -- "$@" "--exit-node=$exit_node"

	# when an exit node is configured, always allow LAN access
	[ -n "$exit_node" ] && set -- "$@" --exit-node-allow-lan-access=true

	# --hostname (only set if non-empty)
	[ -n "$hostname" ] && set -- "$@" "--hostname=$hostname"

	# --relay-server-port (requires tailscale >= 1.90.5)
	local ts_ver
	ts_ver=$("$ts_bin" version 2>/dev/null | head -n1)
	if version_gte "$ts_ver" "1.90.5"; then
		if [ "$enable_relay" = "1" ]; then
			set -- "$@" "--relay-server-port=${relay_server_port:-40000}"
		else
			set -- "$@" "--relay-server-port="
		fi
	fi

	# Always enforce netfilter-mode=off so OpenWrt manages all firewall rules cleanly
	set -- "$@" --netfilter-mode=off

	# Wait for tailscaled daemon to be ready (up to 5 seconds)
	local i=0
	local daemon_ready=0
	while [ $i -lt 5 ]; do
		if "$ts_bin" status >/dev/null 2>&1; then
			daemon_ready=1
			break
		fi
		if ! "$ts_bin" status 2>&1 | grep -q "failed to connect to local tailscaled"; then
			daemon_ready=1
			break
		fi
		sleep 1
		i=$((i + 1))
	done

	if [ "$daemon_ready" = "0" ]; then
		logger -t "$NAME" "Tailscale daemon is not running or socket is unreachable, skipping settings apply"
		return 0
	fi

	# Check logged-in status. Skip applying settings if logged out to prevent command error.
	local status_out
	status_out=$("$ts_bin" status 2>&1)
	if echo "$status_out" | grep -qE "Logged out|NeedsLogin|NeedsCard"; then
		logger -t "$NAME" "Tailscale is logged out, saving settings locally but skipping immediate apply"
		return 0
	fi

	logger -t "$NAME" "Applying settings via tailscale set"
	"$ts_bin" "$@"
	local ret=$?
	if [ $ret -eq 0 ]; then
		logger -t "$NAME" "Settings applied successfully"
	else
		logger -t "$NAME" "Failed to apply settings (exit code: $ret)"
	fi

	return 0
}

handle_service_state() {
	config_load tailscale
	local service_enabled
	config_get service_enabled settings service_enabled '0'
	
	if [ "$service_enabled" = "0" ]; then
		logger -t "$NAME" "Service is disabled, stopping tailscale and cleaning up"
		/etc/init.d/tailscale stop 2>/dev/null
		/etc/init.d/tailscale disable 2>/dev/null
		clean_firewall_rules
		killall -9 tailscaled 2>/dev/null || true
	else
		logger -t "$NAME" "Service is enabled, ensuring tailscale is running"
		/etc/init.d/tailscale enable 2>/dev/null
		if ! /usr/sbin/tailscale status >/dev/null 2>&1 && ! /usr/bin/tailscale status >/dev/null 2>&1; then
			/etc/init.d/tailscale start 2>/dev/null
		fi
		apply_settings
	fi
}

start_service() {
	handle_service_state
}

service_triggers() {
	procd_add_reload_trigger "tailscale"
}

reload_service() {
	handle_service_state
}
