#!/usr/bin/env bash
# =============================================================================
# sync_servers.sh — Server-to-Server Sync (Files via rsync + DB via MariaDB)
# Ausführung auf Server 2 (Ziel). Pulled FROM Server 1 (Quelle).
# =============================================================================
# KONFIGURATION — Hier alle Einstellungen anpassen
# =============================================================================

# --- SSH / Server 1 (Quelle) ---
SRC_HOST="mng323.whf-server.de"
SRC_USER="we8upmb"
SRC_SSH_PORT="1025"
SRC_SSH_KEY="/var/www/we8upmb/home/.ssh/id_ed25519"   # Privater Key auf Server 2

# --- Verzeichnis-Sync ---
# Format: "quelle:ziel" — mehrere Paare möglich
SYNC_DIRS=(
    "/var/www/we8upmb/htdocs/lb_staging:/var/www/we8upmb/htdocs/lb_production"
	"/var/www/we8upmb/htdocs/images:/var/www/we8upmb/htdocs/images"
)

# rsync-Optionen
# -a  = archive (rekursiv, symlinks, permissions, timestamps, owner, group)
# -z  = komprimieren
# -v  = verbose
RSYNC_OPTS="-azv --checksum --human-readable"
RSYNC_EXCLUDE=(
    ".git"
    "*.log"
    "*.tmp"
    "node_modules"
    ".env"
    "var/cache"
    "var/logs"
)

# --- MariaDB Quelle (Server 1) ---
DB_SRC_HOST="localhost"
DB_SRC_USER="12-production"
DB_SRC_PASS="O4NSh8E{^R_15U=T"
DB_SRC_DATABASES=(
    "12-production"
)

# --- MariaDB Ziel (Server 2, lokal) ---
DB_DST_HOST="127.0.0.1"
DB_DST_PORT="3306"
DB_DST_USER="12-production"
DB_DST_PASS="O4NSh8E{^R_15U=T"
# Format: "src_db:dst_db" oder leer lassen für 1:1
DB_MAPPING=(
    "12-production:12-production"
)

# Dump-Optionen
MARIADB_DUMP_OPTS="--single-transaction --quick --skip-lock-tables --routines --triggers"

# --- Backup ---
BACKUP_DIR="/var/www/we8upmb/htdocs/backup"
KEEP_BACKUPS=7
BACKUP_BEFORE_SYNC=false

# --- Logging ---
LOG_DIR="/var/www/we8upmb/htdocs/backup"
LOG_FILE="${LOG_DIR}/sync_$(date +%Y%m%d_%H%M%S).log"
LOG_RETENTION_DAYS=30
NOTIFY_ON_ERROR=true
NOTIFY_EMAIL="s.swoboda@luftbude.de"

# --- Verhalten ---
DRY_RUN=false
ABORT_ON_RSYNC_ERROR=true
ABORT_ON_DB_ERROR=true

# =============================================================================
# KEIN ÄNDERUNGSBEDARF AB HIER
# =============================================================================

set -euo pipefail

# --- Farben ---
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'

# --- Globals ---
ERRORS=0
START_TIME=$(date +%s)

# =============================================================================
# FUNKTIONEN
# =============================================================================

log() {
    local level="$1"; shift
    local msg="$*"
    local ts
    ts="$(date '+%Y-%m-%d %H:%M:%S')"
    local color="${NC}"
    case "$level" in
        INFO)  color="${GREEN}"  ;;
        WARN)  color="${YELLOW}" ;;
        ERROR) color="${RED}"    ;;
        STEP)  color="${CYAN}"   ;;
        DRY)   color="${BLUE}"   ;;
    esac
    local line="[${ts}] [${level}] ${msg}"
    echo -e "${color}${line}${NC}" | tee -a "${LOG_FILE}"
}

die() {
    log ERROR "$*"
    if [[ "${NOTIFY_ON_ERROR}" == "true" ]] && command -v mail &>/dev/null; then
        echo "Sync-Fehler auf $(hostname): $*" | mail -s "[SYNC ERROR] $(hostname)" "${NOTIFY_EMAIL}" 2>/dev/null || true
    fi
    exit 1
}

check_deps() {
    log INFO "Prüfe Abhängigkeiten..."
    local deps=("rsync" "ssh" "mariadb")
    for dep in "${deps[@]}"; do
        command -v "$dep" &>/dev/null || die "Abhängigkeit fehlt: ${dep}"
    done
    log INFO "Alle Abhängigkeiten vorhanden."
}

setup_dirs() {
    mkdir -p "${LOG_DIR}" "${BACKUP_DIR}"
}

cleanup_old_logs() {
    find "${LOG_DIR}" -name "sync_*.log" -mtime +"${LOG_RETENTION_DAYS}" -delete 2>/dev/null || true
}

cleanup_old_backups() {
    local count
    count=$(find "${BACKUP_DIR}" -name "*.sql.gz" | wc -l)
    if (( count > KEEP_BACKUPS )); then
        find "${BACKUP_DIR}" -name "*.sql.gz" -printf '%T+ %p\n' \
            | sort | head -n $(( count - KEEP_BACKUPS )) \
            | awk '{print $2}' | xargs rm -f
        log INFO "Alte Backups bereinigt (behalte ${KEEP_BACKUPS})."
    fi
}

# =============================================================================
# RSYNC
# =============================================================================

build_rsync_exclude_args() {
    local args=()
    for pattern in "${RSYNC_EXCLUDE[@]}"; do
        args+=("--exclude=${pattern}")
    done
    echo "${args[@]}"
}

sync_directories() {
    log STEP "===== DATEI-SYNC via rsync ====="

    local exclude_args
    exclude_args=$(build_rsync_exclude_args)

    local dry_run_flag=""
    if [[ "${DRY_RUN}" == "true" ]]; then
        dry_run_flag="--dry-run"
        log DRY "[DRY-RUN] Keine Änderungen werden vorgenommen."
    fi

    for pair in "${SYNC_DIRS[@]}"; do
        local src_dir="${pair%%:*}"
        local dst_dir="${pair##*:}"

        log INFO "Sync: ${SRC_USER}@${SRC_HOST}:${src_dir} -> ${dst_dir}"

        mkdir -p "${dst_dir}"

        local rsync_cmd=(
            rsync
            ${RSYNC_OPTS}
            ${dry_run_flag}
            -e "ssh -p ${SRC_SSH_PORT} -i ${SRC_SSH_KEY} -o StrictHostKeyChecking=no"
            ${exclude_args}
            "${SRC_USER}@${SRC_HOST}:${src_dir}/"
            "${dst_dir}/"
        )

        log INFO "Befehl: ${rsync_cmd[*]}"

        if ! "${rsync_cmd[@]}" 2>&1 | tee -a "${LOG_FILE}"; then
            ERRORS=$(( ERRORS + 1 ))
            log ERROR "rsync fehlgeschlagen für ${src_dir}"
            if [[ "${ABORT_ON_RSYNC_ERROR}" == "true" ]]; then
                die "rsync abgebrochen."
            fi
        else
            log INFO "rsync erfolgreich: ${src_dir} -> ${dst_dir}"
        fi
    done
}

# =============================================================================
# DATENBANK
# =============================================================================

db_local_backup() {
    local db="$1"
    local backup_file="${BACKUP_DIR}/${db}_$(date +%Y%m%d_%H%M%S).sql.gz"
    log INFO "Erstelle lokales Backup: ${db} -> ${backup_file}"
    if [[ "${DRY_RUN}" == "true" ]]; then
        log DRY "[DRY-RUN] Würde Backup erstellen: ${backup_file}"; return 0
    fi
    mariadb-dump \
        -h "${DB_DST_HOST}" -P "${DB_DST_PORT}" \
        -u "${DB_DST_USER}" -p"${DB_DST_PASS}" \
        ${MARIADB_DUMP_OPTS} \
        "${db}" | gzip > "${backup_file}" \
        || { log WARN "Lokales Backup für ${db} fehlgeschlagen (DB existiert ggf. noch nicht)."; return 0; }
    log INFO "Backup erstellt: ${backup_file}"
}

resolve_db_mapping() {
    local src_db="$1"
    for mapping in "${DB_MAPPING[@]}"; do
        local map_src="${mapping%%:*}"
        local map_dst="${mapping##*:}"
        if [[ "${map_src}" == "${src_db}" ]]; then
            echo "${map_dst}"; return 0
        fi
    done
    echo "${src_db}"
}

sync_databases() {
    log STEP "===== DATENBANK-SYNC via MariaDB ====="

    for src_db in "${DB_SRC_DATABASES[@]}"; do
        local dst_db
        dst_db=$(resolve_db_mapping "${src_db}")

        log INFO "DB-Sync: ${src_db} (Quelle) -> ${dst_db} (Ziel)"

        # Optionales lokales Backup vor dem Überschreiben
        if [[ "${BACKUP_BEFORE_SYNC}" == "true" ]]; then
            db_local_backup "${dst_db}"
        fi

        if [[ "${DRY_RUN}" == "true" ]]; then
            log DRY "[DRY-RUN] Würde DB ${src_db} -> ${dst_db} synchronisieren."
            continue
        fi

        # Ziel-DB anlegen falls nicht vorhanden
        mariadb \
            -h "${DB_DST_HOST}" -P "${DB_DST_PORT}" \
            -u "${DB_DST_USER}" -p"${DB_DST_PASS}" \
            -e "CREATE DATABASE IF NOT EXISTS \`${dst_db}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" \
            2>>"${LOG_FILE}" || die "Konnte Datenbank ${dst_db} nicht anlegen."

        # Dump via SSH auf Server 1 (localhost = Unix-Socket, kein Tunnel nötig)
        local dump_file="${BACKUP_DIR}/sync_${src_db}_$$.sql"
        log INFO "Starte mariadb-dump via SSH auf ${SRC_HOST} -> ${dump_file}..."

        if ! ssh -p "${SRC_SSH_PORT}" -i "${SRC_SSH_KEY}" -o StrictHostKeyChecking=no \
                "${SRC_USER}@${SRC_HOST}" \
                "mariadb-dump -h localhost -u '${DB_SRC_USER}' -p'${DB_SRC_PASS}' ${MARIADB_DUMP_OPTS} '${src_db}'" \
                > "${dump_file}" 2>>"${LOG_FILE}"; then
            rm -f "${dump_file}"
            ERRORS=$(( ERRORS + 1 ))
            log ERROR "mariadb-dump fehlgeschlagen: ${src_db}"
            if [[ "${ABORT_ON_DB_ERROR}" == "true" ]]; then
                die "DB-Sync abgebrochen (Dump fehlgeschlagen)."
            fi
            continue
        fi

        # Prüfen ob Dump-Datei nicht leer ist
        if [[ ! -s "${dump_file}" ]]; then
            rm -f "${dump_file}"
            ERRORS=$(( ERRORS + 1 ))
            log ERROR "Dump-Datei für ${src_db} ist leer — Import übersprungen."
            if [[ "${ABORT_ON_DB_ERROR}" == "true" ]]; then
                die "DB-Sync abgebrochen (leerer Dump)."
            fi
            continue
        fi

        log INFO "Dump OK ($(du -h "${dump_file}" | cut -f1)). Importiere in ${dst_db}..."

        if ! mariadb \
                -h "${DB_DST_HOST}" -P "${DB_DST_PORT}" \
                -u "${DB_DST_USER}" -p"${DB_DST_PASS}" \
                "${dst_db}" < "${dump_file}" 2>>"${LOG_FILE}"; then
            ERRORS=$(( ERRORS + 1 ))
            log ERROR "DB-Import fehlgeschlagen: ${src_db} -> ${dst_db}"
            if [[ "${ABORT_ON_DB_ERROR}" == "true" ]]; then
                rm -f "${dump_file}"
                die "DB-Sync abgebrochen (Import fehlgeschlagen)."
            fi
        else
            log INFO "DB-Sync erfolgreich: ${src_db} -> ${dst_db}"
        fi
        rm -f "${dump_file}"
    done
}

# =============================================================================
# ZUSAMMENFASSUNG
# =============================================================================

print_summary() {
    local end_time elapsed
    end_time=$(date +%s)
    elapsed=$(( end_time - START_TIME ))
    local minutes=$(( elapsed / 60 ))
    local seconds=$(( elapsed % 60 ))

    log STEP "===== ZUSAMMENFASSUNG ====="
    log INFO "Dauer:  ${minutes}m ${seconds}s"
    log INFO "Fehler: ${ERRORS}"
    if (( ERRORS == 0 )); then
        log INFO "Status: ✓ Erfolgreich"
    else
        log WARN "Status: ✗ Mit Fehlern abgeschlossen (${ERRORS} Fehler)"
    fi
    log INFO "Log:    ${LOG_FILE}"
}

# =============================================================================
# MAIN
# =============================================================================

main() {
    setup_dirs
    log STEP "===== SYNC START — $(date '+%Y-%m-%d %H:%M:%S') ====="
    [[ "${DRY_RUN}" == "true" ]] && log DRY "DRY-RUN MODUS — keine Änderungen werden durchgeführt."

    check_deps
    cleanup_old_logs
    cleanup_old_backups
    sync_directories
    sync_databases
    print_summary

    (( ERRORS > 0 )) && exit 1 || exit 0
}

main "$@"