#!/bin/bash

PATH=/bin:/usr/bin:/sbin:/usr/sbin
: ${XIVO_CONFD_PORT:='9486'}
xivo_version_file="/var/lib/xivo/xivo_previous_version"

source /usr/bin/xivo-upgrade-functions

execute() {
    cmd=$*
    $cmd
    if [ $? -ne 0 ]; then
        start_xivo
        exit 1
    fi
}

stop_xivo() {
    xivo-service stop
    xivo-service disable
}

start_xivo() {
    xivo-service enable
    xivo-service start
}

clean_tokens_from_consul() {
    echo "Cleaning tokens from consul"
    /etc/init.d/consul stop
    rm -rf /var/lib/consul/*
    dpkg-reconfigure consul
}

remove_rabbitmq_anonymous_volume() {
    echo "Cleaning rabbitmq configuration and data"
    xivo-dcomp stop rabbitmq
    xivo-dcomp rm -f rabbitmq > /dev/null
}

upgrade_rabbitmq() {
    remove_rabbitmq_anonymous_volume
    # We need to clean tokens from consul otherwise they will stay forever (because we cleaned rabbitmq memory)
    clean_tokens_from_consul
    xivo-dcomp pull rabbitmq
    xivo-dcomp up -d rabbitmq
}

export_xivo_version_to_env_and_file() {
    export XIVO_VERSION_INSTALLED=$(get_xivo_version_installed_from_apt)
    export XIVO_VERSION_CANDIDATE=$(get_xivo_version_candidate_from_apt)
    echo "$XIVO_VERSION_INSTALLED" > $xivo_version_file
}

does_plpython_remains_in_db() {
    local query_pg_language="SELECT COUNT(lanname) FROM pg_language WHERE lanname IN ('plpythonu', 'plpython2', 'plpython2u')"
    local query_pg_proc="SELECT COUNT(proname) FROM pg_proc LEFT JOIN pg_language ON pg_language.oid = pg_proc.prolang WHERE pg_language.lanname IN ('plpythonu', 'plpython2', 'plpython2u')"
    local plpython_remains_in_asterisk=$(psql -At -U postgres -d postgres -c "$query_pg_language")
    local plpython_remains_in_postgres=$(psql -At -U postgres -d asterisk -c "$query_pg_language")
    local processes_using_plpython_in_asterisk=$(psql -At -U postgres -d asterisk -c "$query_pg_proc")
    local processes_using_plpython_in_postgres=$(psql -At -U postgres -d asterisk -c "$query_pg_proc")

    if [ "$plpython_remains_in_asterisk" -gt 0 ] || \
    [ "$plpython_remains_in_postgres" -gt 0 ] || \
    [ "$processes_using_plpython_in_asterisk" -gt 0 ] || \
    [ "$processes_using_plpython_in_postgres" -gt 0 ];
    then
        return 0
    else
        return 1
    fi
}

display_plpython_msg() {
    echo "
    Your database contains artifacts related to python plugins, which are deprecated and block the database upgrade.
    To continue the upgrade, make sure that these artifacts aren't used and decide the impacts, which their removal will cause.
    Below are manual steps needed to be done, before continuing with the upgrade.

    # Checking which language and procedure blocks upgrade
    psql -U postgres asterisk -c \"SELECT * FROM pg_language WHERE lanname IN ('plpythonu', 'plpython2', 'plpython2u');\"
    psql -U postgres asterisk -c \"SELECT proname FROM pg_proc LEFT JOIN pg_language ON pg_language.oid = pg_proc.prolang WHERE pg_language.lanname IN ('plpythonu', 'plpython2', 'plpython2u');\"
    psql -U postgres postgres -c \"SELECT * FROM pg_language WHERE lanname IN ('plpythonu', 'plpython2', 'plpython2u');\"
    psql -U postgres postgres -c \"SELECT proname FROM pg_proc LEFT JOIN pg_language ON pg_language.oid = pg_proc.prolang WHERE pg_language.lanname IN ('plpythonu', 'plpython2', 'plpython2u');\"

    # Removing plpython related artifacts in asterisk database:
    psql -U postgres asterisk -c \"DELETE FROM pg_proc WHERE pg_proc.prolang IN (SELECT pg_language.oid FROM pg_language WHERE lanname IN ('plpythonu', 'plpython2', 'plpython2u'));\"
    psql -U postgres asterisk -c \"DROP LANGUAGE plpython2;\"
    psql -U postgres asterisk -c \"DROP LANGUAGE plpython2u;\"
    psql -U postgres asterisk -c \"DROP LANGUAGE plpythonu;\"

    # Removing plpython related artifacts in postgres database:
    psql -U postgres postgres -c \"DELETE FROM pg_proc WHERE pg_proc.prolang IN (SELECT pg_language.oid FROM pg_language WHERE lanname IN ('plpythonu', 'plpython2', 'plpython2u'));\"
    psql -U postgres postgres -c \"DROP LANGUAGE plpython2;\"
    psql -U postgres postgres -c \"DROP LANGUAGE plpython2u;\"
    psql -U postgres postgres -c \"DROP LANGUAGE plpythonu;\"
    "
}

check_for_plpython_in_db() {
    if dpkg --compare-versions "$XIVO_VERSION_INSTALLED" "<<" "2023.05.00"; then
        echo "Checking if database contains plpython artifacts."
        if does_plpython_remains_in_db; then
            display_plpython_msg
            exit 1
        fi
    fi
}

xivo_upgrade() {
    apt update

    pre_stop "xivo"
    stop_xivo
    post_stop "xivo"
    display_upgrade_notice "Upgrading xivo..."
    if is_bookworm; then
		check_enough_space_for_boot_partition
        check_active_db_conn
        export XIVO_SYSTEM_UPGRADE=1
        exec_bookworm_to_trixie_migration
    else
        display_upgrade_notice "Executing manual upgrade actions..."
        if dpkg --compare-versions "$XIVO_VERSION_INSTALLED" "<<" "2022.10.02"; then
            execute apt-get install -y -o Dpkg::Options::="--force-confnew" xivo-docker-components
            upgrade_rabbitmq
        fi

        display_upgrade_notice "Executing XiVO PBX upgrade actions..."
        execute apt dist-upgrade -y -o Dpkg::Options::="--force-confnew"

        display_upgrade_notice "Executing cleaning actions..."
        apt autoremove -y
    fi


    display_upgrade_notice "Running post-upgrade actions..."
    real-xivo-finish-upgrade
    if [[ "$XIVO_SYSTEM_UPGRADE" == "1" ]]; then
        check_if_grub_is_broken
		if is_trixie; then
        	display_system_upgrade_reboot_notice
        	display_sources_list_notice
		else
			cat <<-EOF
			**********************************************************
			*  WARNING: the system was not correctly upgraded        *
			*                                                        *
			**********************************************************
			EOF
		fi
    fi
}

display_xivo_version() {
    local xivo_version_candidate_colored
    xivo_version_candidate_colored=$(color_diff_between_versions "$XIVO_VERSION_INSTALLED" "$XIVO_VERSION_CANDIDATE")
    echo ""
    echo "installed version : $XIVO_VERSION_INSTALLED"
    echo -e "proposed update   : $xivo_version_candidate_colored"
}

recreate_upgrade_status_file_and_export() {
    local status_file="${1}"; shift

    rm -f "${status_file}"
    touch "${status_file}"
    export XIVO_UPGRADE_STATUS_FILE=${status_file}
}

upgrading_system() {
    local force="${1}"; shift

    prepare_package_sources
    if is_bullseye; then
        display_system_upgrade_notice
    fi
    display_xivo_version
    ask_to_continue_upgrade "${force}"
    local xivo_upgrade_status_file="/tmp/xivo-upgrade.status"
    recreate_upgrade_status_file_and_export "${xivo_upgrade_status_file}"

    xivo_upgrade
}

is_linux_image_amd64_meta_installed() {
    [ -n "$(dpkg -l linux-image-amd64 2>/dev/null | grep '^ii')" ]
}

is_linux_image_686_pae_meta_installed() {
    [ -n "$(dpkg -l linux-image-686-pae 2>/dev/null | grep '^ii')" ]
}

is_linux_image_586_meta_installed() {
    [ -n "$(dpkg -l linux-image-586 2>/dev/null | grep '^ii')" ]
}

is_cpu_support_pae() {
    grep -q pae /proc/cpuinfo
}

install_linux_image() {
    case $(dpkg --print-architecture) in
        amd64)
            if ! is_linux_image_amd64_meta_installed ; then
                apt install --yes linux-image-amd64
            fi
            ;;
        i386)
            if is_cpu_support_pae ; then
                if ! is_linux_image_686_pae_meta_installed ; then
                    apt install --yes linux-image-686-pae
                fi
            else
                if ! is_linux_image_586_meta_installed ; then
                    apt install --yes linux-image-586
                fi
            fi
            ;;
    esac
}

is_there_active_db_conn() {
    res=$(sudo -u postgres psql -Aqt -c "SELECT COUNT(*) FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND state = 'active';" 2> /dev/null)
    if [ $? -ne 0 ]; then
		cat <<-EOF
		***************************************************************
		* ERROR:  there was an error during connection to postgresql. *
		* Upgrade aborted.                                            *
		* Check your installation an re-launch the upgrade.           *
		***************************************************************
		EOF
        exit 1
    else
        return ${res}
    fi
}

check_active_db_conn() {
    if ! is_there_active_db_conn ; then
		cat <<-EOF
		***************************************************************************************************************************
		* WARNING: There are some unexpected active connections to the database.                                                  *
		*          Upgrade aborted.                                                                                               *
		*                                                                                                                         *
		* You MUST:                                                                                                               *
		*   1. close these unexpected active database connections,                                                                *
		*   2. and then, finish this upgrade with xivo-upgrade                                                                    *
		*                                                                                                                         *
		* 1. To find these spurious connections to the database :                                                                 *
		*   - Stop xivo services                                                                                                  *
		*     # xivo-service stop                                                                                                 *
		*   - List active connections                                                                                             *
		*     # sudo -u postgres psql -c "SELECT * FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND state = 'active';"     *
		*     or                                                                                                                  *
		*     # ss -anp|grep [:\.]5432|grep -v LISTEN                                                                             *
		*   - Stop these connections and ensure that they won't re-connect                                                        *
		* 2. Re-run the upgrade :                                                                                                 *
		*     # xivo-service start                                                                                                *
		*     # xivo-upgrade                                                                                                      *
		***************************************************************************************************************************
		EOF
        exit 1
    fi
}

# Stretch installed from PXE may have a broken vimrc.local
fix_vimrc_local_for_pxe_install() {
    local vimrc_local_file="/etc/vim/vimrc.local"
    if [ -e "${vimrc_local_file}" ]; then
        # shellcheck disable=SC2016
        sed -i 's#source /usr/share/vim/vim80/defaults.vim#source $VIMRUNTIME/defaults.vim#' "${vimrc_local_file}"
    fi
}

exec_bookworm_to_trixie_migration() {
	next_debian="trixie"
    local force_yes="--allow-downgrades --allow-remove-essential --allow-change-held-packages"

    add_xivo_apt_conf

    display_upgrade_notice "Executing pre-$next_debian upgrade actions..."
    display_upgrade_notice ".. pre-$next_debian upgrade actions: install new dahdi-linux-dkms"
    # Force install of new dahdi-linux-dkms module before switching to trixie
    # shellcheck disable=SC2086
    apt install --yes ${force_yes} -o Dpkg::Options::="--force-confnew" dahdi-linux-dkms

    display_upgrade_notice ".. pre-$next_debian upgrade actions: swtich repo to $next_debian"
    switch_to_trixie
    apt update

    display_upgrade_notice "Executing manual upgrade actions..."
    display_upgrade_notice ".. manual upgrade actions: upgrade docker"
    ## Docker specific part
    ## We want to control docker installation in order to not have uncontrolled container restart in the middle of the upgrade

    # Docker installation preparation
    install_xivo_docker

    # Upgrade docker-ce before xivo upgrade
    # shellcheck disable=SC2086
    apt install --yes ${force_yes} -o Dpkg::Options::="--force-confnew" docker-ce docker-ce-cli containerd.io

    # Install xivo-docker-components to have new xivo-dcomp (which uses docker compose v2)
    # (Do not use --force-confnew to not force-rewrite factory.env file)
    # shellcheck disable=SC2086
    apt-get install --yes ${force_yes} xivo-docker-components

    # re-stopping services after docker upgrade
    xivo-service stop
    ## End of docker specific part

    display_upgrade_notice ".. manual upgrade actions: upgrade rabbitmq"
    upgrade_rabbitmq

    apt update

    display_upgrade_notice "Executing XiVO PBX full upgrade actions..."
    # shellcheck disable=SC2086
    apt dist-upgrade --yes ${force_yes} -o Dpkg::Options::="--force-confnew"

    # Fixing upgrade: set manually XIVO_UUID and run apt -f to fix xivo-confd/xivo-base/xivo postinst
    display_upgrade_notice "Set the XIVO_UUID at the end of the upgrade..."
    xivo-configure-uuid
    apt -f install --yes

    display_upgrade_notice "Executing cleaning actions..."
    remove_deactivated_sources_list
    apt update
    apt autoremove --yes

    install_linux_image
}

list_packages_with_filter() {
    local filter="$1"
    aptitude -q -F '%p' --disable-columns search "$filter"
}

prepare_package_sources () {
    install_xivo_dist
}

install_xivo_docker() {
    apt install --yes xivo-docker
}

install_xivo_dist() {
    is_dist_installed=$(list_packages_with_filter "?installed?name(\"^xivo-dist$\")")
    if [ -z "$is_dist_installed" ]; then
        echo "Installing xivo-dist package..."
        if ! apt install --yes xivo-dist; then
            echo "Failed to install xivo-dist. Aborting upgrade."
            exit 1
        fi
    fi
}

xivo_package() {
    list_packages_with_filter "?installed?name(\"^xivo$|^xivo-base$|^pf-xivo$\")" | head -n1
}

get_xivo_version_installed_from_apt() {
    echo "$(LANG='C' apt-cache policy $(xivo_package) | grep Installed | grep -oE '[0-9]{2,4}\.[0-9]+(\.[0-9]+)?|1\.2\.[0-9]{1,2}' | head -n1)"
}

get_xivo_version_candidate_from_apt() {
    echo "$(LANG='C' apt-cache policy xivo | grep Candidate | grep -oE '[0-9]{2,4}\.[0-9]+(\.[0-9]+)?|1\.2\.[0-9]{1,2}' | head -n1)"
}

check_64bit_system() {
    machine=$(uname -m)
    if [ "$machine" = "i686" ]; then
		cat <<-EOF
		**********************************************************************
		* You are trying to upgrade to version that requires 64-bit system.  *
		* Upgrade aborted.                                                   *
		*                                                                    *
		* Please revert the sources list and downgrade the xivo-upgrade      *
		* package to the latest LTS version for 32-bit systems :             *
		* # xivo-dist xivo-polaris                                           *
		* # apt update                                                   *
		* # apt policy xivo-upgrade                                    *
		* # apt install xivo-upgrade=<AVAILABLE POLARIS VERSION>         *
		*                                                                    *
		**********************************************************************
		EOF
        exit 1
    fi
}

check_wizard_has_been_run() {
    if [[ "$XIVO_VERSION_INSTALLED" > "16.08.2" && "$XIVO_VERSION_INSTALLED" < "2016" ]]; then
        echo "Your version is $XIVO_VERSION_INSTALLED. Unfortunately, there are no more upgrades for versions above 16.08.2."
        echo "Please contact XiVO (https://xivo.solutions) for support."
        exit 1
    fi

    if ! is_xivo_configured; then
        echo "ERROR: You must configure XiVO by running the web wizard before using xivo-upgrade"
        exit 1
    fi
}

is_xivo_configured() {
    if [ -f "/var/lib/xivo/configured" ]; then
        return 0
    fi

    if [ ! -f "/var/run/xivo-confd/xivo-confd.pid" ]; then
        echo "ERROR: xivo-confd is not started. Cannot check if Wizard was run."
        echo "Start xivo-confd and relaunch upgrade"
        exit 1
    fi

    [ -n "$(curl -skX GET --header 'Accept: application/json' "https://localhost:$XIVO_CONFD_PORT/1.1/wizard" | grep '\"configured\"[[:space:]]*:[[:space:]]*true')" ]
}

usage() {
    local progname="${1}"; shift

    echo "usage: ${progname} [-d] [-f] [-h]"
    echo -e "\t-d: only download packages"
    echo -e "\t-f: force install the proposed version"
    echo -e "\t-h: print usage"
}

main() {
    local progname="xivo-upgrade"
    # Parse flags
    local download_only
    local force
    while getopts :dfh opt
    do
        case ${opt} in
            d) download_only=1;;
            f) force=1;;
            h)
                usage "${progname}"
                exit 0
            ;;
            '?')
                echo "${progname} : option ${OPTARG} is not valid" >&2
                usage "${progname}"
                exit 1
            ;;
        esac
    done
    download_only="${download_only:-"0"}"
    force="${force:-"0"}"

    # Beginning
    export DEBIAN_FRONTEND=noninteractive
    export APT_LISTCHANGES_FRONTEND=none

    check_64bit_system
    export_xivo_version_to_env_and_file
    check_for_plpython_in_db
    check_wizard_has_been_run
    if (! is_bookworm && ! is_trixie); then
        echo "Upgrade aborted."
        echo "Your XiVO is lower than Maia (2024.05) and uses Debian 11 or lower"
        echo "Upgrade to XiVO >= 2026.05 (which uses Debian 13) is not supported"
        echo "You MUST first upgrade to a XiVO >= Maia (2024.05)"
        exit 1
    fi

    if [ $download_only -eq 0 ]; then
        upgrading_system "${force}"
    else
        trap : SIGINT
        if is_bookworm; then
            # Switch repo to bookworm
            switch_to_trixie
            apt update

            # Download packages
            apt -y -d -o Dpkg::Options::="--force-confnew" dist-upgrade

            # Rollback
            rollback_apt_sources_list
            apt update
        else
            # Download packages
            apt -y -d -o Dpkg::Options::="--force-confnew" dist-upgrade
        fi
        # Pull container
        export XIVOCC_TAG=$(echo $XIVO_VERSION_CANDIDATE | cut -d'.' -f1,2)
        xivo-dcomp pull --ignore-pull-failures
    fi
}

main "${@}"
