#!/bin/sh
# 从 cpe.gogogofuture.com/git 拉 manifest + ipk（Gitea raw；已弃用 GitHub / git.ai101.eu.org）。
# 并行探测，失败忽略，取版本号最新者。

[ -f /usr/share/gogogo/defaults.sh ] && . /usr/share/gogogo/defaults.sh
[ -f /usr/share/gogogo/curl-resolve.sh ] && . /usr/share/gogogo/curl-resolve.sh

CPE_PKG_NAME="${CPE_PKG_NAME:-luci-app-gogogo}"
CPE_SERVICE="${CPE_SERVICE:-gogogo}"
UPDATE_MANIFEST="${CPE_UPDATE_MANIFEST:-/software/gogogo/version.json}"
GITHUB_MANIFEST_PATH="${CPE_GITHUB_MANIFEST_PATH:-main/release/version.json}"
UPDATE_LOCK="/tmp/gogogo_update.lock"
UPDATE_IPK="/tmp/gogogo_update.ipk"
CURL_TIMEOUT="${CURL_TIMEOUT:-8}"
GIT_BASE_DEFAULT="https://cpe.gogogofuture.com/git"

_log() {
	echo "gogogo-update: $*" >&2
}

_github_repo() {
	echo "${CPE_GITHUB_REPO:-gogogofuture/luci-app-gogogo}" | sed 's|^/||;s|/*$||'
}

_git_base() {
	echo "${CPE_GIT_BASE:-$GIT_BASE_DEFAULT}" | sed 's|/*$||'
}

_abs_url() {
	local base="$1" path="$2"
	case "$path" in
		http://*|https://*) echo "$path" ;;
		/*) echo "${base%/}${path}" ;;
		*) echo "${base%/}/${path}" ;;
	esac
}

_release_tag() {
	case "$1" in
		v*) echo "$1" ;;
		*) echo "v$1" ;;
	esac
}

_installed_version() {
	local v ctrl="/usr/lib/opkg/info/${CPE_PKG_NAME}.control"
	v=$(opkg list-installed "$CPE_PKG_NAME" 2>/dev/null | awk '{print $3}')
	[ -n "$v" ] || v=$(grep '^Version:' "$ctrl" 2>/dev/null | awk '{print $2}')
	echo "$v" | sed 's/-.*//'
}

_source_rank() {
	case "$1" in
		git:*) echo 2 ;;
		base:*) echo 1 ;;
		*) echo 0 ;;
	esac
}

_version_gt() {
	local new="$1" old="$2"
	[ -n "$new" ] && [ -n "$old" ] || return 1
	case "$new" in
		*[!0-9]*) return 1 ;;
	esac
	case "$old" in
		*[!0-9]*) return 0 ;;
	esac
	[ "$new" -gt "$old" ] 2>/dev/null
}

_fetch_body() {
	local url="$1"
	[ -n "$url" ] || return 1
	gogogo_curl "$url" --connect-timeout "$CURL_TIMEOUT" --max-time "$((CURL_TIMEOUT * 3))" \
		--http1.1 -fsSL 2>/dev/null
}

_manifest_valid() {
	echo "$1" | jq -e '.version and .url and .sha256' >/dev/null 2>&1
}

_manifest_version() {
	echo "$1" | jq -r '.version // empty' 2>/dev/null
}

CAND_DIR=""
_cand_init() {
	CAND_DIR="/tmp/gogogo_update_cand.$$"
	rm -rf "$CAND_DIR"
	mkdir -p "$CAND_DIR"
}

_cand_add_file() {
	local src="$1" url="$2" body="$3" id="$4" compact ver
	_manifest_valid "$body" || return 1
	compact=$(echo "$body" | jq -c . 2>/dev/null) || return 1
	ver=$(_manifest_version "$compact")
	[ -n "$ver" ] || return 1
	printf '%s\n%s\n%s\n' "$src" "$compact" "$url" >"$CAND_DIR/$id"
	_log "manifest ok ($src) version=$ver"
	return 0
}

_try_manifest() {
	local src="$1" url="$2" id="$3" body
	body=$(_fetch_body "$url") || return 1
	_cand_add_file "$src" "$url" "$body" "$id"
}

_origin_manifest_urls() {
	local repo base
	repo=$(_github_repo)
	base=$(_git_base)
	printf '%s\n' \
		"${base}/${repo}/releases/download/latest/version.json" \
		"${base}/${repo}/raw/branch/main/release/version.json"
}

_collect_manifest_candidates() {
	local repo base m url prefix i=0 origin kind wrapped
	_cand_init
	repo=$(_github_repo)

	base=$(gogogo_get_base_url)
	if [ -n "$base" ]; then
		url=$(_abs_url "$base" "$UPDATE_MANIFEST")
		i=$((i + 1))
		_try_manifest "base:${base}" "$url" "$(printf '%03d' "$i")" &
	fi

	for origin in $(_origin_manifest_urls); do
		i=$((i + 1))
		_try_manifest "git:${origin}" "$origin" "$(printf '%03d' "$i")" &
	done
	wait

	ls "$CAND_DIR" >/dev/null 2>&1 || return 1
	set -- "$CAND_DIR"/*
	[ -f "$1" ] || return 1
	return 0
}

_pick_newest_manifest() {
	local best_src="" best_m="" best_url="" best_ver="" best_rank=0 src m url ver rank f

	for f in "$CAND_DIR"/*; do
		[ -f "$f" ] || continue
		src=$(sed -n '1p' "$f")
		m=$(sed -n '2p' "$f")
		url=$(sed -n '3p' "$f")
		[ -n "$src" ] && [ -n "$m" ] || continue
		_manifest_valid "$m" || continue
		ver=$(_manifest_version "$m")
		[ -n "$ver" ] || continue
		rank=$(_source_rank "$src")
		if [ -z "$best_ver" ] || _version_gt "$ver" "$best_ver"; then
			best_ver="$ver"
			best_src="$src"
			best_m="$m"
			best_url="$url"
			best_rank="$rank"
		elif [ "$ver" = "$best_ver" ] && [ "$rank" -gt "$best_rank" ]; then
			best_src="$src"
			best_m="$m"
			best_url="$url"
			best_rank="$rank"
		fi
	done
	rm -rf "$CAND_DIR"
	[ -n "$best_m" ] && [ -n "$best_src" ] || return 1
	_log "manifest picked ($best_src) version=$best_ver"
	printf '%s\n%s\n%s\n' "$best_m" "$best_src" "$best_url"
	return 0
}

_ipk_url_list() {
	local ver="$1" ipk_name="$2" win_url="$3" src="$4" repo base
	repo=$(_github_repo)
	base=$(_git_base)

	# 1) 与命中 manifest 同目录
	case "$win_url" in
		*version.json)
			echo "${win_url%version.json}${ipk_name}"
			;;
	esac

	# 2) base_url 相对路径
	case "$src" in
		base:*)
			echo "$(_abs_url "${src#base:}" "$ipk_name")"
			;;
	esac

	echo "${base}/${repo}/releases/download/latest/${ipk_name}"
	echo "${base}/${repo}/raw/branch/main/release/${ipk_name}"
}

_uniq_urls() {
	awk 'NF && !seen[$0]++ { print }'
}

_download_ipk() {
	local ver="$1" ipk_name="$2" win_url="$3" src="$4" sha="$5" url list
	rm -f "$UPDATE_IPK"
	list="/tmp/gogogo_update_ipk_urls.$$"
	_ipk_url_list "$ver" "$ipk_name" "$win_url" "$src" | _uniq_urls >"$list"
	while read -r url; do
		[ -n "$url" ] || continue
		_log "try ipk $url"
		if gogogo_curl "$url" --connect-timeout "$CURL_TIMEOUT" --max-time 300 \
			--http1.1 -fSL -o "$UPDATE_IPK" 2>/dev/null; then
			if echo "$sha  $UPDATE_IPK" | sha256sum -c - >/dev/null 2>&1; then
				_log "ipk ok from $url"
				rm -f "$list"
				return 0
			fi
			_log "sha256 mismatch from $url"
			rm -f "$UPDATE_IPK"
		fi
	done <"$list"
	rm -f "$list" "$UPDATE_IPK"
	return 1
}

_check_update() {
	local lines manifest src_hint win_url new url sha cur

	if [ -x /usr/sbin/gogogo-pin-github ]; then
		/usr/sbin/gogogo-pin-github apply >/dev/null 2>&1
		/usr/sbin/gogogo-pin-github refresh >/dev/null 2>&1 &
	fi

	[ "${CPE_AUTO_UPDATE:-0}" = "1" ] || {
		_log "auto update disabled"
		return 2
	}

	command -v jq >/dev/null 2>&1 || {
		_log "jq required"
		return 2
	}

	_collect_manifest_candidates || {
		_log "manifest fetch failed (all sources)"
		return 2
	}

	lines=$(_pick_newest_manifest) || {
		_log "no valid manifest from any source"
		return 2
	}
	manifest=$(echo "$lines" | sed -n '1p')
	src_hint=$(echo "$lines" | sed -n '2p')
	win_url=$(echo "$lines" | sed -n '3p')

	new=$(echo "$manifest" | jq -r '.version // empty')
	url=$(echo "$manifest" | jq -r '.url // empty')
	sha=$(echo "$manifest" | jq -r '.sha256 // empty')

	[ -n "$new" ] && [ -n "$url" ] && [ -n "$sha" ] || {
		_log "invalid manifest"
		return 2
	}

	cur=$(_installed_version)
	[ -n "$cur" ] || cur="0"

	if ! _version_gt "$new" "$cur"; then
		_log "already latest ($cur)"
		return 0
	fi

	_log "upgrade $cur -> $new"
	if ! _download_ipk "$new" "$url" "$win_url" "$src_hint" "$sha"; then
		_log "ipk download failed"
		rm -f "$UPDATE_IPK"
		return 2
	fi

	[ -x /usr/sbin/gogogo-kill ] && /usr/sbin/gogogo-kill

	if ! opkg install --force-reinstall "$UPDATE_IPK" >/dev/null 2>&1; then
		_log "opkg install failed"
		rm -f "$UPDATE_IPK"
		return 2
	fi

	rm -f "$UPDATE_IPK"

	if [ -x "/etc/init.d/${CPE_SERVICE}" ]; then
		"/etc/init.d/${CPE_SERVICE}" enable 2>/dev/null
		"/etc/init.d/${CPE_SERVICE}" start 2>/dev/null
	fi

	_log "upgraded to $new"
	return 1
}

case "${1:-check}" in
	check)
		(
			flock -n 9 || {
				_log "update in progress"
				exit 2
			}
			_check_update
		) 9>"$UPDATE_LOCK"
		;;
	*)
		echo "Usage: $0 check" >&2
		exit 2
		;;
esac
