#!/usr/bin/env bash

set -euo pipefail

project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${project_root}"

expected_host="${1:-}"
expected_url="${2:-}"
expected_environment="${3:-DEV}"
config_file="application/config/config.php"
deployment_environment_file=".deployment.env"
stylesheet_path="application/Resources/css/bootstrap.min.css"
stylesheet_checksum_path="application/Resources/css/bootstrap.min.css.sha256"
maximum_stylesheet_size=5242880
incoming_stylesheet=""
incoming_checksum=""
destination_stylesheet_temp=""
destination_checksum_temp=""
previous_stylesheet_backup=""
previous_checksum_backup=""
had_previous_stylesheet=false
had_previous_checksum=false
stylesheet_changes_started=false
deployment_succeeded=false

fail() {
    echo "$1" >&2
    exit 1
}

cleanup() {
    set +e

    if [[ "${stylesheet_changes_started}" == true && "${deployment_succeeded}" != true ]]; then
        if [[ "${had_previous_stylesheet}" == true && -f "${previous_stylesheet_backup}" ]]; then
            cp "${previous_stylesheet_backup}" "${stylesheet_path}"
            chmod 644 "${stylesheet_path}"
        else
            rm -f -- "${stylesheet_path}"
        fi

        if [[ "${had_previous_checksum}" == true && -f "${previous_checksum_backup}" ]]; then
            cp "${previous_checksum_backup}" "${stylesheet_checksum_path}"
            chmod 600 "${stylesheet_checksum_path}"
        else
            rm -f -- "${stylesheet_checksum_path}"
        fi
    fi

    for temporary_file in "${incoming_stylesheet}" "${incoming_checksum}" \
        "${destination_stylesheet_temp}" \
        "${destination_checksum_temp}" "${previous_stylesheet_backup}" \
        "${previous_checksum_backup}"; do
        if [[ -n "${temporary_file}" && -f "${temporary_file}" ]]; then
            rm -f -- "${temporary_file}"
        fi
    done
}

file_sha256() {
    php -r 'echo hash_file("sha256", $argv[1]);' "$1"
}

verify_stylesheet() {
    local stylesheet="$1"
    local checksum_file="$2"
    local stored_checksum=""
    local unexpected_value=""
    local actual_checksum=""

    [[ -f "${stylesheet}" && ! -L "${stylesheet}" ]] \
        || fail "Deployment stylesheet is missing or unsafe: ${stylesheet}"
    [[ -s "${stylesheet}" ]] \
        || fail "Deployment stylesheet is empty: ${stylesheet}"
    [[ -f "${checksum_file}" && ! -L "${checksum_file}" ]] \
        || fail "Stylesheet checksum is missing or unsafe: ${checksum_file}"

    read -r stored_checksum unexpected_value < "${checksum_file}" || true
    [[ "${stored_checksum}" =~ ^[0-9a-f]{64}$ && -z "${unexpected_value}" ]] \
        || fail "Stylesheet checksum is invalid: ${checksum_file}"

    actual_checksum="$(file_sha256 "${stylesheet}")"
    [[ "${actual_checksum}" == "${stored_checksum}" ]] \
        || fail "Deployment stylesheet does not match its checksum."
}

backup_current_stylesheet() {
    [[ ! -L "${stylesheet_path}" ]] \
        || fail "Deployment stylesheet destination must not be a symbolic link."
    [[ ! -L "${stylesheet_checksum_path}" ]] \
        || fail "Stylesheet checksum destination must not be a symbolic link."

    if [[ -f "${stylesheet_path}" ]]; then
        previous_stylesheet_backup="$(mktemp /tmp/rollschool-previous-stylesheet.XXXXXX)"
        cp "${stylesheet_path}" "${previous_stylesheet_backup}"
        had_previous_stylesheet=true
    fi

    if [[ -f "${stylesheet_checksum_path}" ]]; then
        previous_checksum_backup="$(mktemp /tmp/rollschool-previous-stylesheet-checksum.XXXXXX)"
        cp "${stylesheet_checksum_path}" "${previous_checksum_backup}"
        had_previous_checksum=true
    fi
}

install_stylesheet() {
    local source_stylesheet="$1"
    local source_checksum="$2"

    verify_stylesheet "${source_stylesheet}" "${source_checksum}"

    destination_stylesheet_temp="$(mktemp "application/Resources/css/.bootstrap.min.css.deploy.XXXXXX")"
    cp "${source_stylesheet}" "${destination_stylesheet_temp}"
    chmod 644 "${destination_stylesheet_temp}"
    mv "${destination_stylesheet_temp}" "${stylesheet_path}"
    destination_stylesheet_temp=""

    destination_checksum_temp="$(mktemp "application/Resources/css/.bootstrap.min.css.sha256.deploy.XXXXXX")"
    cp "${source_checksum}" "${destination_checksum_temp}"
    chmod 600 "${destination_checksum_temp}"
    mv "${destination_checksum_temp}" "${stylesheet_checksum_path}"
    destination_checksum_temp=""
}

receive_deployment_stylesheet() {
    local deployed_commit="$1"
    local payload_header=""
    local payload_marker=""
    local payload_commit=""
    local expected_checksum=""
    local unexpected_value=""
    local actual_checksum=""
    local incoming_size=""
    if [[ ! -t 0 ]] && IFS= read -r payload_header; then
        read -r payload_marker payload_commit expected_checksum unexpected_value <<< "${payload_header}"
        [[ "${payload_marker}" == "ROLLSCHOOL_CSS_SHA256" \
            && "${payload_commit}" == "${deployed_commit}" \
            && "${expected_checksum}" =~ ^[0-9a-f]{64}$ \
            && -z "${unexpected_value}" ]] \
            || fail "Invalid deployment stylesheet payload header."

        incoming_stylesheet="$(mktemp /tmp/rollschool-stylesheet.XXXXXX)"
        chmod 600 "${incoming_stylesheet}"
        head -c "$((maximum_stylesheet_size + 1))" > "${incoming_stylesheet}"
        incoming_size="$(wc -c < "${incoming_stylesheet}")"
        [[ "${incoming_size}" -gt 0 && "${incoming_size}" -le "${maximum_stylesheet_size}" ]] \
            || fail "Deployment stylesheet must contain between 1 and ${maximum_stylesheet_size} bytes."

        actual_checksum="$(file_sha256 "${incoming_stylesheet}")"
        [[ "${actual_checksum}" == "${expected_checksum}" ]] \
            || fail "Received deployment stylesheet does not match its checksum."

        incoming_checksum="$(mktemp /tmp/rollschool-stylesheet-checksum.XXXXXX)"
        printf '%s\n' "${expected_checksum}" > "${incoming_checksum}"
        install_stylesheet "${incoming_stylesheet}" "${incoming_checksum}"
    else
        verify_stylesheet "${stylesheet_path}" "${stylesheet_checksum_path}"
    fi
}

trap cleanup EXIT

[[ -n "${expected_host}" ]] || fail "Usage: $0 EXPECTED_HOST [EXPECTED_URL] [EXPECTED_ENVIRONMENT]"
[[ "${expected_host}" =~ ^[a-z0-9.-]+$ ]] || fail "Invalid expected host: ${expected_host}"
expected_url="${expected_url:-https://${expected_host}/}"
[[ "${expected_url}" =~ ^https?://[a-z0-9.-]+/$ ]] || fail "Invalid expected URL: ${expected_url}"
[[ "${expected_environment}" =~ ^[A-Z]+$ ]] || fail "Invalid expected environment: ${expected_environment}"

command -v php >/dev/null 2>&1 || fail "Missing required command: php"
command -v git >/dev/null 2>&1 || fail "Missing required command: git"

deployed_commit="$(git rev-parse HEAD)"
[[ "${deployed_commit}" =~ ^[0-9a-f]{40}$ ]] || fail "Could not determine the deployed commit."

if git ls-files --error-unmatch "${stylesheet_path}" >/dev/null 2>&1; then
    fail "Generated ${stylesheet_path} must not be tracked by Git."
fi
if git ls-files --error-unmatch "${stylesheet_checksum_path}" >/dev/null 2>&1; then
    fail "Generated ${stylesheet_checksum_path} must not be tracked by Git."
fi

backup_current_stylesheet
stylesheet_changes_started=true

case "${expected_host}" in
    test-next.rollschool.pl)
        receive_deployment_stylesheet "${deployed_commit}"
        ;;
    rollschool.pl)
        receive_deployment_stylesheet "${deployed_commit}"
        ;;
    *)
        fail "No deployment stylesheet source is configured for ${expected_host}."
        ;;
esac

if [[ -f "${deployment_environment_file}" ]]; then
    [[ ! -L "${deployment_environment_file}" ]] \
        || fail "Deployment environment file must not be a symbolic link."
    [[ -O "${deployment_environment_file}" ]] \
        || fail "Deployment environment file must be owned by the deployment user."
    set -a
    # shellcheck disable=SC1090
    source "${deployment_environment_file}"
    set +a
fi

if command -v composer >/dev/null 2>&1; then
    composer_command=(composer)
elif [[ -f "${project_root}/composer.phar" ]]; then
    composer_command=(php "${project_root}/composer.phar")
elif [[ -n "${HOME:-}" && -f "${HOME}/tools/composer.phar" ]]; then
    composer_command=(php "${HOME}/tools/composer.phar")
else
    fail "Composer is unavailable. Install it with scripts/install-composer-on-ovh.sh."
fi

php_version_id="$(php -r 'echo PHP_VERSION_ID;')"
if (( php_version_id < 80500 )); then
    fail "PHP 8.5 or newer is required; detected $(php -r 'echo PHP_VERSION;')."
fi

for extension_name in curl fileinfo intl mbstring openssl PDO pdo_mysql session zlib; do
    if ! php -r "exit(extension_loaded('${extension_name}') ? 0 : 1);"; then
        fail "Missing required PHP extension: ${extension_name}"
    fi
done

for required_file in composer.json composer.lock public_html/index.php public_html/.htaccess \
    scripts/database-migrations.php "${config_file}" "${stylesheet_path}"; do
    [[ -f "${required_file}" ]] || fail "Missing required file: ${required_file}"
done

[[ -s "${stylesheet_path}" ]] || fail "Deployment stylesheet is empty: ${stylesheet_path}"

for required_directory in Files Files/img Files/pdf application/scheduled_tasks database/migrations; do
    [[ -d "${required_directory}" ]] || fail "Missing required directory: ${required_directory}"
done

[[ -r "${config_file}" ]] || fail "Application config is not readable: ${config_file}"
[[ -w Files ]] || fail "Runtime directory is not writable: Files"
[[ -w application/scheduled_tasks ]] || fail "Scheduled task directory is not writable."

"${composer_command[@]}" install \
    --no-dev \
    --prefer-dist \
    --optimize-autoloader \
    --no-interaction
"${composer_command[@]}" check-platform-reqs --no-dev

[[ -f vendor/autoload.php ]] || fail "Composer did not create vendor/autoload.php."

git ls-files -z -- '*.php' | while IFS= read -r -d '' php_file; do
    php -l "${php_file}" >/dev/null
done

php -r '
$_SERVER["HTTP_HOST"] = $argv[1];
require "application/config/config.php";
require "application/Libs/CommunicationConfiguration.php";
\Mini\Libs\CommunicationConfiguration::validate();
echo "Communication configuration OK." . PHP_EOL;
' "${expected_host}"

php scripts/database-migrations.php migrate "${expected_host}"

php -r '
$configFile = $argv[1];
$expectedHost = $argv[2];
$expectedUrl = $argv[3];
$expectedEnvironment = $argv[4];
$_SERVER["HTTP_HOST"] = $expectedHost;
$_SERVER["HTTPS"] = str_starts_with($expectedUrl, "https://") ? "on" : "off";
require $configFile;

if (!defined("APP_PUBLIC_URL") || rtrim((string) APP_PUBLIC_URL, "/") . "/" !== $expectedUrl) {
    fwrite(STDERR, "APP_PUBLIC_URL does not match " . $expectedUrl . PHP_EOL);
    exit(1);
}
if (!defined("ENVIRONMENT") || ENVIRONMENT !== $expectedEnvironment) {
    fwrite(STDERR, "ENVIRONMENT does not match " . $expectedEnvironment . "." . PHP_EOL);
    exit(1);
}
if (!defined("DB_TYPE") || DB_TYPE !== "mysql") {
    fwrite(STDERR, "The deployment expects the mysql PDO driver." . PHP_EOL);
    exit(1);
}

$dsn = DB_TYPE . ":host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
$database = new PDO($dsn, DB_USER, DB_PASS, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
$databaseVersion = (string) $database->query("SELECT VERSION()")->fetchColumn();
if (stripos($databaseVersion, "mariadb") !== false
    || !preg_match("/^(\\d+\\.\\d+\\.\\d+)/", $databaseVersion, $matches)
    || version_compare($matches[1], "8.0.0", "<")) {
    fwrite(STDERR, "MySQL/Percona 8.0 or newer is required; detected " . $databaseVersion . PHP_EOL);
    exit(1);
}

echo "PHP " . PHP_VERSION . PHP_EOL;
echo "Database " . $databaseVersion . PHP_EOL;
echo "Application " . $expectedUrl . " (" . $expectedEnvironment . ")" . PHP_EOL;
' "${config_file}" "${expected_host}" "${expected_url}" "${expected_environment}"

echo "Code, dependencies, runtime paths and database connectivity are valid."
echo "Pending database migrations and their pre-migration backup are valid."
deployment_succeeded=true
