#!/usr/bin/env bash
set -euo pipefail

nala_bin="${1:-${CARGO_TARGET_DIR:-target}/debug/nala}"
package="nala-history-test"
fail_package="nala-history-fail"
interrupt_package="nala-history-interrupt"
interrupt_marker="/tmp/${interrupt_package}.started"

[[ -x "${nala_bin}" ]] || { echo "Missing Nala binary: ${nala_bin}" >&2; exit 1; }
[[ "$(id -u)" == "0" ]] || { echo "History tests need root." >&2; exit 1; }

work_dir="$(mktemp -d)"
chmod a+rx "${work_dir}"
history_dir="${work_dir}/state/history"
legacy_path="${history_dir}.json"
repo_dir="${work_dir}/repo"
archive_dir="${work_dir}/archives"
sources_list="${work_dir}/sources.list"
config_file="${work_dir}/nala.conf"
server_pid=
ambient_auto=()

cleanup() {
	[[ -z "${server_pid}" ]] || kill "${server_pid}" 2>/dev/null || true
	dpkg --purge "${package}" "${fail_package}" "${interrupt_package}" >/dev/null 2>&1 || true
	((${#ambient_auto[@]} == 0)) || apt-mark auto "${ambient_auto[@]}" >/dev/null 2>&1 || true
	rm -f "${interrupt_marker}"
	rm -f /etc/nala-history-test.conf.dpkg-*
	rm -rf "${work_dir}"
}
trap cleanup EXIT

mkdir -p "${history_dir}" "${repo_dir}" "${archive_dir}/partial"
cp tests/deb/history/legacy.json "${legacy_path}"
cp tests/deb/history/rust-v1.json "${history_dir}/7.json"

legacy_hash="$(sha256sum "${legacy_path}")"
rust_hash="$(sha256sum "${history_dir}/7.json")"

deb_path="${repo_dir}/${package}_1.0_all.deb"
deb_v2_path="${repo_dir}/${package}_2.0_all.deb"
fail_deb_path="${repo_dir}/${fail_package}_1.0_all.deb"
interrupt_deb_path="${repo_dir}/${interrupt_package}_1.0_all.deb"
dpkg-deb --root-owner-group --build tests/deb/packages/nala-history-test-v1 "${deb_path}" >/dev/null
dpkg-deb --root-owner-group --build tests/deb/packages/nala-history-test-v2 "${deb_v2_path}" >/dev/null
dpkg-deb --root-owner-group --build tests/deb/packages/nala-history-fail "${fail_deb_path}" >/dev/null
dpkg-deb --root-owner-group --build tests/deb/packages/nala-history-interrupt "${interrupt_deb_path}" >/dev/null
(
	cd "${repo_dir}"
	dpkg-scanpackages --multiversion . /dev/null >Packages
) 2>/dev/null
python3 -m http.server 8765 --bind 127.0.0.1 --directory "${repo_dir}" >/dev/null 2>&1 &
server_pid=$!
until python3 -c 'import urllib.request; urllib.request.urlopen("http://127.0.0.1:8765/Packages")' 2>/dev/null; do sleep 0.1; done
printf 'deb [trusted=yes] http://127.0.0.1:8765 ./\n' >"${sources_list}"
printf 'Nala = { assume_yes = true }\n' >"${config_file}"

apt_options=(
	-o "Dir::Etc::sourcelist=${sources_list}"
	-o "Dir::Etc::sourceparts=-"
	-o "Dir::Cache::Archives=${archive_dir}/"
)

apt-get "${apt_options[@]}" update >/dev/null
mapfile -t ambient_auto < <(
	LC_ALL=C apt-get -s autoremove | sed -n 's/^Remv \([^ ]*\).*/\1/p'
)
((${#ambient_auto[@]} == 0)) || apt-mark manual "${ambient_auto[@]}" >/dev/null

nala() {
	NALA_HISTORY_DIR="${history_dir}" "${nala_bin}" --config "${config_file}" \
		--no-tui --color never "${apt_options[@]}" "$@"
}

history_entry_count() {
	find "${history_dir}" -maxdepth 1 -type f -name '[0-9]*.json' -printf x | wc -c
}

history="$(runuser -u nobody -- env NALA_HISTORY_DIR="${history_dir}" \
	"${nala_bin}" --config "${config_file}" --no-tui --color never history)"
grep -Fq "legacy-user (1000)" <<<"${history}"
grep -Fq "beta full-upgrade" <<<"${history}"
[[ "$(sha256sum "${legacy_path}")" == "${legacy_hash}" ]]
[[ "$(sha256sum "${history_dir}/7.json")" == "${rust_hash}" ]]
[[ ! -e "${history_dir}/.legacy-history-handled" ]]

nala install --assume-yes "${package}=1.0"
[[ "$(dpkg-query -W -f='${Status} ${Version}' "${package}")" == "install ok installed 1.0" ]]
[[ "$(sha256sum "${legacy_path}")" == "${legacy_hash}" ]]

nala history >/dev/null
python3 tests/deb/history/verify.py "${history_dir}" \
	Upgrade Install Remove Purge Upgrade Upgrade Install
[[ "$(sha256sum "${legacy_path}")" == "${legacy_hash}" ]]

echo "PASS: real install imported mixed history without changing the legacy source."

nala remove --assume-yes "${package}" >/dev/null
[[ "$(dpkg-query -W -f='${db:Status-Abbrev}' "${package}")" == "rc " ]]
[[ -e /etc/nala-history-test.conf ]]
nala install --assume-yes "${package}=1.0" >/dev/null
nala purge --assume-yes "${package}" >/dev/null
! dpkg-query -W "${package}" >/dev/null 2>&1
[[ ! -e /etc/nala-history-test.conf ]]
nala install --assume-yes "${package}=1.0" >/dev/null
apt-mark auto "${package}" >/dev/null
nala autoremove --assume-yes >/dev/null
[[ "$(dpkg-query -W -f='${db:Status-Abbrev}' "${package}")" == "rc " ]]
[[ -e /etc/nala-history-test.conf ]]
python3 tests/deb/history/verify.py "${history_dir}" \
	Upgrade Install Remove Purge Upgrade Upgrade Install Remove Install Purge Install AutoRemove

echo "PASS: remove, purge, and autoremove history match dpkg state."

history_dir="${work_dir}/replay/history"
legacy_path="${history_dir}.json"
mkdir -p "${history_dir}"
cp tests/deb/history/legacy.json "${legacy_path}"
replay_hash="$(sha256sum "${legacy_path}")"

dpkg -i "${deb_path}" >/dev/null
nala history undo 2 >/dev/null
! dpkg-query -W "${package}" >/dev/null 2>&1
nala history redo 2 >/dev/null
[[ "$(dpkg-query -W -f='${Version}' "${package}")" == "1.0" ]]

dpkg --remove "${package}" >/dev/null
nala history undo 3 >/dev/null
[[ "$(dpkg-query -W -f='${Version}' "${package}")" == "1.0" ]]
nala history redo 3 >/dev/null
[[ "$(dpkg-query -W -f='${db:Status-Abbrev}' "${package}")" == "rc " ]]

dpkg --purge "${package}" >/dev/null
nala history undo 4 >/dev/null
[[ "$(dpkg-query -W -f='${Version}' "${package}")" == "1.0" ]]
nala history redo 4 >/dev/null
! dpkg-query -W "${package}" >/dev/null 2>&1

dpkg -i "${deb_v2_path}" >/dev/null
nala history undo 5 >/dev/null
[[ "$(dpkg-query -W -f='${Version}' "${package}")" == "1.0" ]]
nala history redo 5 >/dev/null
[[ "$(dpkg-query -W -f='${Version}' "${package}")" == "2.0" ]]
[[ "$(sha256sum "${legacy_path}")" == "${replay_hash}" ]]
python3 tests/deb/history/verify.py "${history_dir}" \
	Upgrade Install Remove Purge Upgrade Remove Install Install Remove Install Purge Downgrade Upgrade

echo "PASS: imported install, remove, purge, and upgrade entries undo and redo."

history_dir="${work_dir}/clear/history"
legacy_path="${history_dir}.json"
mkdir -p "${history_dir}"
cp tests/deb/history/legacy.json "${legacy_path}"
clear_hash="$(sha256sum "${legacy_path}")"
nala history clear 1 >/dev/null
[[ -e "${history_dir}/.legacy-history-handled" ]]
[[ ! -e "${history_dir}/1.json" && -e "${history_dir}/2.json" ]]
[[ "$(sha256sum "${legacy_path}")" == "${clear_hash}" ]]
nala history clear --all >/dev/null
[[ -z "$(find "${history_dir}" -maxdepth 1 -type f -name '[0-9]*.json' -print -quit)" ]]
[[ -e "${history_dir}/.legacy-history-handled" ]]
[[ "$(sha256sum "${legacy_path}")" == "${clear_hash}" ]]

echo "PASS: clear removes one imported entry or all entries without changing legacy history."

dpkg --purge "${package}" >/dev/null 2>&1 || true
history_dir="${work_dir}/operations/history"
mkdir -p "${history_dir}"

nala update >/dev/null
[[ "$(history_entry_count)" == "0" ]]

nala install "${package}=1.0" >/dev/null
[[ "$(dpkg-query -W -f='${Version}' "${package}")" == "1.0" ]]
nala upgrade --no-update >/dev/null
[[ "$(dpkg-query -W -f='${Version}' "${package}")" == "2.0" ]]
python3 tests/deb/history/verify.py --native "${history_dir}" Install Upgrade
python3 - "${history_dir}/2.json" <<'PY'
import json
import sys

entry = json.load(open(sys.argv[1]))
package, = entry["packages"]
assert (package["name"], package["operation"]) == ("nala-history-test", "Upgrade")
assert (package["before"]["version"], package["after"]["version"]) == ("1.0", "2.0")
PY

echo "PASS: update creates no history and Nala records the real v1-to-v2 upgrade."

dpkg --purge "${package}" >/dev/null
history_dir="${work_dir}/download/history"
mkdir -p "${history_dir}"
find "${archive_dir}" -maxdepth 1 -type f -name '*.deb' -delete

nala install --download-only "${package}=1.0" >/dev/null
! dpkg-query -W "${package}" >/dev/null 2>&1
[[ "$(history_entry_count)" == "0" ]]
[[ -f "${archive_dir}/$(basename "${deb_path}")" ]]

rm -f "${archive_dir}"/*.deb
direct_hash="$(sha256sum "${deb_v2_path}" | cut -d' ' -f1)"
direct_url="http://127.0.0.1:8765/$(basename "${deb_v2_path}"):${direct_hash}"
nala install "${direct_url}" >/dev/null
[[ "$(dpkg-query -W -f='${Version}' "${package}")" == "2.0" ]]
python3 tests/deb/history/verify.py --native "${history_dir}" Install

echo "PASS: download-only avoids mutation and direct URL installation records history."

dpkg --purge "${package}" >/dev/null
history_dir="${work_dir}/refusal/history"
mkdir -p "${history_dir}"
if nala install -n "${package}=1.0" >/dev/null 2>&1; then
	echo "Assume-no unexpectedly allowed package mutation." >&2
	exit 1
fi
! dpkg-query -W "${package}" >/dev/null 2>&1
[[ "$(history_entry_count)" == "0" ]]

echo "PASS: assume-no leaves package state and history untouched."

history_dir="${work_dir}/hooks/history"
mkdir -p "${history_dir}"

if nala -o "DPkg::Pre-Invoke::=exit 23" install "${package}=1.0" >/dev/null 2>&1; then
	echo "Failing pre-hook unexpectedly allowed package mutation." >&2
	exit 1
fi
! dpkg-query -W "${package}" >/dev/null 2>&1
[[ "$(history_entry_count)" == "0" ]]

if nala -o "DPkg::Post-Invoke::=exit 24" install "${package}=1.0" >/dev/null 2>&1; then
	echo "Failing post-hook unexpectedly succeeded." >&2
	exit 1
fi
[[ "$(dpkg-query -W -f='${Version}' "${package}")" == "1.0" ]]
python3 tests/deb/history/verify.py --native "${history_dir}" Install

echo "PASS: pre-hook failure blocks mutation; post-hook failure preserves state and history."

dpkg --purge "${package}" >/dev/null
rm -f /etc/nala-history-test.conf.dpkg-*
dpkg -i "${deb_path}" >/dev/null
printf 'locally modified\n' >/etc/nala-history-test.conf
history_dir="${work_dir}/conffile/history"
legacy_path="${history_dir}.json"
mkdir -p "${history_dir}"
cp tests/deb/history/legacy.json "${legacy_path}"
conffile_legacy_hash="$(sha256sum "${legacy_path}")"

nala -o "Dpkg::Options::=--force-confold" upgrade --no-update >/dev/null
[[ "$(dpkg-query -W -f='${Version}' "${package}")" == "2.0" ]]
grep -Fqx "locally modified" /etc/nala-history-test.conf
grep -Fqx "nala history integration fixture version 2" \
	/etc/nala-history-test.conf.dpkg-dist
[[ "$(sha256sum "${legacy_path}")" == "${conffile_legacy_hash}" ]]
python3 tests/deb/history/verify.py "${history_dir}" \
	Upgrade Install Remove Purge Upgrade Upgrade

echo "PASS: retained conffile survives upgrade while legacy history imports."

dpkg --purge "${package}" >/dev/null
history_dir="${work_dir}/conversion-failure/history"
legacy_path="${history_dir}.json"
mkdir -p "${history_dir}"
cp tests/deb/history/rust-v1.json "${history_dir}/7.json"
printf '{\n' >"${legacy_path}"
failed_legacy_hash="$(sha256sum "${legacy_path}")"
failed_rust_hash="$(sha256sum "${history_dir}/7.json")"
if nala install "${package}=1.0" >/dev/null 2>&1; then
	echo "Malformed legacy conversion unexpectedly allowed package mutation." >&2
	exit 1
fi
! dpkg-query -W "${package}" >/dev/null 2>&1
[[ "$(sha256sum "${legacy_path}")" == "${failed_legacy_hash}" ]]
[[ "$(sha256sum "${history_dir}/7.json")" == "${failed_rust_hash}" ]]
[[ ! -e "${history_dir}/.legacy-history-handled" ]]

echo "PASS: failed conversion blocks package mutation and leaves both stores untouched."

history_dir="${work_dir}/dpkg-failure/history"
mkdir -p "${history_dir}"
if nala install "${fail_package}" >/dev/null 2>&1; then
	echo "Failing postinst unexpectedly succeeded." >&2
	exit 1
fi
[[ "$(history_entry_count)" == "0" ]]
dpkg --purge "${fail_package}" >/dev/null 2>&1 || true

history_dir="${work_dir}/interrupted/history"
mkdir -p "${history_dir}"
if (
	for _ in {1..200}; do
		[[ ! -e "${interrupt_marker}" ]] || break
		sleep 0.05
	done
	printf '\003'
) | nala install "${interrupt_package}" >/dev/null 2>&1; then
	echo "Interrupted postinst unexpectedly succeeded." >&2
	exit 1
fi
[[ -e "${interrupt_marker}" ]]
[[ "$(history_entry_count)" == "0" ]]

echo "PASS: failed and interrupted dpkg runs record no successful history entry."
