#!/bin/bash
#
# l4s-upgrade - safe Fedora major-version upgrade for Linux 4 Switch
#
# Plain `dnf system-upgrade` is unsafe on L4S: the device's kernel, NVIDIA BSP,
# pinned X.Org server and ffmpeg all come from the linux4switch/l4s COPR. If the
# COPR has not been built for the target Fedora release yet, dnf can only resolve
# the transaction with --allowerasing, which silently REMOVES that L4S stack and
# leaves an unbootable, GPU-less system. This wrapper refuses to upgrade unless
# the COPR is ready for the target release, and never erases L4S packages.
#
set -euo pipefail

COPR_REPOID="copr:copr.fedorainfracloud.org:linux4switch:l4s"
PROG="${0##*/}"

die()  { echo "E: $*" >&2; exit 1; }
info() { echo "==> $*"; }

[[ ${EUID} -eq 0 ]] || die "${PROG} must run as root (try: sudo ${PROG} <release>)"

TARGET="${1:-}"
[[ "${TARGET}" =~ ^[0-9]+$ ]] || die "usage: ${PROG} <target-fedora-release>  (e.g. ${PROG} 44)"

CURRENT="$(. /etc/os-release && echo "${VERSION_ID}")"
[[ "${TARGET}" -gt "${CURRENT}" ]] || die "target F${TARGET} is not newer than current F${CURRENT}"
info "Planning upgrade: Fedora ${CURRENT} -> ${TARGET}"

# 1. Make sure the system-upgrade plugin is available.
if ! dnf system-upgrade --help >/dev/null 2>&1; then
	info "Installing dnf5-plugin-system-upgrade"
	dnf install -y dnf5-plugin-system-upgrade
fi

# 2. PROTECTED set = installed packages that the l4s COPR provides. These must
#    NOT be erased and MUST have a candidate from the COPR at the target release.
info "Determining L4S (COPR) packages installed on this system"
mapfile -t COPR_PKGS < <(dnf repoquery --quiet --repoid="${COPR_REPOID}" --available --qf '%{name}\n' 2>/dev/null | sort -u)
[[ ${#COPR_PKGS[@]} -gt 0 ]] || die "could not read the l4s COPR package list (network/repo issue)"

declare -A IS_COPR=()
for p in "${COPR_PKGS[@]}"; do IS_COPR[$p]=1; done

mapfile -t PROTECTED < <(rpm -qa --qf '%{name}\n' | sort -u | while read -r n; do
	[[ -n "${IS_COPR[$n]:-}" ]] && echo "$n"
done)
[[ ${#PROTECTED[@]} -gt 0 ]] || die "no L4S COPR packages appear installed - is this an L4S system?"
info "${#PROTECTED[@]} protected L4S package(s) installed"

# 3. Readiness: every protected package must be available FROM THE COPR at the
#    target release. (skip_if_unavailable makes a not-yet-built COPR chroot read
#    as empty, so a missing target build => everything reports missing => abort.)
info "Checking the l4s COPR is built for Fedora ${TARGET} ..."
mapfile -t TARGET_COPR < <(dnf repoquery --quiet --releasever="${TARGET}" \
	--repoid="${COPR_REPOID}" --available --qf '%{name}\n' 2>/dev/null | sort -u)
declare -A HAVE_TARGET=()
for p in "${TARGET_COPR[@]}"; do HAVE_TARGET[$p]=1; done

MISSING=()
for p in "${PROTECTED[@]}"; do
	[[ -n "${HAVE_TARGET[$p]:-}" ]] || MISSING+=("$p")
done

if [[ ${#MISSING[@]} -gt 0 ]]; then
	echo >&2
	echo "E: the linux4switch/l4s COPR is NOT ready for Fedora ${TARGET}." >&2
	echo "E: ${#MISSING[@]} installed L4S package(s) have no Fedora ${TARGET} build yet:" >&2
	printf '     - %s\n' "${MISSING[@]}" >&2
	echo "E: Aborting to avoid bricking the device. Try again once the COPR is built." >&2
	exit 2
fi
info "COPR is ready for Fedora ${TARGET} (all ${#PROTECTED[@]} protected packages present)"

# 4. Depsolve dry-run WITH --allowerasing. A real major upgrade needs
#    --allowerasing to drop obsolete leaf packages (e.g. an emulator built
#    against a soname Fedora N+2 no longer ships). The guard's job is NOT to
#    forbid --allowerasing, but to guarantee it never removes a PROTECTED L4S
#    package - that, not leaf churn, is the brick scenario.
info "Dry-run depsolve for Fedora ${TARGET} (no changes made) ..."
DRYLOG="$(mktemp)"; trap 'rm -f "${DRYLOG}"' EXIT
dnf distro-sync --releasever="${TARGET}" --allowerasing --assumeno >"${DRYLOG}" 2>&1 || true

if ! grep -qiE 'Transaction Summary|Nothing to do' "${DRYLOG}"; then
	cp -f "${DRYLOG}" /var/log/l4s-upgrade-dryrun.log 2>/dev/null || true
	echo >&2
	echo "E: could not compute a Fedora ${TARGET} transaction (unresolved deps)." >&2
	echo "E: See /var/log/l4s-upgrade-dryrun.log. Aborting." >&2
	exit 3
fi

# Names dnf plans to remove (rows under the "Removing[ dependent packages]:"
# section are indented; any column-0 line ends the section).
mapfile -t REMOVED < <(awk '
	/^Removing/      { inrm=1; next }
	/^[^[:space:]]/  { inrm=0 }
	inrm && NF       { print $1 }
' "${DRYLOG}" | sort -u)

ERASED=()
for p in "${PROTECTED[@]}"; do
	for r in "${REMOVED[@]}"; do [[ "${r}" == "${p}" ]] && { ERASED+=("${p}"); break; }; done
done
if [[ ${#ERASED[@]} -gt 0 ]]; then
	cp -f "${DRYLOG}" /var/log/l4s-upgrade-dryrun.log 2>/dev/null || true
	echo >&2
	echo "E: the Fedora ${TARGET} transaction would REMOVE protected L4S package(s):" >&2
	printf '     - %s\n' "${ERASED[@]}" >&2
	echo "E: This is the brick scenario. Aborting (full log: /var/log/l4s-upgrade-dryrun.log)." >&2
	echo "E: Rebuild these in the COPR for Fedora ${TARGET} first." >&2
	exit 3
fi
if [[ ${#REMOVED[@]} -gt 0 ]]; then
	echo "W: these non-L4S packages will be REMOVED to resolve the upgrade:" >&2
	printf '     - %s\n' "${REMOVED[@]}" >&2
fi
info "Dry-run safe: no protected L4S package would be removed"

# 5. Proceed with the real download (still does not apply until reboot).
info "Downloading the Fedora ${TARGET} upgrade ..."
dnf system-upgrade download --releasever="${TARGET}" --allowerasing

cat <<EOF

==> Fedora ${TARGET} upgrade staged successfully.
    Review the transaction above, then reboot to apply:

        sudo dnf system-upgrade reboot

EOF
