#!/bin/sh
# shellcheck shell=dash
#
# udhcpc default script (IPv4) - Enigma2 resolver integration
#

[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1

IFACE="$interface"
STATE="$1"

[ -n "$broadcast" ] && BROADCAST="brd $broadcast" || BROADCAST=""


# ---- Common Enigma2 resolv.conf merge (POSIX sh, BusyBox-friendly) ----

STATE_DIR="/var/run/enigma2-resolv"
LOCKDIR="${STATE_DIR}/.lock.d"
RESOLV_CONF="/etc/resolv.conf"
SETTINGS_FILE="/etc/enigma2/settings"
OVERRIDE_NS_FILE="/etc/enigma2/nameserversdns.conf"

DEBUG_FLAG="/etc/enigma2-resolv-debug.enable"
DEBUG_LOG="/tmp/enigma2-resolv-debug.log"

log() {
	[ -f "$DEBUG_FLAG" ] || return 0
	ts="$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "")"
	printf '%s [%s pid=%s] %s\n' "$ts" "$IFACE" "$$" "$*" >> "$DEBUG_LOG" 2>/dev/null || true
	return 0
}

get_setting_value() {
	key="$1"
	[ -f "$SETTINGS_FILE" ] || { echo ""; return 0; }
	line="$(grep -m1 "^${key}=" "$SETTINGS_FILE" 2>/dev/null | head -n 1 | tr -d '\r')"
	[ -n "$line" ] || { echo ""; return 0; }
	echo "$line" | cut -d= -f2 | sed 's/[[:space:]]*$//'
}

get_dns_mode()   { get_setting_value 'config\.usage\.dnsMode'; }
get_dns_suffix() { get_setting_value 'config\.usage\.dnsSuffix'; }

dns_rotate_enabled() {
	[ -f "$SETTINGS_FILE" ] || return 1
	grep -q '^config\.usage\.dnsRotate=' "$SETTINGS_FILE" 2>/dev/null || return 1
	val="$(get_setting_value 'config\.usage\.dnsRotate')"
	case "$val" in
		0|[Ff][Aa][Ll][Ss][Ee]|[Nn][Oo]|[Oo][Ff][Ff]) return 1 ;;
		*) return 0 ;;
	esac
}

# Return 0 if argument is invalid placeholder
is_invalid_ns() {
	case "$1" in
		""|"0.0.0.0"|"::"|"0:0:0:0:0:0:0:0") return 0 ;;
	esac
	return 1
}

override_nameservers_present() {
	[ -f "$OVERRIDE_NS_FILE" ] || return 1
	grep -q '^[[:space:]]*nameserver[[:space:]]' "$OVERRIDE_NS_FILE" 2>/dev/null
}

read_override_nameservers() {
	awk '$1=="nameserver" && $2!="" { gsub("\r","",$2); print $2 }' "$OVERRIDE_NS_FILE" 2>/dev/null
}

acquire_lock_or_skip() {
	mkdir -p "$STATE_DIR" 2>/dev/null || true
	if mkdir "$LOCKDIR" 2>/dev/null; then
		echo "$$" > "$LOCKDIR/pid" 2>/dev/null || true
		return 0
	fi

	# stale lock recovery (no waiting)
	if [ -f "$LOCKDIR/pid" ]; then
		oldpid="$(cat "$LOCKDIR/pid" 2>/dev/null || echo "")"
		case "$oldpid" in ''|*[!0-9]*) return 1 ;; esac
		if ! kill -0 "$oldpid" 2>/dev/null; then
			rm -rf "$LOCKDIR" 2>/dev/null || true
			if mkdir "$LOCKDIR" 2>/dev/null; then
				echo "$$" > "$LOCKDIR/pid" 2>/dev/null || true
				return 0
			fi
		fi
	fi
	return 1
}

release_lock() { rm -rf "$LOCKDIR" 2>/dev/null || true; }

write_source_file() {
	# write_source_file <path> <domain> <nameserver_list>
	path="$1"
	domain="$2"
	ns_list="$3"

	tmp="${path}.$$"
	: > "$tmp" 2>/dev/null || { rm -f "$tmp" 2>/dev/null || true; return 1; }

	[ -n "$domain" ] && printf 'domain %s\n' "$domain" >> "$tmp"
	for n in $ns_list; do
		is_invalid_ns "$n" && continue
		[ -n "$n" ] && printf 'nameserver %s\n' "$n" >> "$tmp"
	done

	mv -f "$tmp" "$path" 2>/dev/null || { rm -f "$tmp" 2>/dev/null || true; return 1; }
	return 0
}

apply_to_etc_resolvconf() {
	# apply_to_etc_resolvconf <merged_file>
	merged="$1"

	# Must contain at least one nameserver line, otherwise never touch /etc/resolv.conf
	ns_count="$(grep -c '^[[:space:]]*nameserver[[:space:]]' "$merged" 2>/dev/null || echo 0)"
	case "$ns_count" in ''|*[!0-9]*) ns_count=0 ;; esac
	if [ "$ns_count" -le 0 ]; then
		log "apply: merged has no nameservers, skip /etc/resolv.conf"
		return 0
	fi

	etmp="/etc/resolv.conf.enigma2.$$"
	if ! cat "$merged" > "$etmp" 2>/dev/null; then
		log "apply: cannot write $etmp"
		rm -f "$etmp" 2>/dev/null || true
		return 1
	fi
	chmod 0644 "$etmp" 2>/dev/null || true

	if diff -q "$RESOLV_CONF" "$etmp" >/dev/null 2>&1; then
		rm -f "$etmp" 2>/dev/null || true
		log "apply: unchanged"
		return 0
	fi

	if mv -f "$etmp" "$RESOLV_CONF" 2>/dev/null; then
		log "apply: wrote /etc/resolv.conf"
		return 0
	fi

	log "apply: mv failed ($etmp -> $RESOLV_CONF)"
	rm -f "$etmp" 2>/dev/null || true
	return 1
}

merge_resolv() {
	# Build merged content under STATE_DIR, then apply to /etc/resolv.conf
	merged="${STATE_DIR}/enigma2-resolv.merged.$$"
	ns_tmp="${STATE_DIR}/enigma2-resolv.ns.$$"
	opts_tmp="${STATE_DIR}/enigma2-resolv.opts.$$"
	last_out="${STATE_DIR}/resolv.conf.last"

	: > "$ns_tmp"; : > "$opts_tmp"; : > "$merged"

	# Preserve other lines from current resolv.conf:
	# - drop domain/nameserver/search completely
	# - drop any "options rotate" (with optional leading spaces) to avoid duplication
	grep -E -v '^[[:space:]]*(domain|search|nameserver)[[:space:]]|^[[:space:]]*options[[:space:]]+rotate([[:space:]]|$)|^[[:space:]]*$' \
		"$RESOLV_CONF" 2>/dev/null >> "$opts_tmp" || true

	# Collect domain and nameservers from sources
	domain_v4=""; domain_v6=""
	set -- "$STATE_DIR"/*.conf
	if [ -e "$1" ]; then
		for f in "$STATE_DIR"/*.conf; do
			[ -f "$f" ] || continue
			d="$(grep '^[[:space:]]*domain[[:space:]]' "$f" 2>/dev/null | head -n 1 | awk '{print $2}' | tr -d '\r')"
			case "$f" in
				*-v4.conf) [ -n "$d" ] && domain_v4="$d" ;;
				*-v6.conf) [ -n "$d" ] && domain_v6="$d" ;;
			esac
			grep '^[[:space:]]*nameserver[[:space:]]' "$f" 2>/dev/null | tr -d '\r' >> "$ns_tmp" || true
		done
	fi

	# Domain override via settings (dnssuffix)
	suffix="$(get_dns_suffix)"
	if [ -n "$suffix" ]; then
		domain_line="$suffix"
	else
		domain_line="${domain_v4:-$domain_v6}"
	fi
	[ -n "$domain_line" ] && printf 'domain %s\n' "$domain_line" >> "$merged"

	# options rotate (max once)
	if dns_rotate_enabled; then
		printf 'options rotate\n' >> "$merged"
	fi

	# preserved extra lines
	cat "$opts_tmp" >> "$merged" 2>/dev/null || true

	# Build nameserver lists (override file wins globally)
	DNSMODE="$(get_dns_mode)"
	[ -z "$DNSMODE" ] && DNSMODE=0

	V4_LIST=""
	V6_LIST=""

	if override_nameservers_present; then
		for addr in $(read_override_nameservers); do
			is_invalid_ns "$addr" && continue
			case "$addr" in *:*) V6_LIST="$V6_LIST $addr" ;; *) V4_LIST="$V4_LIST $addr" ;; esac
		done
	else
		SEEN=""
		while read -r _kw addr; do
			[ -n "$addr" ] || continue
			addr="$(printf '%s' "$addr" | tr -d '\r')"
			is_invalid_ns "$addr" && continue
			case " $SEEN " in *" $addr "*) continue ;; esac
			SEEN="$SEEN $addr"
			case "$addr" in *:*) V6_LIST="$V6_LIST $addr" ;; *) V4_LIST="$V4_LIST $addr" ;; esac
		done < "$ns_tmp"
	fi

	emit_list() {
		for a in $1; do
			[ -n "$a" ] && printf 'nameserver %s\n' "$a" >> "$merged"
		done
	}

	case "$DNSMODE" in
		1) emit_list "$V6_LIST"; emit_list "$V4_LIST" ;;
		2) emit_list "$V4_LIST" ;;
		3) emit_list "$V6_LIST" ;;
		*) emit_list "$V4_LIST"; emit_list "$V6_LIST" ;;
	esac

	# Save last_out (best effort)
	last_tmp="${last_out}.$$"
	if cat "$merged" > "$last_tmp" 2>/dev/null; then
		mv -f "$last_tmp" "$last_out" 2>/dev/null || rm -f "$last_tmp" 2>/dev/null || true
	else
		rm -f "$last_tmp" 2>/dev/null || true
	fi

	apply_to_etc_resolvconf "$merged" || true

	rm -f "$merged" "$ns_tmp" "$opts_tmp" 2>/dev/null || true
	return 0
}


SRC_FILE="${STATE_DIR}/10-${IFACE}-v4.conf"

write_v4_source_and_merge() {
	# If override file exists, don't write DHCP nameservers into the v4 source
	NS_LIST=""
	if ! override_nameservers_present; then
		NS_LIST="$dns"
	fi

	write_source_file "$SRC_FILE" "$domain" "$NS_LIST" || true
	merge_resolv
}

remove_v4_source_and_merge() {
	rm -f "$SRC_FILE" 2>/dev/null || true
	merge_resolv
}

case "$STATE" in
	deconfig)
		ip -4 addr flush dev "$IFACE" scope global 2>/dev/null || true
		ip link set dev "$IFACE" up 2>/dev/null || true
		if acquire_lock_or_skip; then
			trap 'release_lock' EXIT INT TERM
			remove_v4_source_and_merge
			release_lock
			trap - EXIT INT TERM
		else
			log "merge_resolv: lock busy, skip"
		fi
		;;
	renew|bound)
		if ip -4 addr replace "$ip/$mask" dev "$IFACE" $BROADCAST 2>/dev/null; then :; else
			ip -4 addr flush dev "$IFACE" scope global 2>/dev/null || true
			[ -z "$BROADCAST" ] && BROADCAST="brd +"
			ip -4 addr add "$ip/$mask" dev "$IFACE" $BROADCAST 2>/dev/null || true
		fi

		if [ -n "$router" ]; then
			for _n in 1 2 3 4 5; do
				ip route del default dev "$IFACE" 2>/dev/null || break
			done
			metric=0
			for gw in $router; do
				ip route replace default via "$gw" dev "$IFACE" metric "$metric" 2>/dev/null || 					ip route add default via "$gw" dev "$IFACE" metric "$metric" 2>/dev/null || true
				metric=$((metric + 1))
			done
		fi

		if acquire_lock_or_skip; then
			trap 'release_lock' EXIT INT TERM
			write_v4_source_and_merge
			release_lock
			trap - EXIT INT TERM
		else
			log "merge_resolv: lock busy, skip"
		fi
		;;
esac

exit 0
