#!/bin/bash

set -e

DB_HOST="localhost"
DB_PORT=5432
PGPASSWORD="proformatique"
DB_USERNAME="postgres"
DB_MIGRATION_VERSION="2025.06"
FACTORY_ENV_FILE="/etc/docker/xivo/factory.env"
CUSTOM_ENV_FILE="/etc/docker/xivo/custom.env"
PG_DATA_FOLDER="/var/lib/postgresql"
PG_17_DATA="$PG_DATA_FOLDER/17/data"
PG_15_DATA="$PG_DATA_FOLDER/15/data"
PG_15_PID_FILE="$PG_15_DATA/postmaster.pid"
DB_DEFAULT_RESTART_POLICY="always"
PG15_CONF_BACKUP_DIR="/var/tmp/xivo-migrate-db-15-to-17/postgresql-15-conf-backup/"

#shellcheck disable=SC1091
source /usr/bin/xivo-upgrade-functions

stop_services() {
    echo "Stopping services..."
    service cron stop
    pkill -f xivo-call-logs || true
}

start_services() {
    echo "Starting services..."
    service cron start
}

run_psql_file() {
  PGPASSWORD=${PGPASSWORD} psql -h $DB_HOST -p $DB_PORT -U $DB_USERNAME -t -f "$1"
}

is_postgres_running() {
    is_postgres_running_on_port "${PGPASSWORD}" "${DB_HOST}" "${DB_PORT}" "${DB_USERNAME}" "asterisk"
}

save_postgres15_conf() {
    local pg15_backup_dir="${1}"; shift

    mkdir -p "${pg15_backup_dir}"
    cp -aR "${PG_15_DATA}"/*.conf "${pg15_backup_dir}"
    cp -aR "${PG_15_DATA}"/conf.d/ "${pg15_backup_dir}"
}

wait_for_postgres() {
    local wait_time=120
    echo -e -n "Waiting $wait_time seconds for database db..."

    local db_running="false"
    for _ in $(seq 1 $wait_time); do
        if is_postgres_running; then
            db_running="true"
            break
        else
            sleep 1
            echo -e -n "."
        fi
    done
    echo
    if [ "$db_running" = "false" ]; then
        echo -e "\e[31mERROR: database is not running.\e[0m"
        return 1
    fi
}

shutdown_database() {
    xivo-dcomp stop db
}

is_pg15_lock_file_present() {
    [ -e "${PG_15_PID_FILE}" ]
}

wait_for_lock_file_removal() {
    local wait_time=120
    echo -e -n "Waiting $wait_time seconds for the database to shut down..."

    for _ in $(seq 1 $wait_time); do
        if ! is_pg15_lock_file_present; then
            echo
            return 0
        fi
        sleep 1
        echo -e -n "."
    done
    echo
    return 1
}

db_container_id() {
    xivo-dcomp ps -aq db 2>/dev/null | head -n 1
}

db_restart_policy() {
    local container_id="${1}"; shift

    docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' "${container_id}" 2>/dev/null
}

suspend_db_restart_policy() {
    local container_id="${1}"; shift

    docker update --restart=no "${container_id}" > /dev/null
}

restore_db_restart_policy() {
    local container_id="${1}"; shift
    local restart_policy="${1}"; shift

    docker update --restart="${restart_policy}" "${container_id}" > /dev/null
}

shutdown_database_from_inside_container() {
    local container_id="${1}"; shift

    echo -e "\e[33mStarting the db again so that postgres removes its own lock file...\e[0m"
    xivo-dcomp start db || return 1
    wait_for_postgres || return 1

    echo -e "\e[33mAsking postgres to shut down from inside the container...\e[0m"
    docker exec "${container_id}" sh -c 'kill -INT 1' || return 1

    wait_for_lock_file_removal
}

recover_unclean_shutdown() {
    local container_id restart_policy shutdown_status

    container_id=$(db_container_id)
    if [ -z "${container_id}" ]; then
        echo -e "\e[31mERROR: no db container to recover.\e[0m"
        return 1
    fi

    echo -e "\e[33mThe database did not shut down cleanly: ${PG_15_PID_FILE} is still there.\e[0m"

    restart_policy=$(db_restart_policy "${container_id}")
    restart_policy="${restart_policy:-no}"
    suspend_db_restart_policy "${container_id}" || return 1

    shutdown_database_from_inside_container "${container_id}"
    shutdown_status=$?

    restore_db_restart_policy "${container_id}" "${restart_policy}" || true

    if [ "${shutdown_status}" -eq 0 ]; then
        echo -e "\e[32mThe database shut down cleanly.\e[0m"
    fi

    return "${shutdown_status}"
}

display_unclean_shutdown_error() {
	cat <<-EOF
	***************************************************************************************
	* ERROR: the postgres 15 cluster could not be shut down cleanly. Migration aborted.
	*
	*   ${PG_15_PID_FILE} is still there.
	*
	* pg_upgrade will refuse to run.
	*
	* You MUST shutdown the database by hand, then finish this upgrade with xivo-upgrade.
	* Suspending the restart policy is required: docker brings the container straight back
	* up otherwise, as postgres exiting looks like a plain exit to it.
	*   # db=\$(xivo-dcomp ps -aq db)
	*   # docker update --restart=no \$db
	*   # xivo-dcomp start db
	*   # docker exec \$db sh -c 'kill -INT 1'
	*   # docker update --restart=${DB_DEFAULT_RESTART_POLICY} \$db
	***************************************************************************************
	EOF
}

get_distribution() {
    local distribution=""
    if [ -f ${CUSTOM_ENV_FILE} ] && [ "$(grep -oP -m 1 'XIVOCC_DIST=\K.*' ${CUSTOM_ENV_FILE})" != "" ]; then
        distribution=$(grep -oP -m 1 'XIVOCC_DIST=\K.*' ${CUSTOM_ENV_FILE})
    else
        distribution=$(grep -oP -m 1 'XIVOCC_DIST=\K.*' ${FACTORY_ENV_FILE})
    fi
    echo -e "$distribution"
}

# Get locale of db
# Params:
#  - locale name
#  - db (default to template1)
#  - default locale (default to en_US.UTF-8)
# Returns locale or default locale if retrieved locale is null
get_locale() {
    local lc="${1}"; shift
    local pg_maintenance_db="${1:-"template1"}"; shift
    local default_lc="${1:-"en_US.UTF-8"}"; shift

    local res=""
    res=$(cd /tmp/ && sudo -u postgres psql -U postgres --quiet -tAc "SELECT DISTINCT ${lc} FROM pg_catalog.pg_database WHERE datname='${pg_maintenance_db}'")
    if [ -z "$res" ]; then
        # Set locale to en_US.UTF-8 if value is null
        res=${default_lc}
    fi
    echo "${res}"
}

get_tag() {
    local tag;
    tag=$(grep -oP -m 1 'XIVOCC_TAG=\K.*' ${FACTORY_ENV_FILE})
    echo "$tag"
}

migrate_15_to_17() {
    set -o pipefail
    local distribution="${1}"; shift
    local old_lc_collate="${1}"; shift
    local old_lc_ctype="${1}"; shift

    # Construct initdb args with encoding
    local postgres_initdb_args="--encoding UTF8 --lc-collate ${old_lc_collate} --lc-ctype ${old_lc_ctype}"

    echo -e "\e[33mPulling docker database container...\e[0m"
    local tag; tag=$(get_tag)
    docker pull "xivoxc/xivo-db:${tag}.${distribution}"

    echo -e  "\e[33mInit new postgres 17 database with locale ${old_lc_collate}, ${old_lc_ctype} ...\e[0m"
    docker run --rm \
        -e XIVO_EXIT_AFTER_INITDB="true" \
        -e POSTGRES_INITDB_ARGS="${postgres_initdb_args}" \
        -e POSTGRES_PASSWORD="proformatique" \
        -v /etc/timezone:/etc/timezone:ro \
        -v /etc/localtime:/etc/localtime:ro \
        -v /etc/passwd:/etc/passwd:ro \
        -v /etc/group:/etc/group:ro \
        -v /var/log/postgresql:/var/log/postgresql \
        -v /var/lib/postgresql/:/var/lib/postgresql/ \
        -v /run/postgresql:/run/postgresql \
        "xivoxc/xivo-db:${tag}.${distribution}" 2>&1
    
    #shellcheck disable=SC2181
    if [ $? -eq 0 ]; then 
        echo -e  "\e[32mPostgres 17 Init success with locale ${old_lc_collate}, ${old_lc_ctype} ...\e[0m"
    else
        echo -e  "\e[31mERROR: Postgres Init failed."
        exit 1;
    fi

    echo -e  "\e[33mStarting Migration container\e[0m"
    # Note: Using implicit parameter from tianon's image: PGDATANEW & PGDATAOLD
    docker run --rm \
        -v /etc/timezone:/etc/timezone:ro \
        -v /etc/localtime:/etc/localtime:ro \
        -v /var/lib/postgresql/:/var/lib/postgresql/ \
        "xivoxc/xivo-db-migration:${DB_MIGRATION_VERSION}.${distribution}" --link 2>&1
}

display_upgrade_end_notice() {
    echo -e "\e[32m*********************************************************************************"
    echo -e "*   XiVO Database was upgraded from Postgres version 15 to version 17        *"
    echo -e "*********************************************************************************\e[0m"
}

display_upgrade_start_notice() {
    echo -e "\e[32m*********************************************************************************"
    echo -e "*   About to upgrade Postgres from version 15 to version 17                  *"
    echo -e "*********************************************************************************\e[0m"
}


clean_pg_upgrade_remainders() {
    echo -e  "\e[33mRemove old cluster and clean pg_upgrade files\e[0m"
    rm -rf "${PG_DATA_FOLDER}/15" \
           "${PG_DATA_FOLDER}/data" \
            /var/lib/postgresql/delete_old_cluster.sh
}

add_access_configuration() {
    cp "${PG_15_DATA}/pg_hba.conf" "${PG_17_DATA}"
    cp -afu "${PG_15_DATA}"/conf.d/*.conf "${PG_17_DATA}/conf.d/"
}

pull_and_start_db() {
    xivo-dcomp upgrade-db
}

post_migration_steps() {
    if [ -f "/var/lib/postgresql/update_extensions.sql" ]; then
        run_psql_file "/var/lib/postgresql/update_extensions.sql"
    fi
}

migrate() {
    local distribution
    display_upgrade_start_notice
    echo -e "\e[32mStarting database migration to postgres 17...\e[0m"

    echo -e "\e[32mEnsure db is running...\e[0m"
    if ! is_postgres_running; then
        echo -e "\e[32mStarting the db...\e[0m"
        xivo-dcomp start db
        wait_for_postgres
    fi

    echo -e "\e[32mRetrieving the old database locale\e[0m"
    local old_lc_collate; old_lc_collate=$(get_locale "datcollate")
    local old_lc_ctype; old_lc_ctype=$(get_locale "datctype")

    echo -e "\e[32mStopping services that might be using the db...\e[0m"
    stop_services

    echo -e "\e[33mStopping db...\e[0m"
    shutdown_database

    # This specific step was needed when pg migration was done
    # at the same time as Debian 13 migration (see #9332 for details)
    if is_pg15_lock_file_present; then
        recover_unclean_shutdown || true
    fi

    if is_pg15_lock_file_present || is_postgres_running; then
        display_unclean_shutdown_error
        exit 3
    fi

    echo "Saving postgres 15 conf to ${PG15_CONF_BACKUP_DIR}"
    save_postgres15_conf "${PG15_CONF_BACKUP_DIR}"

    echo -e "\e[33mPulling docker migration container...\e[0m"

    distribution=$(get_distribution)
    docker pull "xivoxc/xivo-db-migration:${DB_MIGRATION_VERSION}.${distribution}"


    echo -e "\e[33mRunning migration...\e[0m"
    if migrate_15_to_17 "$distribution" "$old_lc_collate" "$old_lc_ctype"; then
        echo -e "\e[32mDatabase upgrade successful.\e[0m"
        set +o pipefail

        echo -e "\e[33mRetrieve old conf (pg_hba.conf and conf.d) ...\e[0m"
        add_access_configuration

        echo -e "\e[33mCleaning old data ...\e[0m"
        clean_pg_upgrade_remainders

        echo -e "\e[33mStarting database container...\e[0m"
        pull_and_start_db
        wait_for_postgres

        post_migration_steps

        start_services

        display_upgrade_end_notice
    fi
}

if [ ! -d "$PG_17_DATA" ]; then
    migrate
else
    echo -e "\e[31mERROR: $PG_17_DATA directory exists.\e[0m"
fi

exit 0

