#!/bin/sh
# shellcheck shell=ash

# Variables below are populated by sourced helpers and ss_load_config()
# shellcheck disable=SC2154

LC_ALL=C
IPKG_INSTROOT="${IPKG_INSTROOT:-}"
export IPKG_INSTROOT

# shellcheck disable=SC1091
. /lib/functions.sh
# shellcheck disable=SC1091
. /usr/lib/safeshield/core.sh

readonly SS_REFRESHD_LOCK='/var/lock/safeshield-refreshd.lock'
readonly SS_REFRESHD_LOCK_FD=308

ss_should_terminate=0
ss_sleep_pid=''

refreshd_lock() {
	mkdir -p "${SS_REFRESHD_LOCK%/*}" || return 1
	eval "exec ${SS_REFRESHD_LOCK_FD}>\"${SS_REFRESHD_LOCK}\"" || return 1
	flock -n "${SS_REFRESHD_LOCK_FD}" || return 1
}

refreshd_unlock() {
	flock -u "${SS_REFRESHD_LOCK_FD}" 2>/dev/null || true
	eval "exec ${SS_REFRESHD_LOCK_FD}>&-" 2>/dev/null || true
}

handle_term() {
	ss_should_terminate=1
	if [ -n "$ss_sleep_pid" ]; then
		kill "$ss_sleep_pid" 2>/dev/null || true
	fi
}

sleep_seconds() {
	local seconds="$1"
	local rc=0

	[ "$ss_should_terminate" -eq 1 ] && return 1
	[ "$seconds" -gt 0 ] 2>/dev/null || return 0

	sleep "$seconds" &
	ss_sleep_pid=$!
	wait "$ss_sleep_pid" 2>/dev/null || rc=$?
	ss_sleep_pid=''

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

wait_for_wan_if_needed() {
	local require_wan

	require_wan="$(ss_config_get config require_wan 1)"

	case "$require_wan" in
		1)
			ubus -t 30 wait_for network.interface.wan 2>/dev/null || true
			;;
	esac
}

status_epoch() {
	local key="$1"
	local value

	value="$(json get "$key" 2>/dev/null)"
	is_valid_integer "$value" || value=0
	printf '%s\n' "$value"
}

schedule_base_epoch() {
	local now="$1"
	local last_success last_attempt base=0

	last_success="$(status_epoch last_success)"
	last_attempt="$(status_epoch last_attempt)"

	if [ "$last_success" -gt 0 ] 2>/dev/null && [ "$last_success" -le "$now" ] 2>/dev/null; then
		base="$last_success"
	fi

	# A failed or currently-running refresh must also move the retry window
	# forward so refreshd cannot enter an immediate retry loop. A successful
	# refresh is naturally newer than its attempt timestamp and wins here.
	if [ "$last_attempt" -gt "$base" ] 2>/dev/null && [ "$last_attempt" -le "$now" ] 2>/dev/null; then
		base="$last_attempt"
	fi

	printf '%s\n' "$base"
}

has_recent_success() {
	local interval="$1"
	local now last_success age

	now="$(date +%s)"
	last_success="$(status_epoch last_success)"

	[ "$last_success" -gt 0 ] 2>/dev/null || return 1
	[ "$last_success" -le "$now" ] 2>/dev/null || return 1

	age=$((now - last_success))
	[ "$age" -lt "$interval" ] 2>/dev/null
}

set_next_refresh_epoch() {
	local epoch="$1"

	is_valid_integer "$epoch" || epoch=0
	ss_status_set next_refresh_at "$epoch"
}

clear_next_refresh_at() {
	ss_status_set next_refresh_at "0"
}

wait_until_refresh_due() {
	local interval="$1"
	local fallback_epoch="$2"
	local now base due remaining

	while :; do
		[ "$ss_should_terminate" -eq 1 ] && return 1

		now="$(date +%s)"
		base="$(schedule_base_epoch "$now")"
		if [ "$base" -le 0 ] 2>/dev/null; then
			base="$fallback_epoch"
		fi

		due=$((base + interval))
		remaining=$((due - now))

		if [ "$remaining" -le 0 ] 2>/dev/null; then
			clear_next_refresh_at
			return 0
		fi

		set_next_refresh_epoch "$due"
		sleep_seconds "$remaining" || return 1

		# A manual refresh may have completed while refreshd was sleeping.
		# Recalculate from the latest last_success/last_attempt before running.
	done
}

main() {
	local interval
	local boot_delay
	local refresh_on_boot
	local schedule_anchor

	refreshd_lock || exit 0

	ss_load_config || exit 1
	if [ ! -s "${RUNNING_STATUS_FILE}" ]; then
		ss_status_reset || true
	fi
	ss_sync_blocklist_status
	ss_status_set status "idle"
	ss_status_set stage "starting"

	interval="$(ss_config_get config refresh_interval_s 28800)"
	boot_delay="$(ss_config_get config boot_start_delay_s 30)"
	refresh_on_boot="$(ss_config_get config refresh_on_boot 1)"

	is_valid_integer "$interval" || interval=28800
	[ "$interval" -gt 0 ] 2>/dev/null || interval=28800

	is_valid_integer "$boot_delay" || boot_delay=30

	wait_for_wan_if_needed
	ss_status_set stage "boot_delay"
	set_next_refresh_epoch "$(($(date +%s) + boot_delay))"
	sleep_seconds "$boot_delay" || exit 0

	if [ "$refresh_on_boot" = "1" ] && [ "${ss_enabled}" = "1" ]; then
		if has_recent_success "$interval"; then
			log_info "Skipping boot refresh because a recent refresh already succeeded"
			ss_status_set status "idle"
			ss_status_set stage "boot_refresh_skipped"
		else
			ss_status_set status "running"
			ss_status_set stage "boot_refresh"
			clear_next_refresh_at
			safeshield_force_download
		fi
	fi

	schedule_anchor="$(date +%s)"

	while :; do
		ss_load_config || {
			schedule_anchor="$(date +%s)"
			sleep_seconds 5 || break
			continue
		}

		interval="$(ss_config_get config refresh_interval_s 28800)"
		is_valid_integer "$interval" || interval=28800
		[ "$interval" -gt 0 ] 2>/dev/null || interval=28800

		if [ "${ss_enabled}" != "1" ]; then
			ss_status_set status "disabled"
			ss_status_set stage "disabled"
			clear_next_refresh_at
			sleep_seconds "$interval" || break
			schedule_anchor="$(date +%s)"
			continue
		fi

		ss_status_set status "idle"
		ss_status_set stage "scheduled_wait"
		wait_until_refresh_due "$interval" "$schedule_anchor" || break

		ss_load_config || {
			schedule_anchor="$(date +%s)"
			continue
		}

		if [ "${ss_enabled}" != "1" ]; then
			ss_status_set status "disabled"
			ss_status_set stage "disabled"
			clear_next_refresh_at
			schedule_anchor="$(date +%s)"
			continue
		fi

		wait_for_wan_if_needed || true

		ss_status_set status "running"
		ss_status_set stage "scheduled_refresh"
		clear_next_refresh_at
		safeshield_force_download
		schedule_anchor="$(date +%s)"
	done

	ss_load_config >/dev/null 2>&1 || true
	if [ "${ss_enabled:-0}" = "1" ]; then
		ss_status_set status "idle"
		ss_status_set stage "stopped"
	else
		ss_status_set status "disabled"
		ss_status_set stage "disabled"
	fi
	clear_next_refresh_at
}

trap "handle_term" TERM INT
trap "refreshd_unlock; clear_next_refresh_at" EXIT

main "$@"
