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

nala_bin="${1:-${CARGO_TARGET_DIR:-target}/debug/nala}"

if [[ ! -x "${nala_bin}" ]]; then
	echo "Missing executable Nala binary: ${nala_bin}" >&2
	echo "Build one first or pass a binary path." >&2
	exit 1
fi
nala_bin="$(realpath "${nala_bin}")"

if [[ "$(id -u)" != "0" ]]; then
	echo "Deb tests need root because Nala requires root for install." >&2
	exit 1
fi

work_dir="$(mktemp -d)"
trap 'rm -rf "${work_dir}"' EXIT
chmod a+rx "${work_dir}"

policy_status="${work_dir}/policy.status"
dpkg-query --status dpkg >"${policy_status}"
mkdir "${work_dir}/policy-lists"
policy_options=(
	-o "Dir::State::status=${policy_status}"
	-o "Dir::State::lists=${work_dir}/policy-lists"
	-o "Dir::Etc::sourcelist=/dev/null"
	-o "Dir::Etc::sourceparts=-"
)
for format in text json; do
	format_options=()
	if [[ "${format}" == json ]]; then
		format_options=(--machine)
	fi
	"${nala_bin}" --no-tui --color never "${policy_options[@]}" \
		policy "${format_options[@]}" dpkg >"${work_dir}/policy-${format}-package"
	"${nala_bin}" --no-tui --color never "${policy_options[@]}" \
		policy "${format_options[@]}" >"${work_dir}/policy-${format}-global"
	grep -Fq "${policy_status}" "${work_dir}/policy-${format}-package"
	grep -Fq "${policy_status}" "${work_dir}/policy-${format}-global"
done
python3 - "${work_dir}" "${policy_status}" <<'PY'
import json, pathlib, sys
root, status = pathlib.Path(sys.argv[1]), sys.argv[2]
package = json.loads((root / "policy-json-package").read_text())
assert package[0]["versions"][0]["sources"] == [{"priority": 100, "source": status}]
global_policy = json.loads((root / "policy-json-global").read_text())
assert global_policy["package_files"][0]["source"] == status
assert global_policy["package_files"][0]["downloadable"] is False
PY
echo "PASS: text and JSON policy describe the installed-only status database."

for fixture in tests/deb/packages/broken-*; do
	case_name="$(basename "${fixture}")"
	deb_path="${work_dir}/${case_name}.deb"
	apt_log="${work_dir}/${case_name}.apt.log"
	nala_log="${work_dir}/${case_name}.nala.log"

	dpkg-deb --root-owner-group --build "${fixture}" "${deb_path}" >/dev/null

	if apt-get install -s "${deb_path}" >"${apt_log}" 2>&1; then
		echo "Expected apt-get install -s ${deb_path} to fail" >&2
		cat "${apt_log}" >&2
		exit 1
	fi

	nala_status=0
	"${nala_bin}" --no-tui --color never install --assume-no "${deb_path}" \
		>"${nala_log}" 2>&1 || nala_status=$?

	if [[ "${nala_status}" == "0" ]]; then
		echo "Expected Nala to reject ${case_name}" >&2
		cat "${nala_log}" >&2
		exit 1
	fi

	if grep -q "Nothing to do\\." "${nala_log}"; then
		echo "Nala hid unresolved dependencies as Nothing to do." >&2
		cat "${nala_log}" >&2
		exit 1
	fi

	if ! grep -Fq "missing-a" "${nala_log}" || ! grep -Fq "missing-b" "${nala_log}"; then
		echo "Expected Nala to report both missing dependency alternatives." >&2
		cat "${nala_log}" >&2
		exit 1
	fi

	echo "PASS: apt and nala reject ${case_name}."
done

repo_dir="${work_dir}/repo"
lists_dir="${work_dir}/lists"
archive_dir="${work_dir}/archives"
sources_list="${work_dir}/sources.list"
main_deb="${work_dir}/recommends-or-main.deb"
apt_log="${work_dir}/recommends-or.apt.log"
nala_log="${work_dir}/recommends-or.nala.log"

mkdir -p "${repo_dir}" "${lists_dir}/partial" "${archive_dir}/partial"
dpkg-deb --root-owner-group --build tests/deb/packages/recommends-or-main \
	"${main_deb}" >/dev/null
for package in recommends-or-bad recommends-or-good; do
	dpkg-deb --root-owner-group --build "tests/deb/packages/${package}" \
		"${repo_dir}/${package}_1.0_all.deb" >/dev/null
done
(
	cd "${repo_dir}"
	dpkg-scanpackages . /dev/null >Packages
) 2>/dev/null
printf 'deb [trusted=yes] file:%s ./\n' "${repo_dir}" >"${sources_list}"

apt_options=(
	-o "Dir::Etc::sourcelist=${sources_list}"
	-o "Dir::Etc::sourceparts=-"
	-o "Dir::State::lists=${lists_dir}"
	-o "Dir::Cache::Archives=${archive_dir}/"
)
if ! apt-get "${apt_options[@]}" update >"${work_dir}/apt-update.log" 2>&1; then
	cat "${work_dir}/apt-update.log" >&2
	exit 1
fi
apt-get "${apt_options[@]}" install -s "${main_deb}" >"${apt_log}" 2>&1

grep -Fq "recommends-or-good" "${apt_log}"
if grep -Fq "recommends-or-bad" "${apt_log}"; then
	echo "APT selected the broken recommendation alternative." >&2
	cat "${apt_log}" >&2
	exit 1
fi

nala_status=0
"${nala_bin}" --no-tui --color never "${apt_options[@]}" \
	install --assume-no "${main_deb}" >"${nala_log}" 2>&1 || nala_status=$?

if [[ "${nala_status}" == "0" ]]; then
	echo "Expected Nala --assume-no to refuse the planned transaction." >&2
	cat "${nala_log}" >&2
	exit 1
fi
grep -Fq "recommends-or-good" "${nala_log}"
if grep -Fq "recommends-or-bad" "${nala_log}"; then
	echo "Nala selected the broken recommendation alternative." >&2
	cat "${nala_log}" >&2
	exit 1
fi

echo "PASS: apt and nala select the valid recommendation alternative."

info_dir="${work_dir}/info"
mkdir -p "${info_dir}/repo" "${info_dir}/lists/partial"
for version in 1 2; do
	cp -a "tests/deb/packages/nala-history-test-v${version}" "${info_dir}/repo-${version}"
	sed -i 's/^Installed-Size:.*/Installed-Size: 262144/' "${info_dir}/repo-${version}/DEBIAN/control"
	if [[ "${version}" == 2 ]]; then
		printf 'Depends: recommends-or-good\nProvides: nala-only-upgrade-virtual\n' \
			>>"${info_dir}/repo-${version}/DEBIAN/control"
	fi
	dpkg-deb --root-owner-group --build "${info_dir}/repo-${version}" \
		"${info_dir}/repo/package_${version}.deb" >/dev/null
done
cp "${repo_dir}/recommends-or-good_1.0_all.deb" \
	"${repo_dir}/recommends-or-bad_1.0_all.deb" "${info_dir}/repo/"
cp -a tests/deb/packages/nala-history-test-v1 "${info_dir}/local"
sed -i -e 's/^Installed-Size:.*/Installed-Size: 131072/' \
	-e 's/^Description:.*/Description: Local archive metadata/' "${info_dir}/local/DEBIAN/control"
local_deb="${info_dir}/local package.deb"
dpkg-deb --root-owner-group --build "${info_dir}/local" "${local_deb}" >/dev/null
(
	cd "${info_dir}/repo"
	dpkg-scanpackages --multiversion . /dev/null >Packages
) 2>/dev/null
printf 'deb [trusted=yes] file:%s ./\n' "${info_dir}/repo" >"${info_dir}/sources.list"
info_options=(
	-o "Dir::State::status=${policy_status}"
	-o "Dir::State::lists=${info_dir}/lists"
	-o "Dir::Etc::sourcelist=${info_dir}/sources.list"
	-o "Dir::Etc::sourceparts=-"
)
apt-get "${info_options[@]}" update >/dev/null 2>&1
(
	cd "${info_dir}"
	"${nala_bin}" --color never "${info_options[@]}" show './local package.deb'
) >"${info_dir}/local.txt"
grep -Fqx 'Version: 1.0' "${info_dir}/local.txt"
grep -Fqx 'Installed-Size: 128 MiB' "${info_dir}/local.txt"
grep -Fq 'Description: Local archive metadata' "${info_dir}/local.txt"
grep -Fqx "APT-Sources: ${local_deb}" "${info_dir}/local.txt"
runuser -u nobody -- "${nala_bin}" --color never "${info_options[@]}" \
	info --machine "${local_deb}" >"${info_dir}/local.json"
python3 - "${info_dir}/local.json" "${local_deb}" <<'PY'
import json, pathlib, sys
record = json.loads(pathlib.Path(sys.argv[1]).read_text())
assert record["Version"] == "1.0"
assert record["Installed-Size"] == "131072"
assert record["Size"] == str(pathlib.Path(sys.argv[2]).stat().st_size)
PY
"${nala_bin}" --color never "${info_options[@]}" show nala-history-test >"${info_dir}/repo.txt"
grep -Fqx 'Version: 2.0' "${info_dir}/repo.txt"
grep -Fqx 'Installed-Size: 256 MiB' "${info_dir}/repo.txt"
printf 'not a Debian archive\n' >"${info_dir}/corrupt.deb"
for archive in missing corrupt; do
	if "${nala_bin}" "${info_options[@]}" show "${info_dir}/${archive}.deb" >/dev/null 2>&1; then
		echo "Expected local archive lookup to reject ${archive}.deb." >&2
		exit 1
	fi
done
echo "PASS: local show/info uses the requested archive's records and units without root."

python3 - "${nala_bin}" "${info_dir}" <<'PY'
import os, pathlib, subprocess, sys
binary, root = sys.argv[1], pathlib.Path(sys.argv[2])
registration = subprocess.check_output(
    ["bash", "-c", "source debian/nala.bash-completion; complete -p nala"],
    env=dict(os.environ, PATH=str(pathlib.Path(binary).parent) + os.pathsep + os.environ["PATH"]),
    text=True,
)
assert "-o filenames" in registration, registration
env = dict(os.environ, COMPLETE="bash", _CLAP_COMPLETE_INDEX="2")
for command in ("install", "show", "info"):
    for prefix, expected in (
        ("./local", "./local package.deb"),
        ("local", "local package.deb"),
        (str(root / "local"), str(root / "local package.deb")),
        ("./repo/pack", "./repo/package_1.deb"),
        ("./rep", "./repo/"),
        ("dpk", "dpkg"),
    ):
        matches = subprocess.check_output(
            [binary, "--", "nala", command, prefix], cwd=root, env=env, text=True
        ).splitlines()
        assert expected in matches, (command, prefix, matches)
        assert not any(name.endswith(".txt") for name in matches), matches
for command in ("list", "search", "policy", "download"):
    assert not subprocess.check_output(
        [binary, "--", "nala", command, "./local"], cwd=root, env=env
    ), command
PY
echo "PASS: install/show/info complete package names, local DEBs and directories."

upgrade_status="${info_dir}/status"
dpkg-deb --field "${local_deb}" >"${upgrade_status}"
printf 'Status: install ok installed\n\n' >>"${upgrade_status}"
touch "${info_dir}/extended_states"
upgrade_options=(
	"${info_options[@]}"
	-o "Dir::State::status=${upgrade_status}"
	-o "Dir::State::extended_states=${info_dir}/extended_states"
)
for target in nala-history-test nala-history-test=2.0 nala-only-upgrade-virtual; do
	apt-get "${upgrade_options[@]}" install -s --only-upgrade \
		"${target}" recommends-or-bad >"${info_dir}/upgrade.apt.log" 2>&1
	grep -q '^Inst nala-history-test ' "${info_dir}/upgrade.apt.log"
	grep -q '^Inst recommends-or-good ' "${info_dir}/upgrade.apt.log"
	[[ "$(grep -c '^Inst ' "${info_dir}/upgrade.apt.log")" == 2 ]]
	nala_status=0
	"${nala_bin}" --no-tui --color never "${upgrade_options[@]}" install \
		--only-upgrade --no-update --no-autoremove --simple --assume-no \
		"${target}" recommends-or-bad >"${info_dir}/upgrade.nala.log" 2>&1 || nala_status=$?
	[[ "${nala_status}" == 1 ]]
	grep -Fqx 'Upgrade: 1' "${info_dir}/upgrade.nala.log"
	grep -Fqx '  nala-history-test' "${info_dir}/upgrade.nala.log"
	grep -Fqx 'Install: 1' "${info_dir}/upgrade.nala.log"
	grep -Fqx '  recommends-or-good' "${info_dir}/upgrade.nala.log"
	grep -Fq 'User refused confirmation' "${info_dir}/upgrade.nala.log"
done
"${nala_bin}" --no-tui --color never "${upgrade_options[@]}" install \
	--only-upgrade --no-update --no-autoremove recommends-or-bad \
	>"${info_dir}/upgrade-skipped.log" 2>&1
grep -Fq 'recommends-or-bad is not installed' "${info_dir}/upgrade-skipped.log"
grep -Fq 'Nothing to do.' "${info_dir}/upgrade-skipped.log"
echo "PASS: only-upgrade matches APT for installed, versioned and virtual targets, skips absent requests and allows new dependencies."
