#!/bin/sh
# shellcheck shell=dash
# Based on odhcp6c sample script and rdnssd merge-hook

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

IFACE=$1
STATE=$2
DEBUG=0

DOMAINS=${DOMAINS:=""}

# ---- 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
		case "$n" in
			fe80*%*) printf 'nameserver %s\n' "$n" >> "$tmp" ;;
			fe80*)   printf 'nameserver %s%%%s\n' "$n" "$IFACE" >> "$tmp" ;;
			*)       [ -n "$n" ] && printf 'nameserver %s\n' "$n" >> "$tmp" ;;
		esac
	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}/20-${IFACE}-v6.conf"

update_resolv() {
	set -- $ALL_DOMAINS
	DOMAIN="$1"

	if acquire_lock_or_skip; then
		trap 'release_lock' EXIT INT TERM
		write_source_file "$SRC_FILE" "$DOMAIN" "$ALL_DNS" || true
		merge_resolv
		release_lock
		trap - EXIT INT TERM
	else
		log "merge_resolv: lock busy, skip"
	fi
}

remove_v6_source_and_merge() {
	if acquire_lock_or_skip; then
		trap 'release_lock' EXIT INT TERM
		rm -f "$SRC_FILE" 2>/dev/null || true
		merge_resolv
		release_lock
		trap - EXIT INT TERM
	else
		log "merge_resolv: lock busy, skip"
	fi
}

setup_interface () {
	local device="$1"

	local prefixpart=""
	for entry in $PREFIXES; do
		local addr="${entry%%,*}"
                entry="${entry#*,}"
                local preferred="${entry%%,*}"
                entry="${entry#*,}"
                local valid="${entry%%,*}"
                entry="${entry#*,}"
		[ "$entry" = "$valid" ] && entry=

		local class=""
		local excluded=""

		while [ -n "$entry" ]; do
			local key="${entry%%=*}"
			entry="${entry#*=}"
			local val="${entry%%,*}"
			entry="${entry#*,}"
			[ "$entry" = "$val" ] && entry=

			if [ "$key" = "class" ]; then
				class=", \"class\": $val"
			elif [ "$key" = "excluded" ]; then
				excluded=", \"excluded\": \"$val\""
			fi
		done

		local prefix="{\"address\": \"$addr\", \"preferred\": $preferred, \"valid\": $valid $class $excluded}"

		if [ -z "$prefixpart" ]; then
			prefixpart="$prefix"
		else
			prefixpart="$prefixpart, $prefix"
		fi

		# TODO: delete this somehow when the prefix disappears
		#ip -6 route add unreachable "$addr"
	done

	# Merge addresses
	for entry in $RA_ADDRESSES; do
		local duplicate=0
		local addr="${entry%%/*}"
		for dentry in $ADDRESSES; do
			local daddr="${dentry%%/*}"
			[ "$addr" = "$daddr" ] && duplicate=1
		done
		[ "$duplicate" = "0" ] && ADDRESSES="$ADDRESSES $entry"
	done

	realip=$(readlink /sbin/ip)

	for entry in $ADDRESSES; do
		local addr="${entry%%,*}"
		entry="${entry#*,}"
		local preferred="${entry%%,*}"
		entry="${entry#*,}"
		local valid="${entry%%,*}"

		if [ "x$realip" = "x/sbin/ip.iproute2" ]; then
			ip -6 address replace "$addr" dev "$device" preferred_lft "$preferred" valid_lft "$valid"
		else
			# Busybox ip does not understand lifetimes
			ip -6 address replace "$addr" dev "$device"
		fi
	done
}

teardown_interface() {
	local device="$1"
	OLDADDRS=$(ip -6 addr show scope global dev "$device" | grep ' *inet6 *.*/128 *scope' | awk '{ print $2 }')
	for oldaddr in $OLDADDRS; do
		ip -6 addr delete "$oldaddr" dev "$device"
	done
#	ip -6 route replace ff00::/8 dev "$device" metric 256
}

read_auto() {
	# Method auto with option "dhcp" = Use stateless DHCPv6

	DHCP_CONF=$(awk -v par="$IFACE" '
	/^iface/ && $2==par && $3=="inet6" && $4=="auto" {f=1}
	/^iface/ && $2==par && $3=="inet" {f=0}
	/^iface/ && $2!=par {f=0}
	f && /^\s*dhcp/ {print $2; f=0}' /etc/network/interfaces)
	[ "x$DHCP_CONF" = "x" ] && DHCP_CONF=1
}

read_static() {
	# Method static with option "autoconf" = Perform stateless autoconfiguration

	RA_CONF=$(awk -v par="$IFACE" '
	/^iface/ && $2==par && $3=="inet6" && $4=="static" {f=1}
	/^iface/ && $2==par && $3=="inet" {f=0}
	/^iface/ && $2!=par {f=0}
	f && /^\s*autoconf/ {print $2; f=0}' /etc/network/interfaces)
	[ "x$RA_CONF" = "x" ] && RA_CONF=0
}

read_dhcp() {
	# Method dhcp with option "autoconf" = Perform stateless autoconfiguration

	RA_CONF=$(awk -v par="$IFACE" '
	/^iface/ && $2==par && $3=="inet6" && $4=="dhcp" {f=1}
	/^iface/ && $2==par && $3=="inet" {f=0}
	/^iface/ && $2!=par {f=0}
	f && /^\s*autoconf/ {print $2; f=0}' /etc/network/interfaces)
	[ "x$RA_CONF" = "x" ] && RA_CONF=1
}


get_config() {
	METHOD=""
	METHOD=$(grep "^iface [[:space:]]*${IFACE} [[:space:]]*inet6" /etc/network/interfaces | awk '{ print $4 }')
	if [ "x$METHOD" = "x" ]; then
		METHOD="auto"
		RA_CONF=1
		DHCP_CONF=1
		DHCP_ADDR=0
	else
		case $METHOD in
			auto)
				DHCP_ADDR=0
				RA_CONF=1
				read_auto
				return
				;;
			dhcp)
				DHCP_ADDR=1
				DHCP_CONF=1
				read_dhcp
				return
				;;
			manual)
				DHCP_ADDR=0
				DHCP_CONF=0
				RA_CONF=0
				return
				;;
			static)
				DHCP_ADDR=0
				DHCP_CONF=0
				read_static
				return
				;;
			*)
				return
				;;
		esac
	fi
}


merge_dhcp_ra() {
	if [ "x$DHCP_CONF" = "x0" ] && [ "x$RA_CONF" = "x0" ]; then

		# Neither RA nor DHCPv6

		ALL_DNS=""
		ALL_DOMAINS=""
	elif [ "x$DHCP_CONF" = "x0" ]; then

		# Only RA

		ALL_DNS=$RA_DNS
		ALL_DOMAINS=$RA_DOMAINS
	elif [ "x$RA_CONF" = "x0" ]; then

		# Only DHCP

		ALL_DNS=$RDNSS
		ALL_DOMAINS=$DOMAINS
	else

		# RA and DHCP - Merge RA and DHCPv6

		for dns in $RDNSS; do
			local duplicate=0
			for radns in $RA_DNS; do
				[ "$radns" = "$dns" ] && duplicate=1
			done
			[ "$duplicate" = 0 ] && RA_DNS="$RA_DNS $dns"
		done

		for domain in $DOMAINS; do
			local duplicate=0
			for radomain in $RA_DOMAINS; do
				[ "$radomain" = "$domain" ] && duplicate=1
			done
			[ "$duplicate" = 0 ] && RA_DOMAINS="$RA_DOMAINS $domain"
		done

		ALL_DNS=$RA_DNS
		ALL_DOMAINS=$RA_DOMAINS
	fi

	dnscount=0
	for dns in $ALL_DNS; do
		dnscount=$((dnscount + 1))
	done

	if [ $dnscount -gt 1 ]; then
		NEW_DNS=""
		for dns in $ALL_DNS; do
			case $dns in
			        fe80*)
				;;
			        *)
					NEW_DNS="$NEW_DNS $dns"
					;;
			esac
		done
		dnscount=0
		for dns in $NEW_DNS; do
			dnscount=$((dnscount + 1))
		done
		[ $dnscount -ge 1 ] && ALL_DNS=$NEW_DNS
	fi
}




DHCP_ADDR=0
DHCP_CONF=0
RA_CONF=0

ALL_DNS=""
ALL_DOMAINS=""

get_config
merge_dhcp_ra

if [ "x$DEBUG" = "x1" ]; then
	echo
	echo New advertisement:
	echo "State:         $STATE"
	echo "DHCP Domains:  $DOMAINS"
	echo "DHCP DNSS:     $RDNSS"
	echo "RA Domains:    $RA_DOMAINS"
	echo "RA DNS:        $RA_DNS"
	echo "Joint domains: $ALL_DOMAINS"
	echo "Joint DNSS:    $ALL_DNS"
	echo
	echo "RA Routes:     $RA_ROUTES"
	echo "RA Adresses:   $RA_ADDRESSES"
	echo "DHCP Adresses: $ADDRESSES"
fi

case $STATE in
	bound)
		if [ "x$DHCP_ADDR" = "x1" ]; then
			teardown_interface "$IFACE"
			setup_interface "$IFACE"
		fi
		update_resolv
		;;
	informed|updated|rebound|ra-updated)
		if [ "x$DHCP_ADDR" = "x1" ]; then
			teardown_interface "$IFACE"
			setup_interface "$IFACE"
		fi
		update_resolv
		;;
	stopped|unbound)
		[ "x$DHCP_ADDR" = "x1" ] && teardown_interface "$IFACE"
		remove_v6_source_and_merge
		;;
	started)
		[ "x$DHCP_ADDR" = "x1" ] && teardown_interface "$IFACE"
		update_resolv
		;;
esac

exit 0
