#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
umask 022

SOURCE_ROOT="${DVM_GEN_SOURCE_ROOT:-/var/www/html/presenze.distribuite}"
SOURCE_DB="${DVM_GEN_SOURCE_DB:-presenze_distribuite}"
SOURCE_DOMAIN="${DVM_GEN_SOURCE_DOMAIN:-presenze.distribuite.axuno.it}"
WEB_ROOT="${DVM_GEN_WEB_ROOT:-/var/www/html}"
SITES_AVAILABLE="${DVM_GEN_SITES_AVAILABLE:-/etc/apache2/sites-available}"
SITES_ENABLED="${DVM_GEN_SITES_ENABLED:-/etc/apache2/sites-enabled}"
SOURCE_VHOST="${DVM_GEN_SOURCE_VHOST:-${SITES_AVAILABLE}/${SOURCE_DOMAIN}.conf}"
LOCK_FILE="${DVM_GEN_LOCK_FILE:-/var/lock/dvm-presenze-portal-generator.lock}"
LOG_FILE="${DVM_GEN_LOG_FILE:-/var/log/dvm-presenze-portal-generator.log}"
MYSQL_BIN="${DVM_GEN_MYSQL_BIN:-mysql}"
MYSQLDUMP_BIN="${DVM_GEN_MYSQLDUMP_BIN:-mysqldump}"
APACHECTL_BIN="${DVM_GEN_APACHECTL_BIN:-apache2ctl}"
A2ENSITE_BIN="${DVM_GEN_A2ENSITE_BIN:-a2ensite}"
A2DISSITE_BIN="${DVM_GEN_A2DISSITE_BIN:-a2dissite}"
RSYNC_BIN="${DVM_GEN_RSYNC_BIN:-rsync}"
CURL_BIN="${DVM_GEN_CURL_BIN:-curl}"
EXPECTED_HOST="${DVM_GEN_EXPECTED_HOST:-ax-accessi}"
EXPECTED_IP="${DVM_GEN_EXPECTED_IP:-192.168.215.6}"
TEST_MODE="${DVM_GEN_TEST_MODE:-0}"

COMMAND="${1:-}"
SLUG="${2:-}"
TARGET_DOMAIN=""
TARGET_ROOT=""
TARGET_DB=""
TARGET_VHOST=""
TARGET_VHOST_NAME=""
STAGE_ROOT=""
HTTP_BODY=""
HTTP_HEADERS=""
CREATED_DB=0
CREATED_ROOT=0
CREATED_VHOST=0
ENABLED_SITE=0
IN_ROLLBACK=0
SOURCE_OBJECTS=0
TARGET_OBJECTS=0
HTTP_STATUS=""

usage() {
    cat <<'USAGE'
Uso:
  dvm-presenze-portal-generator inspect
  dvm-presenze-portal-generator plan <slug>
  dvm-presenze-portal-generator create <slug>

Regole slug:
  lettere minuscole, numeri e trattini; deve iniziare e finire con lettera o numero.

Nomi generati:
  dominio:   presenze.<slug>.axuno.it
  cartella:  /var/www/html/presenze.<slug>
  database:  presenze_<slug_con_trattini_convertiti_in_underscore>
USAGE
}

emit() {
    printf '%s=%s\n' "$1" "$2"
}

log_event() {
    local message="$1"
    printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$message" >> "$LOG_FILE"
    if [[ "$TEST_MODE" != "1" ]]; then
        chown root:root "$LOG_FILE" 2>/dev/null || true
        chmod 0640 "$LOG_FILE" 2>/dev/null || true
    fi
}

validate_server() {
    [[ "$TEST_MODE" == "1" ]] && return 0
    local host_now ips_now
    host_now="$(hostname -s 2>/dev/null || hostname)"
    ips_now="$(hostname -I 2>/dev/null || true)"
    [[ "$host_now" == "$EXPECTED_HOST" ]] || { echo "ERRORE: host errato: $host_now" >&2; exit 20; }
    [[ " $ips_now " == *" $EXPECTED_IP "* ]] || { echo "ERRORE: IP atteso non presente: $ips_now" >&2; exit 21; }
}

validate_slug() {
    [[ "$SLUG" =~ ^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$ ]] || {
        echo "ERRORE: slug non valido: $SLUG" >&2
        exit 22
    }
    case "$SLUG" in
        distribuite|distro|silvelox|dvmservice|webservice)
            echo "ERRORE: slug riservato: $SLUG" >&2
            exit 23
            ;;
    esac
}

build_names() {
    local db_suffix
    db_suffix="${SLUG//-/_}"
    TARGET_DOMAIN="presenze.${SLUG}.axuno.it"
    TARGET_ROOT="${WEB_ROOT}/presenze.${SLUG}"
    TARGET_DB="presenze_${db_suffix}"
    TARGET_VHOST_NAME="${TARGET_DOMAIN}.conf"
    TARGET_VHOST="${SITES_AVAILABLE}/${TARGET_VHOST_NAME}"
}

mysql_scalar() {
    "$MYSQL_BIN" --batch --skip-column-names -e "$1"
}

database_exists() {
    local db="$1" result
    result="$(mysql_scalar "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='${db}'" 2>/dev/null || true)"
    [[ "$result" == "$db" ]]
}

database_object_count() {
    local db="$1"
    mysql_scalar "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='${db}'"
}

extract_php_setting() {
    local key="$1" file="$2" value
    value="$(sed -nE "s/^[[:space:]]*'${key}'[[:space:]]*=>[[:space:]]*'([^']*)'.*/\\1/p" "$file" | head -n 1)"
    [[ -n "$value" ]] || {
        echo "ERRORE: parametro ${key} non trovato in ${file}" >&2
        return 1
    }
    printf '%s' "$value"
}

application_db_access() {
    local settings_file="$1" expected_db="$2"
    local app_host app_user app_password app_database result
    app_host="$(extract_php_setting host "$settings_file")"
    app_user="$(extract_php_setting username "$settings_file")"
    app_password="$(extract_php_setting password "$settings_file")"
    app_database="$(extract_php_setting database "$settings_file")"
    [[ "$app_database" == "$expected_db" ]] || {
        echo "ERRORE: database applicativo inatteso in ${settings_file}: ${app_database}" >&2
        return 1
    }
    result="$(MYSQL_PWD="$app_password" "$MYSQL_BIN" --batch --skip-column-names --host="$app_host" --user="$app_user" "$app_database" -e 'SELECT 1' 2>/dev/null || true)"
    [[ "$result" == "1" ]] || {
        echo "ERRORE: l'utente applicativo non accede al database ${expected_db}" >&2
        return 1
    }
}

check_source() {
    [[ -d "$SOURCE_ROOT" ]] || { echo "ERRORE: base assente: $SOURCE_ROOT" >&2; exit 30; }
    [[ -f "$SOURCE_ROOT/phpgen_settings.php" ]] || { echo "ERRORE: phpgen_settings.php assente" >&2; exit 31; }
    [[ -f "$SOURCE_VHOST" ]] || { echo "ERRORE: vhost sorgente assente: $SOURCE_VHOST" >&2; exit 32; }
    [[ -L "$SOURCE_ROOT/bower_components" ]] || { echo "ERRORE: symlink bower_components assente" >&2; exit 33; }
    [[ -L "$SOURCE_ROOT/node_modules" ]] || { echo "ERRORE: symlink node_modules assente" >&2; exit 34; }
    [[ -d "$(readlink -f "$SOURCE_ROOT/bower_components")" ]] || { echo "ERRORE: target bower_components interrotto" >&2; exit 35; }
    [[ -d "$(readlink -f "$SOURCE_ROOT/node_modules")" ]] || { echo "ERRORE: target node_modules interrotto" >&2; exit 36; }
    database_exists "$SOURCE_DB" || { echo "ERRORE: database sorgente assente: $SOURCE_DB" >&2; exit 37; }
    grep -Fq "'database' => '${SOURCE_DB}'" "$SOURCE_ROOT/phpgen_settings.php" || {
        echo "ERRORE: riferimento database sorgente non trovato in phpgen_settings.php" >&2
        exit 38
    }
    application_db_access "$SOURCE_ROOT/phpgen_settings.php" "$SOURCE_DB" || exit 45
    grep -Fq "$SOURCE_DOMAIN" "$SOURCE_VHOST" || { echo "ERRORE: dominio sorgente non trovato nel vhost" >&2; exit 39; }
    grep -Fq "$SOURCE_ROOT" "$SOURCE_VHOST" || { echo "ERRORE: DocumentRoot sorgente non trovato nel vhost" >&2; exit 40; }
    SOURCE_OBJECTS="$(database_object_count "$SOURCE_DB")"
    [[ "$SOURCE_OBJECTS" =~ ^[0-9]+$ ]] && (( SOURCE_OBJECTS > 0 )) || {
        echo "ERRORE: inventario database sorgente non valido: ${SOURCE_OBJECTS}" >&2
        exit 46
    }
}

check_target_absent() {
    [[ ! -e "$TARGET_ROOT" ]] || { echo "ERRORE: cartella già esistente: $TARGET_ROOT" >&2; exit 41; }
    [[ ! -e "$TARGET_VHOST" ]] || { echo "ERRORE: vhost già esistente: $TARGET_VHOST" >&2; exit 42; }
    [[ ! -e "$SITES_ENABLED/$TARGET_VHOST_NAME" ]] || { echo "ERRORE: sito già abilitato: $TARGET_VHOST_NAME" >&2; exit 43; }
    ! database_exists "$TARGET_DB" || { echo "ERRORE: database già esistente: $TARGET_DB" >&2; exit 44; }
}

clear_runtime_dir() {
    local root="$1"
    local relative="$2"
    local dir="${root}/${relative}"
    [[ -d "$dir" ]] || return 0
    find "$dir" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
}

patch_database_name() {
    local file="$1" count
    count="$(grep -F -c "'database' => '${SOURCE_DB}'" "$file" || true)"
    [[ "$count" == "1" ]] || {
        echo "ERRORE: atteso un solo riferimento a ${SOURCE_DB} in ${file}, trovati ${count}" >&2
        return 1
    }
    sed -i "s/'database' => '${SOURCE_DB}'/'database' => '${TARGET_DB}'/" "$file"
    grep -Fq "'database' => '${TARGET_DB}'" "$file"
    ! grep -Fq "'database' => '${SOURCE_DB}'" "$file"
}

render_vhost() {
    local destination="$1"
    cp -- "$SOURCE_VHOST" "$destination"
    sed -i \
        -e "s#${SOURCE_DOMAIN}#${TARGET_DOMAIN}#g" \
        -e "s#${SOURCE_ROOT}#${TARGET_ROOT}#g" \
        "$destination"
    grep -Fq "ServerName ${TARGET_DOMAIN}" "$destination"
    grep -Fq "DocumentRoot ${TARGET_ROOT}" "$destination"
    grep -Fq "<Directory ${TARGET_ROOT}>" "$destination"
    ! grep -Fq "$SOURCE_DOMAIN" "$destination"
    ! grep -Fq "$SOURCE_ROOT" "$destination"
}

local_http_check() {
    HTTP_BODY="$(mktemp)"
    HTTP_HEADERS="$(mktemp)"
    HTTP_STATUS="$("$CURL_BIN" --silent --show-error --max-time 30 \
        --header "Host: ${TARGET_DOMAIN}" \
        --dump-header "$HTTP_HEADERS" \
        --output "$HTTP_BODY" \
        --write-out '%{http_code}' \
        'http://127.0.0.1/' || true)"
    [[ "$HTTP_STATUS" =~ ^[123][0-9][0-9]$ ]] || {
        echo "ERRORE: test HTTP locale fallito per ${TARGET_DOMAIN}, stato=${HTTP_STATUS:-N/D}" >&2
        return 1
    }
    if grep -Eiq 'Fatal error|Uncaught (Error|Exception)|Access denied for user|Unknown database' "$HTTP_BODY"; then
        echo "ERRORE: risposta HTTP contiene un errore applicativo fatale per ${TARGET_DOMAIN}" >&2
        return 1
    fi
    rm -f -- "$HTTP_BODY" "$HTTP_HEADERS"
    HTTP_BODY=""
    HTTP_HEADERS=""
}

rollback() {
    local rc=$?
    rm -f -- "${HTTP_BODY:-}" "${HTTP_HEADERS:-}" 2>/dev/null || true
    [[ "$COMMAND" == "create" ]] || exit "$rc"
    [[ $rc -ne 0 ]] || exit 0
    [[ $IN_ROLLBACK -eq 0 ]] || exit "$rc"
    IN_ROLLBACK=1
    trap - ERR
    echo "ROLLBACK: ripristino dopo errore." >&2
    if [[ $ENABLED_SITE -eq 1 ]]; then
        "$A2DISSITE_BIN" "$TARGET_VHOST_NAME" >/dev/null 2>&1 || true
    fi
    if [[ $CREATED_VHOST -eq 1 ]]; then
        rm -f -- "$TARGET_VHOST" "$SITES_ENABLED/$TARGET_VHOST_NAME" || true
    fi
    if [[ $CREATED_ROOT -eq 1 ]]; then
        rm -rf --one-file-system -- "$TARGET_ROOT" || true
    fi
    if [[ -n "$STAGE_ROOT" && -e "$STAGE_ROOT" ]]; then
        rm -rf --one-file-system -- "$STAGE_ROOT" || true
    fi
    if [[ $CREATED_DB -eq 1 ]]; then
        "$MYSQL_BIN" -e "DROP DATABASE IF EXISTS \`${TARGET_DB}\`" >/dev/null 2>&1 || true
    fi
    "$APACHECTL_BIN" configtest >/dev/null 2>&1 || true
    "$APACHECTL_BIN" graceful >/dev/null 2>&1 || true
    log_event "FAILED slug=${SLUG} rollback=completed exit=${rc}"
    exit "$rc"
}
trap rollback EXIT
trap 'echo "ERRORE linea ${LINENO}: ${BASH_COMMAND}" >&2' ERR

inspect_command() {
    check_source
    emit status OK
    emit source_root "$SOURCE_ROOT"
    emit source_database "$SOURCE_DB"
    emit source_domain "$SOURCE_DOMAIN"
    emit source_database_objects "$SOURCE_OBJECTS"
    emit application_database_access OK
    emit bower_target "$(readlink -f "$SOURCE_ROOT/bower_components")"
    emit node_target "$(readlink -f "$SOURCE_ROOT/node_modules")"
}

plan_command() {
    validate_slug
    build_names
    check_source
    check_target_absent
    emit status READY
    emit slug "$SLUG"
    emit domain "$TARGET_DOMAIN"
    emit document_root "$TARGET_ROOT"
    emit database "$TARGET_DB"
    emit source_database_objects "$SOURCE_OBJECTS"
    emit vhost "$TARGET_VHOST"
}

create_command() {
    [[ ${EUID} -eq 0 ]] || { echo "ERRORE: create richiede root." >&2; exit 50; }
    validate_slug
    build_names
    check_source
    check_target_absent

    mkdir -p "$(dirname "$LOCK_FILE")" "$(dirname "$LOG_FILE")"
    exec 9>"$LOCK_FILE"
    flock -n 9 || { echo "ERRORE: un'altra generazione è in corso." >&2; exit 51; }

    STAGE_ROOT="$(mktemp -d "${WEB_ROOT}/.dvm-generator.${SLUG}.XXXXXX")"
    log_event "START slug=${SLUG} domain=${TARGET_DOMAIN} database=${TARGET_DB}"

    "$RSYNC_BIN" -aH \
        --exclude='/bower_components' \
        --exclude='/node_modules' \
        "$SOURCE_ROOT/" "$STAGE_ROOT/"

    rm -rf -- "$STAGE_ROOT/bower_components" "$STAGE_ROOT/node_modules"
    ln -s "$(readlink "$SOURCE_ROOT/bower_components")" "$STAGE_ROOT/bower_components"
    ln -s "$(readlink "$SOURCE_ROOT/node_modules")" "$STAGE_ROOT/node_modules"

    while IFS= read -r runtime_dir; do
        clear_runtime_dir "$STAGE_ROOT" "$runtime_dir"
    done <<'RUNTIME_DIRS'
templates_c
log
libs/stimulsoft_legacy/tmp
libs/mpdf/tmp
libs/pdo/classes/library/tcpdf/cache
theme/php/uploads
webservices_report/log
webServiceTime/classes/library/tcpdf/cache
components/templates/custom_templates/ax_attendance/ax_schede_presenze_modA/timeCtrl_lastonsilvelox/log
components/templates/custom_templates/ax_attendance/ax_schede_presenze_modA/timeCtrl_silvelox_ok/log
components/templates/custom_templates/ax_attendance/ax_time_controll/timeCtrl/log
components/templates/custom_templates/ax_attendance/impostazioni-generali/ctrl/upload
RUNTIME_DIRS

    patch_database_name "$STAGE_ROOT/phpgen_settings.php"

    "$MYSQL_BIN" -e "CREATE DATABASE \`${TARGET_DB}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci"
    CREATED_DB=1
    "$MYSQLDUMP_BIN" --default-character-set=utf8mb4 --routines --events --triggers --hex-blob "$SOURCE_DB" \
        | "$MYSQL_BIN" --default-character-set=utf8mb4 "$TARGET_DB"

    TARGET_OBJECTS="$(database_object_count "$TARGET_DB")"
    [[ "$TARGET_OBJECTS" == "$SOURCE_OBJECTS" ]] || {
        echo "ERRORE: inventario database non coincidente: sorgente=${SOURCE_OBJECTS}, destinazione=${TARGET_OBJECTS}" >&2
        return 1
    }
    application_db_access "$STAGE_ROOT/phpgen_settings.php" "$TARGET_DB"

    mv -- "$STAGE_ROOT" "$TARGET_ROOT"
    STAGE_ROOT=""
    CREATED_ROOT=1

    render_vhost "$TARGET_VHOST"
    chown root:root "$TARGET_VHOST"
    chmod 0644 "$TARGET_VHOST"
    CREATED_VHOST=1

    "$A2ENSITE_BIN" "$TARGET_VHOST_NAME" >/dev/null
    ENABLED_SITE=1
    "$APACHECTL_BIN" configtest
    "$APACHECTL_BIN" graceful

    [[ -d "$TARGET_ROOT" ]]
    [[ -L "$TARGET_ROOT/bower_components" ]]
    [[ -L "$TARGET_ROOT/node_modules" ]]
    [[ -f "$TARGET_VHOST" ]]
    database_exists "$TARGET_DB"
    application_db_access "$TARGET_ROOT/phpgen_settings.php" "$TARGET_DB"
    local_http_check

    emit status CREATED
    emit slug "$SLUG"
    emit domain "$TARGET_DOMAIN"
    emit document_root "$TARGET_ROOT"
    emit database "$TARGET_DB"
    emit source_database_objects "$SOURCE_OBJECTS"
    emit target_database_objects "$TARGET_OBJECTS"
    emit application_database_access OK
    emit local_http_status "$HTTP_STATUS"
    emit vhost "$TARGET_VHOST"
    log_event "SUCCESS slug=${SLUG} domain=${TARGET_DOMAIN} database=${TARGET_DB} objects=${TARGET_OBJECTS} http=${HTTP_STATUS}"
}

case "$COMMAND" in
    -h|--help|help|'')
        usage
        exit 0
        ;;
    inspect)
        validate_server
        inspect_command
        ;;
    plan)
        validate_server
        plan_command
        ;;
    create)
        validate_server
        create_command
        ;;
    *)
        echo "ERRORE: comando sconosciuto: $COMMAND" >&2
        usage >&2
        exit 2
        ;;
esac
