#!/bin/bash

#===============================================================================
# SCRIPT DE BACKUP
# Sauvegarde un dossier avec exclusions, compression et rotation
#===============================================================================

set -euo pipefail

# Variables globales
SCRIPT_NAME=$(basename "$0" .sh)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONFIG_FILE="${SCRIPT_DIR}/${SCRIPT_NAME}.ini"
LOG_FILE="${SCRIPT_DIR}/${SCRIPT_NAME}.log"
VERSION="1.0.0"

# Valeurs par défaut
SOURCE_DIR="/opt/immich/"
BACKUP_DIR="/var/backups"
EXCLUDES=""
RETENTION_DAYS=7
BACKUP_NAME="backup"

#===============================================================================
# FONCTIONS UTILITAIRES
#===============================================================================

# Fonction de logging
log() {
    local level="$1"
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[${timestamp}] [${level}] ${message}" | tee -a "$LOG_FILE"
}

log_info()  { log "INFO" "$@"; }
log_warn()  { log "WARN" "$@"; }
log_error() { log "ERROR" "$@"; }
log_success() { log "SUCCESS" "$@"; }

# Afficher l'aide
show_help() {
    cat << EOF
Usage: $0 [OPTIONS]

Script de backup avec compression et rotation automatique.

OPTIONS:
    -s, --source DIR        Dossier source à sauvegarder (défaut: $SOURCE_DIR)
    -d, --dest DIR          Dossier de destination des backups (défaut: $BACKUP_DIR)
    -e, --exclude PATTERN   Fichiers/dossiers à exclure (peut être répété)
    -r, --retention DAYS    Nombre de jours de rétention (défaut: $RETENTION_DAYS)
    -n, --name NAME         Nom du backup (défaut: $BACKUP_NAME)
    -c, --config FILE       Fichier de configuration alternatif
    -h, --help              Afficher cette aide
    -v, --version           Afficher la version

EXEMPLES:
    $0
    $0 -s /opt/immich -d /backups -r 14
    $0 --exclude "*.log" --exclude "cache/*"
    $0 -c /etc/mybackup.ini

EOF
    exit 0
}

# Afficher la version
show_version() {
    echo "$SCRIPT_NAME version $VERSION"
    exit 0
}

# Vérifier les droits sudo
check_sudo() {
    if [[ $EUID -ne 0 ]]; then
        log_warn "Ce script nécessite les droits root. Relancement avec sudo..."
        exec sudo "$0" "$@"
        exit $?
    fi
}

# Charger la configuration INI
load_config() {
    local config_file="$1"
    
    if [[ ! -f "$config_file" ]]; then
        log_warn "Fichier de configuration non trouvé: $config_file"
        log_info "Utilisation des valeurs par défaut"
        return 1
    fi
    
    log_info "Chargement de la configuration: $config_file"
    
    while IFS='=' read -r key value || [[ -n "$key" ]]; do
        # Ignorer les commentaires et lignes vides
        [[ "$key" =~ ^[[:space:]]*# ]] && continue
        [[ -z "$key" ]] && continue
        
        # Nettoyer les espaces
        key=$(echo "$key" | xargs)
        value=$(echo "$value" | xargs | sed 's/^["'\'']//;s/["'\'']$//')
        
        case "$key" in
            source_dir)     SOURCE_DIR="$value" ;;
            backup_dir)     BACKUP_DIR="$value" ;;
            excludes)       EXCLUDES="$value" ;;
            retention_days) RETENTION_DAYS="$value" ;;
            backup_name)    BACKUP_NAME="$value" ;;
        esac
    done < "$config_file"
    
    return 0
}

# Parser les arguments en ligne de commande
parse_args() {
    local excludes_array=()
    
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -s|--source)
                SOURCE_DIR="$2"
                shift 2
                ;;
            -d|--dest)
                BACKUP_DIR="$2"
                shift 2
                ;;
            -e|--exclude)
                excludes_array+=("$2")
                shift 2
                ;;
            -r|--retention)
                RETENTION_DAYS="$2"
                shift 2
                ;;
            -n|--name)
                BACKUP_NAME="$2"
                shift 2
                ;;
            -c|--config)
                CONFIG_FILE="$2"
                shift 2
                ;;
            -h|--help)
                show_help
                ;;
            -v|--version)
                show_version
                ;;
            *)
                log_error "Option inconnue: $1"
                show_help
                ;;
        esac
    done
    
    # Convertir le tableau d'exclusions en chaîne
    if [[ ${#excludes_array[@]} -gt 0 ]]; then
        EXCLUDES=$(IFS=','; echo "${excludes_array[*]}")
    fi
}

# Valider la configuration
validate_config() {
    local errors=0
    
    # Vérifier le dossier source
    if [[ ! -d "$SOURCE_DIR" ]]; then
        log_error "Le dossier source n'existe pas: $SOURCE_DIR"
        ((errors++))
    fi
    
    # Vérifier/créer le dossier de destination
    if [[ ! -d "$BACKUP_DIR" ]]; then
        log_info "Création du dossier de destination: $BACKUP_DIR"
        if ! mkdir -p "$BACKUP_DIR"; then
            log_error "Impossible de créer le dossier de destination: $BACKUP_DIR"
            ((errors++))
        fi
    fi
    
    # Vérifier la rétention
    if ! [[ "$RETENTION_DAYS" =~ ^[0-9]+$ ]] || [[ "$RETENTION_DAYS" -lt 1 ]]; then
        log_error "La rétention doit être un nombre positif: $RETENTION_DAYS"
        ((errors++))
    fi
    
    # Vérifier les outils nécessaires
    for cmd in zip find; do
        if ! command -v "$cmd" &> /dev/null; then
            log_error "Commande requise non trouvée: $cmd"
            ((errors++))
        fi
    done
    
    return $errors
}

# Afficher la configuration
show_config() {
    log_info "=== Configuration ==="
    log_info "Source:        $SOURCE_DIR"
    log_info "Destination:   $BACKUP_DIR"
    log_info "Exclusions:    ${EXCLUDES:-aucune}"
    log_info "Rétention:     $RETENTION_DAYS jours"
    log_info "Nom backup:    $BACKUP_NAME"
    log_info "===================="
}

#===============================================================================
# FONCTIONS DE BACKUP
#===============================================================================

# Construire les options d'exclusion pour zip
build_exclude_options() {
    local exclude_opts=""
    
    if [[ -n "$EXCLUDES" ]]; then
        IFS=',' read -ra EXCLUDE_ARRAY <<< "$EXCLUDES"
        for pattern in "${EXCLUDE_ARRAY[@]}"; do
            pattern=$(echo "$pattern" | xargs)  # Trim
            exclude_opts="$exclude_opts -x '$pattern'"
        done
    fi
    
    echo "$exclude_opts"
}

# Effectuer le backup
do_backup() {
    local timestamp=$(date '+%Y%m%d_%H%M%S')
    local backup_file="${BACKUP_DIR}/${BACKUP_NAME}_${timestamp}.zip"
    local exclude_opts
    
    log_info "Démarrage du backup..."
    log_info "Fichier de sortie: $backup_file"
    
    # Construire les options d'exclusion
    exclude_opts=$(build_exclude_options)
    
    # Construire la commande zip
    local zip_cmd="zip -r '$backup_file' '$SOURCE_DIR'"
    
    # Ajouter les exclusions
    if [[ -n "$EXCLUDES" ]]; then
        IFS=',' read -ra EXCLUDE_ARRAY <<< "$EXCLUDES"
        for pattern in "${EXCLUDE_ARRAY[@]}"; do
            pattern=$(echo "$pattern" | xargs)
            zip_cmd="$zip_cmd -x '${SOURCE_DIR%/}/$pattern' -x '$pattern'"
        done
    fi
    
    log_info "Exécution: $zip_cmd"
    
    # Exécuter le backup
    local start_time=$(date +%s)
    
    if eval "$zip_cmd" >> "$LOG_FILE" 2>&1; then
        local end_time=$(date +%s)
        local duration=$((end_time - start_time))
        local size=$(du -h "$backup_file" | cut -f1)
        
        log_success "Backup terminé avec succès!"
        log_info "Taille: $size"
        log_info "Durée: ${duration}s"
        
        return 0
    else
        log_error "Échec du backup!"
        return 1
    fi
}

# Rotation des anciens backups
rotate_backups() {
    log_info "Rotation des backups (rétention: $RETENTION_DAYS jours)..."
    
    local count=0
    while IFS= read -r -d '' file; do
        log_info "Suppression: $(basename "$file")"
        rm -f "$file"
        ((count++))
    done < <(find "$BACKUP_DIR" -name "${BACKUP_NAME}_*.zip" -type f -mtime +"$RETENTION_DAYS" -print0 2>/dev/null)
    
    if [[ $count -gt 0 ]]; then
        log_info "$count ancien(s) backup(s) supprimé(s)"
    else
        log_info "Aucun ancien backup à supprimer"
    fi
}

# Lister les backups existants
list_backups() {
    log_info "Backups existants dans $BACKUP_DIR:"
    
    local found=0
    while IFS= read -r file; do
        if [[ -n "$file" ]]; then
            local size=$(du -h "$file" | cut -f1)
            local date=$(stat -c %y "$file" 2>/dev/null | cut -d' ' -f1)
            log_info "  - $(basename "$file") [$size] ($date)"
            ((found++))
        fi
    done < <(find "$BACKUP_DIR" -name "${BACKUP_NAME}_*.zip" -type f 2>/dev/null | sort)
    
    if [[ $found -eq 0 ]]; then
        log_info "  Aucun backup trouvé"
    fi
}

#===============================================================================
# FONCTION PRINCIPALE
#===============================================================================

main() {
    local original_args=("$@")
    
    # Initialiser le fichier de log
    echo "" >> "$LOG_FILE"
    log_info "=========================================="
    log_info "Démarrage de $SCRIPT_NAME v$VERSION"
    log_info "=========================================="
    
    # Vérifier les droits sudo
    check_sudo "${original_args[@]}"
    
    # Charger la configuration INI (si existe)
    load_config "$CONFIG_FILE" || true
    
    # Parser les arguments (écrase la config INI)
    parse_args "$@"
    
    # Recharger la config si spécifiée en argument
    if [[ "$CONFIG_FILE" != "${SCRIPT_DIR}/${SCRIPT_NAME}.ini" ]]; then
        load_config "$CONFIG_FILE" || true
        parse_args "$@"  # Re-parser pour que les args aient priorité
    fi
    
    # Afficher la configuration
    show_config
    
    # Valider la configuration
    if ! validate_config; then
        log_error "Configuration invalide. Abandon."
        exit 1
    fi
    
    # Effectuer le backup
    if ! do_backup; then
        log_error "Le backup a échoué!"
        exit 2
    fi
    
    # Rotation des anciens backups
    rotate_backups
    
    # Lister les backups
    list_backups
    
    log_success "Script terminé avec succès"
    log_info "=========================================="
    
    exit 0
}

# Gestion des erreurs
trap 'log_error "Erreur ligne $LINENO. Code de sortie: $?"; exit 1' ERR

# Point d'entrée
main "$@"
