#!/bin/bash
#
# Payload CMS - Tägliches PostgreSQL Backup-Skript
# Erstellt: 11.12.2025
# Aktualisiert: 11.12.2025 (S3 Offsite-Backup)
#
# Verwendung:
#   ./backup-db.sh           # Normales Backup
#   ./backup-db.sh --verbose # Mit detaillierter Ausgabe
#
# Voraussetzungen:
#   - ~/.pgpass mit korrekten Berechtigungen (chmod 600)
#   - ~/.s3cfg mit korrekten Berechtigungen (chmod 600) für Offsite-Backup
#

set -euo pipefail

# ============================================================================
# Konfiguration
# ============================================================================

# Datenbank-Verbindung (Passwort via ~/.pgpass)
DB_HOST="${PGHOST:-10.10.181.101}"
DB_PORT="${PGPORT:-5432}"
DB_NAME="${PGDATABASE:-payload_db}"
DB_USER="${PGUSER:-payload}"

# Backup-Verzeichnis
BACKUP_DIR="/home/payload/backups/postgres"

# Retention: Anzahl der Tage, die Backups behalten werden (lokal und S3)
RETENTION_DAYS=30

# Log-Datei und maximale Größe (10MB)
LOG_FILE="${BACKUP_DIR}/backup.log"
LOG_MAX_SIZE=$((10 * 1024 * 1024))

# S3 Offsite-Backup (Credentials via ~/.s3cfg)
S3_ENABLED="${S3_BACKUP_ENABLED:-true}"
S3_BUCKET="${S3_BUCKET:-c2s}"
S3_PATH="${S3_PATH:-backups/postgres}"

# ============================================================================
# Variablen
# ============================================================================

TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
BACKUP_FILE="${BACKUP_DIR}/payload_db_${TIMESTAMP}.sql.gz"
VERBOSE=${1:-""}

# ============================================================================
# Funktionen
# ============================================================================

log() {
    local message="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
    echo "$message" >> "$LOG_FILE"
    if [[ "$VERBOSE" == "--verbose" ]]; then
        echo "$message"
    fi
}

log_error() {
    local message="[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1"
    echo "$message" >> "$LOG_FILE"
    echo "$message" >&2
}

rotate_log() {
    if [[ -f "$LOG_FILE" ]]; then
        local log_size
        log_size=$(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0)

        if [[ $log_size -gt $LOG_MAX_SIZE ]]; then
            # Behalte die letzten 1000 Zeilen
            tail -1000 "$LOG_FILE" > "${LOG_FILE}.tmp"
            mv "${LOG_FILE}.tmp" "$LOG_FILE"
            log "Log-Datei rotiert (war ${log_size} Bytes)"
        fi
    fi
}

check_pgpass() {
    local pgpass_file="$HOME/.pgpass"

    if [[ ! -f "$pgpass_file" ]]; then
        log_error ".pgpass Datei nicht gefunden: $pgpass_file"
        log_error "Erstelle sie mit: echo 'host:port:database:user:password' > ~/.pgpass && chmod 600 ~/.pgpass"
        exit 1
    fi

    local perms
    perms=$(stat -c%a "$pgpass_file" 2>/dev/null || echo "000")

    if [[ "$perms" != "600" ]]; then
        log_error ".pgpass hat unsichere Berechtigungen: $perms (erwartet: 600)"
        log_error "Behebe mit: chmod 600 ~/.pgpass"
        exit 1
    fi
}

check_s3cfg() {
    local s3cfg_file="$HOME/.s3cfg"

    if [[ ! -f "$s3cfg_file" ]]; then
        log "WARNUNG: .s3cfg nicht gefunden - S3-Upload wird übersprungen"
        return 1
    fi

    local perms
    perms=$(stat -c%a "$s3cfg_file" 2>/dev/null || echo "000")

    if [[ "$perms" != "600" ]]; then
        log_error ".s3cfg hat unsichere Berechtigungen: $perms (erwartet: 600)"
        log_error "Behebe mit: chmod 600 ~/.s3cfg"
        return 1
    fi

    if ! command -v s3cmd &> /dev/null; then
        log "WARNUNG: s3cmd nicht installiert - S3-Upload wird übersprungen"
        return 1
    fi

    return 0
}

upload_to_s3() {
    local backup_file="$1"
    local filename
    filename=$(basename "$backup_file")

    log "--- S3 Offsite-Backup ---"
    log "Lade hoch nach s3://${S3_BUCKET}/${S3_PATH}/"

    local start_time
    start_time=$(date +%s)

    if s3cmd put "$backup_file" "s3://${S3_BUCKET}/${S3_PATH}/" 2>> "$LOG_FILE"; then
        local end_time
        end_time=$(date +%s)
        local duration=$((end_time - start_time))

        log "S3-Upload erfolgreich: $filename (${duration}s)"
        return 0
    else
        log_error "S3-Upload fehlgeschlagen für: $filename"
        return 1
    fi
}

cleanup_s3_backups() {
    log "Prüfe S3 auf alte Backups..."

    local cutoff_date
    cutoff_date=$(date -d "-${RETENTION_DAYS} days" +"%Y-%m-%d")

    local deleted_count=0

    # Liste alle Backups im S3-Bucket
    while IFS= read -r line; do
        # Format: 2025-12-11 09:54        42356  s3://c2s/backups/postgres/payload_db_...
        local file_date file_path
        file_date=$(echo "$line" | awk '{print $1}')
        file_path=$(echo "$line" | awk '{print $4}')

        if [[ -n "$file_path" && "$file_date" < "$cutoff_date" ]]; then
            if s3cmd del "$file_path" 2>> "$LOG_FILE"; then
                log "S3 gelöscht: $(basename "$file_path")"
                ((deleted_count++))
            fi
        fi
    done < <(s3cmd ls "s3://${S3_BUCKET}/${S3_PATH}/" 2>/dev/null | grep "payload_db_.*\.sql\.gz")

    if [[ $deleted_count -gt 0 ]]; then
        log "S3: $deleted_count alte Backups gelöscht"
    else
        log "S3: Keine alten Backups zum Löschen"
    fi
}

get_s3_stats() {
    local s3_count s3_size

    s3_count=$(s3cmd ls "s3://${S3_BUCKET}/${S3_PATH}/" 2>/dev/null | grep -c "payload_db_.*\.sql\.gz" || echo 0)
    s3_size=$(s3cmd ls "s3://${S3_BUCKET}/${S3_PATH}/" 2>/dev/null | grep "payload_db_.*\.sql\.gz" | awk '{sum+=$3} END {print sum}' || echo 0)

    # Bytes zu lesbarer Größe konvertieren (ohne bc)
    local s3_size_human
    if [[ $s3_size -gt 1073741824 ]]; then
        s3_size_human="$((s3_size / 1073741824))G"
    elif [[ $s3_size -gt 1048576 ]]; then
        s3_size_human="$((s3_size / 1048576))M"
    elif [[ $s3_size -gt 1024 ]]; then
        s3_size_human="$((s3_size / 1024))K"
    else
        s3_size_human="${s3_size}B"
    fi

    log "S3 Backups: $s3_count (${s3_size_human})"
}

cleanup_old_backups() {
    log "Lösche lokale Backups älter als ${RETENTION_DAYS} Tage..."

    local deleted_count=0
    while IFS= read -r -d '' file; do
        rm -f "$file"
        log "Gelöscht: $(basename "$file")"
        ((deleted_count++))
    done < <(find "$BACKUP_DIR" -name "payload_db_*.sql.gz" -type f -mtime +${RETENTION_DAYS} -print0 2>/dev/null)

    if [[ $deleted_count -gt 0 ]]; then
        log "Lokal: $deleted_count alte Backups gelöscht"
    else
        log "Lokal: Keine alten Backups zum Löschen"
    fi
}

get_backup_stats() {
    local backup_count
    local total_size
    local oldest_backup
    local newest_backup

    backup_count=$(find "$BACKUP_DIR" -name "payload_db_*.sql.gz" -type f 2>/dev/null | wc -l)
    total_size=$(find "$BACKUP_DIR" -name "payload_db_*.sql.gz" -type f -exec du -ch {} + 2>/dev/null | tail -1 | cut -f1)
    oldest_backup=$(find "$BACKUP_DIR" -name "payload_db_*.sql.gz" -type f -printf '%T+ %p\n' 2>/dev/null | sort | head -1 | cut -d' ' -f2 | xargs basename 2>/dev/null || echo "N/A")
    newest_backup=$(find "$BACKUP_DIR" -name "payload_db_*.sql.gz" -type f -printf '%T+ %p\n' 2>/dev/null | sort -r | head -1 | cut -d' ' -f2 | xargs basename 2>/dev/null || echo "N/A")

    log "--- Backup-Statistik ---"
    log "Lokale Backups: $backup_count (${total_size:-0})"
    log "Ältestes: $oldest_backup"
    log "Neuestes: $newest_backup"
}

# ============================================================================
# Hauptprogramm
# ============================================================================

main() {
    # Log-Rotation am Anfang
    rotate_log

    log "=========================================="
    log "Starte PostgreSQL Backup für $DB_NAME"
    log "=========================================="

    # Prüfe ob Backup-Verzeichnis existiert
    if [[ ! -d "$BACKUP_DIR" ]]; then
        mkdir -p "$BACKUP_DIR"
        log "Backup-Verzeichnis erstellt: $BACKUP_DIR"
    fi

    # Prüfe .pgpass
    check_pgpass

    # Prüfe Datenbankverbindung
    log "Prüfe Datenbankverbindung..."
    if ! psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT 1" > /dev/null 2>&1; then
        log_error "Datenbankverbindung fehlgeschlagen!"
        log_error "Prüfe ~/.pgpass und Netzwerkverbindung zu $DB_HOST:$DB_PORT"
        exit 1
    fi
    log "Datenbankverbindung erfolgreich"

    # Erstelle Backup
    log "Erstelle Backup: $(basename "$BACKUP_FILE")"

    local start_time
    start_time=$(date +%s)

    if pg_dump \
        -h "$DB_HOST" \
        -p "$DB_PORT" \
        -U "$DB_USER" \
        -d "$DB_NAME" \
        --format=plain \
        --no-owner \
        --no-privileges \
        --clean \
        --if-exists \
        2>> "$LOG_FILE" | gzip > "$BACKUP_FILE"; then

        local end_time
        end_time=$(date +%s)
        local duration=$((end_time - start_time))
        local file_size
        file_size=$(du -h "$BACKUP_FILE" | cut -f1)

        log "Backup erfolgreich erstellt!"
        log "Datei: $(basename "$BACKUP_FILE")"
        log "Größe: $file_size"
        log "Dauer: ${duration}s"

        # Verifiziere Backup
        if gzip -t "$BACKUP_FILE" 2>/dev/null; then
            log "Backup-Integrität: OK (gzip-Test bestanden)"
        else
            log_error "Backup-Integrität: FEHLER (gzip-Test fehlgeschlagen)"
            exit 1
        fi
    else
        log_error "Backup fehlgeschlagen!"
        rm -f "$BACKUP_FILE"
        exit 1
    fi

    # S3 Offsite-Backup
    local s3_success=false
    if [[ "$S3_ENABLED" == "true" ]]; then
        if check_s3cfg; then
            if upload_to_s3 "$BACKUP_FILE"; then
                s3_success=true
                cleanup_s3_backups
            fi
        else
            log "S3-Backup übersprungen (Konfiguration fehlt)"
        fi
    else
        log "S3-Backup deaktiviert (S3_BACKUP_ENABLED=false)"
    fi

    # Lösche alte lokale Backups
    cleanup_old_backups

    # Zeige Statistik
    get_backup_stats
    if [[ "$s3_success" == "true" ]]; then
        get_s3_stats
    fi

    log "=========================================="
    log "Backup abgeschlossen"
    log "=========================================="

    # Ausgabe für Cron (nur bei Erfolg, nicht-verbose)
    if [[ "$VERBOSE" != "--verbose" ]]; then
        if [[ "$s3_success" == "true" ]]; then
            echo "Backup OK: $(basename "$BACKUP_FILE") ($file_size) + S3"
        else
            echo "Backup OK: $(basename "$BACKUP_FILE") ($file_size)"
        fi
    fi
}

# Skript ausführen
main "$@"
