#!/bin/sh -efu
#
# Copyright (C) 2023 Evgeny Sinelnikov <sin@altlinux.org>
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#

. shell-args
. shell-error
. shell-getopt

# Set the textdomain for the translations using $"..."
TEXTDOMAIN="alterator-backend-packages"

declare -a temp_files=()

cleanup() {
	rm -f "${temp_files[@]}"
}

trap cleanup EXIT

verbose=

show_help() {
	cat <<-EOF
		Usage: $PROG [options] <command>

		Commands:
		  check-apply             check the execution of the installation and removal transaction
		  check-install           check packages list on installation and installation status
		  check-reinstall         check packages list on reinstallation
		  check-remove            check packages list on removal
		  check-dist-upgrade      check packages list on removal and installation for upgrade
		  apply                   run an install/remove transaction using an exclude list of packages for pkgpriorities
		  dist-upgrade            upgrade for each installed packages
		  listall                 list all available packages
		  search <pattern>        search through all packages
		  lastupdate              shows time of last update in format YYYY.MM.DD HH:MM:SS UTC
		  lastdistupgrade         shows time of last dist-upgrade in format YYYY.MM.DD HH:MM:SS UTC

		Options:
		  -V, --version           print program version and exit;
		  -h, --help              show this text and exit.

		Report bugs to http://bugzilla.altlinux.org/

	EOF
	exit
}

print_version() {
	echo "@VERSION@"
	exit
}

get_last_log_entry() {
	if [ ! -f "$1" ]; then
		echo "Error: unable to get last update date, $1 not found" 1>&2
		exit 1
	fi
	tail -n 1 "$1"
}

get_last_update_date() {
	local date_result="$(TZ=UTC stat -c %y "/var/lib/apt/lists" 2>/dev/null)"
	if [[ -z "${date_result}"  ]]; then
		exit 1
	fi

	local parse_result="$(echo "${date_result}" | cut -d '.' -f1)"
	echo "${parse_result} UTC"
}

# takes one parameter:
# $1 is a string with packages to exclude from pkgpriorities in format: "pkg1\npkg2\n..."
create_pkgpriorities() {
	if [ $# -eq 1 ]; then
		local exclude_pkgs="$1"
	elif [ $# -eq 0 ]; then
		local exclude_pkgs=""
	else
		echo $"The wrong number of arguments was passed to create the pkgpriorities file" 1>&2
		exit 1
	fi

	local pkgpriorities
	pkgpriorities=$(mktemp "${TMPDIR:-/tmp}/alterator-pkgpriorities.XXXXXXXXXXXX") || {
		echo $"Error creating temporary pkgpriorities file" 1>&2
		exit 1
	}

	local -i exit_code=0
	{
		echo "Required:"
		# get manually installed packages, filter out those to be removed
		apt-mark showmanual | sed '1,2d' | grep -vFxf <(printf "%s" "${exclude_pkgs}") | sed -e 's/^/  /'
	} > "$pkgpriorities" || exit_code=${?}

	if [[ ${exit_code} -ne 0 ]]; then
		rm -f "$pkgpriorities"
		echo $"Failed to populate the pkgpriorities file with data" 1>&2
		exit ${exit_code}
	fi

	echo "$pkgpriorities"
}

transaction_start_signal() {
	echo $"Preparing the transaction..."
}

apply() {
	transaction_start_signal
	local to_remove=""
	local exclude_pkgs
	local pkgpriorities
	for pkg in "$@"; do
		if [[ $pkg == *- ]]; then
			to_remove+="${pkg%-}"$'\n'
		fi
	done

	exclude_pkgs=$(echo "$1" | tr ' ' '\n')
	exclude_pkgs="$to_remove$exclude_pkgs"

	# сreate pkgpriorities with manually installed packages
	local -i exit_code=0
	pkgpriorities=$(create_pkgpriorities "$exclude_pkgs") || exit_code=${?}

	if [[ ${exit_code} -ne 0 ]]; then
		echo "${pkgpriorities}" 1>&2
		exit ${exit_code}
	fi

	temp_files+=("$pkgpriorities")

	apt-get install -y -q "${@:2}" -o Dir::Etc::pkgpriorities="$pkgpriorities"
}

check_apply() {
	local reply
	local pkgpriorities
	local to_remove=""
	for pkg in "$@"; do
		if [[ $pkg == *- ]]; then
			to_remove+="${pkg%-}"$'\n'
		fi
	done
	to_remove="${to_remove%$'\n'}"

	# сreate pkgpriorities with manually installed packages
	local -i exit_code=0
	pkgpriorities=$(create_pkgpriorities "$to_remove") || exit_code=${?}

	if [[ ${exit_code} -ne 0 ]]; then
		echo "${pkgpriorities}" 1>&2
		exit ${exit_code}
	fi

	temp_files+=("$pkgpriorities")

	reply="$(LANG=en apt-get install --just-print -q "${@}" -o Dir::Etc::pkgpriorities="$pkgpriorities")" || exit_code=${?}

	if [[ ${exit_code} -ne 0 ]]; then
		echo "${reply}" 1>&2
		exit ${exit_code}
	fi

	local install_pkgs
	local remove_pkgs
	local extra_remove_pkgs

	install_pkgs="$(echo "${reply}" | awk '/Inst/ { print $2 }' | sed "s/^/\"/;s/$/\"/" | tr '\n' ',' | sed 's/,$//')"
	remove_pkgs="$(echo "${reply}" | awk '/Remv/ { print $2 }' | sed "s/^/\"/;s/$/\"/" | tr '\n' ',' | sed 's/,$//')"
	extra_remove_pkgs="$(echo "${reply}" \
		| awk '/This should NOT be done unless you know exactly what you are doing!/ {flag=1; next} /^[0-9]+ upgraded/ {flag=0} flag' \
		| tr -s '\n' ' ' \
		| sed 's/^[ \t]*//' \
		| sed 's/([^)]*)//g' \
		| sed -E 's/[[:space:]]+/\n/g' \
		| grep -v '^$' \
		| sed "s/^/\"/;s/$/\"/" \
		| tr '\n' ',' \
		| sed 's/,$//')"

	echo "{\"install_packages\": [${install_pkgs}], \"remove_packages\": [${remove_pkgs}], \"extra_remove_packages\": [${extra_remove_pkgs}]}"
}

check_reinstall() {
	local reply
	local -i exit_code=0
	reply="$(apt-get reinstall -s -q ${@})" || exit_code=${?}
	if [[ ${exit_code} -ne 0 ]]; then
		echo "${reply}" 1>&2
		exit ${exit_code}
	fi

	echo "${reply}" | awk '/Inst/ { print $2 }'
	echo "${reply}" | awk '/Remv/ { print $2 }' 1>&2
}

check_dist_upgrade() {
	local reply
	local -i exit_code=0
	reply="$(apt-get dist-upgrade -s -q)" || exit_code=${?}
	if [[ ${exit_code} -ne 0 ]]; then
		echo "${reply}" 1>&2
		exit ${exit_code}
	fi

	echo "${reply}" | awk '/Inst/ { print $2 }'
	echo "${reply}" | awk '/Remv/ { print $2 }' 1>&2
}

dist_upgrade() {
	transaction_start_signal
	local reply
	local -i exit_code=0
	reply="$(apt-get dist-upgrade --assume-yes -q)" || exit_code=${?}
	if [[ ${exit_code} -ne 0 ]]; then
		echo "${reply}" 1>&2
		exit ${exit_code}
	fi
}

TEMP=$(getopt -n $PROG -o $getopt_common_opts -l $getopt_common_longopts -- "$@") ||
	show_usage
eval set -- "$TEMP"

while :; do
	case "$1" in
	--)
		shift
		break
		;;
	*)
		parse_common_option "$1"
		;;
	esac
	shift
done

[ "$#" -gt 0 ] ||
	show_usage

case "$1" in
listall)
	apt-cache search . --names-only | awk '{ print $1 }'
	;;
search)
	[ "$#" -gt 1 ] ||
		show_usage
	apt-cache search "$2" | awk '{ print $1 }'
	;;
lastupdate)
	get_last_update_date
	;;
lastdistupgrade)
	get_last_log_entry "/var/log/alterator/apt/dist-upgrades.log"
	;;
apply)
	apply "${@:2}"
	;;
check-apply)
	check_apply "${@:2}"
	;;
check-reinstall)
	check_reinstall "${@:2}"
	;;
check-dist-upgrade)
	check_dist_upgrade
	;;
dist-upgrade)
	dist_upgrade
	;;
esac
