<?php
/**
 * Script de backup général (à utiliser en cron sur cPanel / Linux).
 * - Récupère les compagnies actives depuis la base 'assurance_gestion' (table compagnies_table)
 * - Pour chaque compagnie active : dump SQL + fichiers data/<compagnie_ref> dans un ZIP
 * - Transfère chaque ZIP vers un serveur FTP distant
 *
 * Cron exemple (tous les jours à 02h10) :
 * 10 2 * * * /usr/bin/php -d display_errors=0 /home/USER/public_html/hiba/backup_cron_general.php >> /home/USER/logs/backup_general.log 2>&1
 *
 * IMPORTANT : Pour la sécurité, idéalement remplacer les identifiants en clair par des variables d'environnement.
 */

@set_time_limit(0);
@ini_set('memory_limit','1024M');
@ini_set('default_charset','UTF-8');
 date_default_timezone_set('Africa/Casablanca');

// === CONFIG BASE GESTION (adapter si nécessaire / ou utiliser getenv) ===
$GESTION_DB_HOST = 'localhost';
$GESTION_DB_NAME = 'assurance_gestion';
$GESTION_DB_USER = 'root'; // A adapter pour cPanel
$GESTION_DB_PASS = '';

// === CONFIG FTP DISTANT (UTILISER DES VARIABLES D'ENVIRONNEMENT DE PRÉFÉRENCE) ===
$FTP_HOST = '46.105.59.3';
$FTP_USER = 'backup@association-tdc.org';
$FTP_PASS = 'Pass@1234@1234';
$FTP_PORT = 21;
$FTP_TIMEOUT = 30;
$FTP_REMOTE_BASE = 'backups_compagnies'; // dossier distant principal

// === CONFIG GOOGLE DRIVE (Upload optionnel) ===
$GDRIVE_ENABLE = false; // Google Drive désactivé (demande utilisateur)
$GDRIVE_CREDENTIALS_FILE = __DIR__ . '/api/credentials.json'; // service account JSON
$GDRIVE_FOLDER_ID = ''; // on va chercher / créer par nom
$GDRIVE_FOLDER_NAME = 'backup'; // nouveau dossier cible à tester
$GDRIVE_SCOPE = 'https://www.googleapis.com/auth/drive'; // portée étendue (évite 403 si parents dossier existant)
$GDRIVE_DEBUG = true; // logs détaillés
$GDRIVE_SHARED_DRIVE_ID = ''; // Optionnel: ID d'un Shared Drive (Drive partagé). Laisser vide si dossier dans "Mon Drive" de l'utilisateur propriétaire.
$GDRIVE_IMPERSONATE_EMAIL = ''; // Optionnel: email d'un utilisateur Workspace à impersoner (nécessite délégation globale et configuration Admin). Laisser vide sinon.


// === CONFIG LOCALE ===
$LOCAL_BASE_DIR = __DIR__ . '/backup_general';
$RETENTION_DAYS = 14; // supprimer dossiers locaux plus vieux que X jours

// === LOGGING SIMPLE SUR STDOUT ===
// Ajout d'un log fichier dans /gestion_compagnies
$__BACKUP_LOG_DIR = __DIR__ . '/gestion_compagnies';
if (!is_dir($__BACKUP_LOG_DIR)) { @mkdir($__BACKUP_LOG_DIR, 0755, true); }
$__BACKUP_LOG_FILE = $__BACKUP_LOG_DIR . '/backup_general.log';

function logLine($msg){
    global $__BACKUP_LOG_FILE;
    $line = '['.date('Y-m-d H:i:s').'] '.$msg.PHP_EOL;
    echo $line; // sortie console / cron
    // append dans le fichier (suppression si > ~5MB pour éviter croissance infinie)
    if (is_file($__BACKUP_LOG_FILE) && filesize($__BACKUP_LOG_FILE) > 5*1024*1024) {
        @rename($__BACKUP_LOG_FILE, $__BACKUP_LOG_FILE.'.'.date('Ymd_His').'.old');
    }
    @file_put_contents($__BACKUP_LOG_FILE, $line, FILE_APPEND | LOCK_EX);
}

// Diagnostic basique FTP: résolution DNS et test TCP
function ftpConnectivityDebug($host, $port, $timeout){
    $ip = @gethostbyname($host);
    if ($ip === $host) {
        logLine("FTP debug: résolution DNS peut-être échouée ($host)");
    } else {
        logLine("FTP debug: $host -> $ip");
    }
    $start = microtime(true);
    $errno = 0; $errstr='';
    $sock = @fsockopen($ip, $port, $errno, $errstr, $timeout);
    if (!$sock){
        logLine("FTP debug: échec TCP ($errno: $errstr)");
        return false;
    }
    $dur = round((microtime(true)-$start)*1000,1);
    logLine("FTP debug: TCP ok en {$dur} ms");
    @fclose($sock);
    return true;
}

// Connexion gestion
try {
    $gestionDsn = "mysql:host=$GESTION_DB_HOST;dbname=$GESTION_DB_NAME;charset=utf8";
    $gestionPdo = new PDO($gestionDsn, $GESTION_DB_USER, $GESTION_DB_PASS, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
    ]);
} catch(Exception $e){
    logLine('ERREUR: Connexion base gestion impossible : '.$e->getMessage());
    if (defined('BACKUP_EMBED')) { return; } else { exit(1); }
}

// Récupérer compagnies actives
try {
    $companies = $gestionPdo->query("SELECT compagnie_ref, compagnie_db, compagnie_db_user, compagnie_db_pass FROM compagnies_table WHERE active = 1")->fetchAll();
    if (!$companies){
        logLine('Aucune compagnie active trouvée. Fin.');
        if (defined('BACKUP_EMBED')) { return; } else { exit(0); }
    }
} catch(Exception $e){
    logLine('ERREUR: Lecture compagnies : '.$e->getMessage());
    if (defined('BACKUP_EMBED')) { return; } else { exit(1); }
}

// Préparer dossier local du jour
$dayFolder = $LOCAL_BASE_DIR . '/' . date('Y-m-d');
if (!is_dir($dayFolder)) {
    if (!@mkdir($dayFolder, 0755, true)) {
        logLine('ERREUR: Impossible de créer le dossier jour '.$dayFolder);
        exit(1);
    }
}

// Fonction de dump + zip
function createCompanyZip($ref, $dbName, $dbUser, $dbPass, $destDir) {
    $timestamp = date('Y-m-d_H-i-s');
    $zipPath = $destDir . '/' . $ref . '_backup_' . $timestamp . '.zip';
    $tmpDir = sys_get_temp_dir();
    $tmpSql = $tmpDir . '/sql_' . $ref . '_' . uniqid() . '.sql';

    try {
        $dsn = "mysql:host=localhost;dbname=$dbName;charset=utf8"; // host fixe
        $pdo = new PDO($dsn, $dbUser, $dbPass, [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
        ]);
    } catch(Exception $e) {
        throw new Exception('Connexion DB compagnie échouée: '.$e->getMessage());
    }

    $sql = "-- Backup Compagnie $ref\n-- Base: $dbName\n-- Date: ".date('Y-m-d H:i:s')."\n\n";
    try {
        $tables = $pdo->query('SHOW TABLES')->fetchAll(PDO::FETCH_COLUMN);
        foreach ($tables as $table) {
            $row = $pdo->query("SHOW CREATE TABLE `$table`")->fetch();
            if (!isset($row['Create Table'])) continue;
            $sql .= "DROP TABLE IF EXISTS `$table`;\n".$row['Create Table'].";\n\n";
            $rows = $pdo->query("SELECT * FROM `$table`")->fetchAll();
            if ($rows) {
                $sql .= "INSERT INTO `$table` VALUES\n";
                $vals = [];
                foreach ($rows as $r) {
                    $escaped = [];
                    foreach ($r as $v) {
                        $escaped[] = ($v === null) ? 'NULL' : $pdo->quote($v);
                    }
                    $vals[] = '(' . implode(', ', $escaped) . ')';
                }
                $sql .= implode(",\n", $vals) . ";\n\n";
            }
        }
    } catch(Exception $e) {
        throw new Exception('Dump SQL échoué: '.$e->getMessage());
    }

    file_put_contents($tmpSql, $sql);

    $zip = new ZipArchive();
    if ($zip->open($zipPath, ZipArchive::CREATE) !== true) {
        @unlink($tmpSql);
        throw new Exception('Création ZIP impossible');
    }
    $zip->addFile($tmpSql, 'database.sql');

    $dataDir = __DIR__ . '/data/' . $ref;
    $fileCount = 0;
    if (is_dir($dataDir)) {
        $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dataDir, FilesystemIterator::SKIP_DOTS));
        foreach ($it as $file) {
            if ($file->isFile()) {
                $real = $file->getPathname();
                $rel = 'files/' . substr($real, strlen($dataDir) + 1);
                $zip->addFile($real, $rel);
                $fileCount++;
            }
        }
    }
    $zip->close();
    @unlink($tmpSql);
    return [$zipPath, $fileCount];
}

// === Google Drive Helper Functions ===
function gdriveLoadCredentials($file){
    if(!is_file($file)) throw new Exception('Credentials JSON introuvable');
    $json = json_decode(file_get_contents($file), true);
    if(!$json || empty($json['private_key']) || empty($json['client_email'])) throw new Exception('Credentials JSON invalide');
    return $json;
}

function gdriveGetAccessToken($creds, $scope){
    // Permet d'ajouter un claim 'sub' si impersonation souhaitée (domaine Workspace + délégation activée)
    global $GDRIVE_IMPERSONATE_EMAIL; 
    $header = rtrim(strtr(base64_encode(json_encode(['alg'=>'RS256','typ'=>'JWT'])), '+/', '-_'), '=');
    $now = time();
    $claimSet = [
        'iss' => $creds['client_email'],
        'scope' => $scope,
        'aud' => 'https://oauth2.googleapis.com/token',
        'exp' => $now + 3600,
        'iat' => $now
    ];
    if (!empty($GDRIVE_IMPERSONATE_EMAIL)) {
        $claimSet['sub'] = $GDRIVE_IMPERSONATE_EMAIL; // impersonation
    }
    $payload = rtrim(strtr(base64_encode(json_encode($claimSet)), '+/', '-_'), '=');
    $data = $header.'.'.$payload;
    $key = openssl_pkey_get_private($creds['private_key']);
    if(!$key) throw new Exception('Clé privée invalide');
    $signature = '';
    openssl_sign($data, $signature, $key, 'sha256');
    $jwt = $data.'.'.rtrim(strtr(base64_encode($signature), '+/', '-_'), '=');
    $resp = gdriveHttpRequest('POST','https://oauth2.googleapis.com/token',['Content-Type: application/x-www-form-urlencoded'], http_build_query(['grant_type'=>'urn:ietf:params:oauth:grant-type:jwt-bearer','assertion'=>$jwt]),30,false);
    if($resp['error']) throw new Exception('Echec token: '.$resp['error']);
    $tok = json_decode($resp['body'],true);
    if(empty($tok['access_token'])) throw new Exception('Token invalide');
    return $tok['access_token'];
}

function gdriveUploadFile($accessToken, $filePath, $folderId=''){
    $size = filesize($filePath);
    $name = basename($filePath);
    // Petits fichiers (<4MB) -> multipart simple, sinon -> résumable
    if ($size <= 4*1024*1024) {
        $metadata = ['name'=>$name];
        if($folderId) $metadata['parents'] = [$folderId];
        $boundary = '====GDRIVE_'.md5(uniqid('',true));
        $metaPart = "--$boundary\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n".json_encode($metadata)."\r\n";
        $fileData = file_get_contents($filePath);
        $filePart = "--$boundary\r\nContent-Type: application/zip\r\nContent-Transfer-Encoding: binary\r\n\r\n".$fileData."\r\n";
        $end = "--$boundary--";
        $body = $metaPart.$filePart.$end;
        // Ajout supportsAllDrives pour autoriser les Shared Drives / dossiers partagés
        $resp = gdriveHttpRequest('POST','https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true&fields=id',[
            'Authorization: Bearer '.$accessToken,
            'Content-Type: multipart/related; boundary='.$boundary
        ], $body,180,true);
    if($resp['error']) throw new Exception('Upload HTTP multipart échoué (code='.$resp['code'].'): '.substr($resp['body'],0,400));
        $data = json_decode($resp['body'],true);
        if(empty($data['id'])) throw new Exception('Réponse upload multipart invalide: '.substr($resp['body'],0,200));
        return $data['id'];
    }
    // Upload résumable
    $meta = ['name'=>$name]; if($folderId) $meta['parents']=[$folderId];
    $init = gdriveHttpRequest('POST','https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&supportsAllDrives=true&fields=id',[
        'Authorization: Bearer '.$accessToken,
        'Content-Type: application/json; charset=UTF-8',
        'X-Upload-Content-Type: application/zip',
        'X-Upload-Content-Length: '.$size
    ], json_encode($meta),60,false);
    if($init['error']) throw new Exception('Init résumable échoué: '.$init['error']);
    $location='';
    foreach($init['headers'] as $h){ if(stripos($h,'Location:')===0){ $location=trim(substr($h,9)); break; } }
    if(!$location){ throw new Exception('Init résumable sans Location header (code='.$init['code'].')'); }
    // Envoi fichier complet (un seul chunk) – pour très gros, on pourrait découper.
    $fileData = file_get_contents($filePath);
    $upload = gdriveHttpRequest('PUT',$location,[
        'Authorization: Bearer '.$accessToken,
        'Content-Type: application/zip'
    ], $fileData,600,true);
    if($upload['error']) throw new Exception('Upload résumable échoué (code='.$upload['code'].'): '.substr($upload['body'],0,400));
    $data = json_decode($upload['body'],true);
    if(empty($data['id'])) throw new Exception('Upload résumable réponse invalide (code='.$upload['code'].', body='.substr($upload['body'],0,200).')');
    return $data['id'];
}

// Requêtes génériques Google Drive (retour JSON décodé ou exception)
function gdriveApiRequest($accessToken, $method, $url, $body=null, $contentType='application/json'){
    $headers = [ 'Authorization: Bearer '.$accessToken ];
    if ($contentType && $body!==null) $headers[] = 'Content-Type: '.$contentType;
    $r = gdriveHttpRequest($method,$url,$headers,$body,30,false);
    if($r['error']) throw new Exception('HTTP '.$method.' échec URL='.$url.' : '.$r['error']);
    $json = json_decode($r['body'],true);
    return $json!==null ? $json : $r['body'];
}

function gdriveEnsureFolder($accessToken, $folderName, &$folderIdRef){
    if ($folderIdRef) return; // déjà fourni
    if (!$folderName) return; // rien à faire
    global $GDRIVE_SHARED_DRIVE_ID;
    // Construire requête recherche selon présence shared drive
    $parentFilter = '';
    if (!empty($GDRIVE_SHARED_DRIVE_ID)) {
        $parentFilter = sprintf(" and '%s' in parents", addslashes($GDRIVE_SHARED_DRIVE_ID));
    }
    $q = sprintf("name='%s' and mimeType='application/vnd.google-apps.folder' and trashed=false%s", addslashes($folderName), $parentFilter);
    $url = 'https://www.googleapis.com/drive/v3/files?pageSize=10&supportsAllDrives=true&includeItemsFromAllDrives=true&fields=files(id,name,driveId,parents)&q='.rawurlencode($q);
    $res = gdriveApiRequest($accessToken,'GET',$url,null,null);
    if (!empty($res['files'][0]['id'])) {
        $folderIdRef = $res['files'][0]['id'];
        logLine('GDRIVE: dossier "'.$folderName.'" trouvé (id='.$folderIdRef.')');
        return;
    }
    // Créer dossier
    $meta = [ 'name'=>$folderName, 'mimeType'=>'application/vnd.google-apps.folder' ];
    if (!empty($GDRIVE_SHARED_DRIVE_ID)) {
        $meta['parents'] = [$GDRIVE_SHARED_DRIVE_ID];
    }
    $createUrl = 'https://www.googleapis.com/drive/v3/files?supportsAllDrives=true&fields=id,name,driveId,parents';
    $create = gdriveApiRequest($accessToken,'POST',$createUrl, json_encode($meta),'application/json');
    if (empty($create['id'])) throw new Exception('Création dossier Google Drive échouée');
    $folderIdRef = $create['id'];
    logLine('GDRIVE: dossier "'.$folderName.'" créé (id='.$folderIdRef.')');
}

// Diagnostic supplémentaire sur le dossier cible (permissions / driveId)
function gdriveDebugFolder($accessToken, $folderId){
    if (!$folderId) return;
    try {
    $metaUrl = 'https://www.googleapis.com/drive/v3/files/'.urlencode($folderId).'?fields=id,name,mimeType,owners(emailAddress,displayName),driveId,parents,permissionIds,quotaBytesUsed&supportsAllDrives=true';
        $meta = gdriveApiRequest($accessToken,'GET',$metaUrl,null,null);
    logLine('GDRIVE DEBUG dossier: name='.( $meta['name'] ?? '??').' driveId='.( $meta['driveId'] ?? 'none').' owners='.(isset($meta['owners'][0]['emailAddress'])?$meta['owners'][0]['emailAddress']:'?').' quotaBytesUsed='.( $meta['quotaBytesUsed'] ?? 'n/a'));
    } catch(Exception $e){
        logLine('GDRIVE DEBUG dossier: impossible de lire metadata ('.$e->getMessage().')');
    }
}

// HTTP helper avec fallback cURL / stream
function gdriveHttpRequest($method,$url,$headers=[],$body=null,$timeout=60,$binary=false){
    $result = ['code'=>0,'headers'=>[],'body'=>'','error'=>null];
    $useCurl = function_exists('curl_init');
    if ($useCurl) {
        $ch = curl_init($url);
        $hdrAssoc = [];
        curl_setopt($ch,CURLOPT_CUSTOMREQUEST,$method);
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
        curl_setopt($ch,CURLOPT_TIMEOUT,$timeout);
        curl_setopt($ch,CURLOPT_FOLLOWLOCATION,true);
        curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
        curl_setopt($ch,CURLOPT_HEADER,true);
        if($body!==null) curl_setopt($ch,CURLOPT_POSTFIELDS,$body);
        // SSL options (laisser vérification; fallback si erreur)
        $resp = curl_exec($ch);
        if($resp===false){
            // tentative désactivation vérification en dernier recours
            curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
            curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
            $resp = curl_exec($ch);
        }
        if($resp===false){
            $result['error'] = 'cURL: '.curl_error($ch);
            curl_close($ch);
            return $result;
        }
        $code = curl_getinfo($ch,CURLINFO_HTTP_CODE);
        $hs = curl_getinfo($ch,CURLINFO_HEADER_SIZE);
        $rawHeaders = substr($resp,0,$hs);
        $bodyPart = substr($resp,$hs);
        $result['code']=$code; $result['body']=$bodyPart;
        $lines = preg_split('/\r?\n/',$rawHeaders,-1,PREG_SPLIT_NO_EMPTY);
        $result['headers']=$lines;
        if ($code>=400){ $result['error']='HTTP '.$code; }
        curl_close($ch);
        return $result;
    }
    // stream fallback
    $hstr = implode("\r\n", $headers);
    $opts = ['http'=>['method'=>$method,'header'=>$hstr,'timeout'=>$timeout,'ignore_errors'=>true]];
    if($body!==null) $opts['http']['content']=$body;
    $ctx = stream_context_create($opts);
    $resp = @file_get_contents($url,false,$ctx);
    global $http_response_header; $result['headers']= $http_response_header ?? [];
    if($resp===false){ $result['error']='stream wrapper failure'; return $result; }
    $result['body']=$resp;
    foreach($result['headers'] as $h){ if(preg_match('/^HTTP\/\S+\s+(\d+)/',$h,$m)){ $result['code']=(int)$m[1]; break; } }
    if($result['code']>=400) $result['error']='HTTP '.$result['code'];
    return $result;
}

// Connexion FTP (optionnelle) - ignorer si extension ftp absente
$ftpConn = false;
$ftpExtensionOk = (function_exists('ftp_connect') || function_exists('ftp_ssl_connect'));
if (!$ftpExtensionOk) {
    logLine('Extension FTP non disponible: transferts distants ignorés.');
} else {
    $canReach = ftpConnectivityDebug($FTP_HOST, $FTP_PORT, 6); // test rapide (6s max)
    if (!$canReach) {
        logLine('FTP debug: saut des tentatives FTP (port injoignable)');
    } else {
        if (function_exists('ftp_ssl_connect')) {
            $ftpConn = @ftp_ssl_connect($FTP_HOST, $FTP_PORT, 15);
            if ($ftpConn) {
                logLine('FTP debug: connexion SSL établie');
            } else {
                logLine('FTP debug: ssl échec, essai non-SSL');
            }
        }
        if (!$ftpConn && function_exists('ftp_connect')) {
            $ftpConn = @ftp_connect($FTP_HOST, $FTP_PORT, 15);
            if ($ftpConn) logLine('FTP debug: connexion standard établie');
        }
        if (!$ftpConn) {
            logLine('ERREUR: Connexion FTP impossible');
        } else {
            if (!@ftp_login($ftpConn, $FTP_USER, $FTP_PASS)) {
                logLine('ERREUR: Authentification FTP échouée');
                @ftp_close($ftpConn);
                $ftpConn = false;
            } else {
                ftp_pasv($ftpConn, true);
                logLine('FTP debug: mode passif activé');
                $remoteToday = $FTP_REMOTE_BASE . '/' . date('Y-m-d');
                $parts = explode('/', $remoteToday);
                $pathBuild = '';
                foreach ($parts as $p) {
                    if ($p==='') continue;
                    $pathBuild .= ($pathBuild?'/' : '') . $p;
                    @ftp_mkdir($ftpConn, $pathBuild);
                }
                logLine('FTP debug: répertoires distants préparés');
            }
        }
    }
}

logLine('--- DEBUT BACKUP GENERAL ---');
logLine('Compagnies actives: '.count($companies));
$success = 0; $errors = 0; $ftpUploaded = 0;
$gdriveUploaded = 0; $gdriveErrors = 0; $gdriveToken = null; $gdriveReady = false;

if ($GDRIVE_ENABLE) {
    try {
        $creds = gdriveLoadCredentials($GDRIVE_CREDENTIALS_FILE);
        $gdriveToken = gdriveGetAccessToken($creds, $GDRIVE_SCOPE);
        $gdriveReady = true;
        logLine('GDRIVE: token obtenu (service account='.$creds['client_email'].')');
        if (!empty($GDRIVE_FOLDER_ID)) {
            logLine('GDRIVE: utilisation du dossier existant ID='.$GDRIVE_FOLDER_ID.' (pas de création par nom)');
            gdriveDebugFolder($gdriveToken, $GDRIVE_FOLDER_ID);
        } elseif (!empty($GDRIVE_FOLDER_NAME)) {
            // S'assurer du dossier cible si ID manquant
            try { gdriveEnsureFolder($gdriveToken, $GDRIVE_FOLDER_NAME, $GDRIVE_FOLDER_ID); }
            catch(Exception $fe){ logLine('GDRIVE: dossier NON résolu ('.$fe->getMessage().')'); }
            if(!empty($GDRIVE_FOLDER_ID)) gdriveDebugFolder($gdriveToken, $GDRIVE_FOLDER_ID);
        }
    } catch(Exception $e){
        logLine('GDRIVE: désactivé ('.$e->getMessage().')');
        $gdriveReady = false;
    }
}

foreach ($companies as $c) {
    $ref = $c['compagnie_ref'];
    logLine("[{$ref}] Traitement en cours...");
    try {
        [$zipPath, $fileCount] = createCompanyZip($ref, $c['compagnie_db'], $c['compagnie_db_user'], $c['compagnie_db_pass'], $dayFolder);
        $size = filesize($zipPath);
        logLine("[{$ref}] ZIP créé (".basename($zipPath).", ".number_format($size/1024,1).' KB, fichiers='.$fileCount.")");
        $success++;
        if ($ftpConn) {
            $remoteFile = $FTP_REMOTE_BASE . '/' . date('Y-m-d') . '/' . basename($zipPath);
            if (@ftp_put($ftpConn, $remoteFile, $zipPath, FTP_BINARY)) {
                logLine("[{$ref}] Transfert FTP OK -> $remoteFile");
                $ftpUploaded++;
            } else {
                logLine("[{$ref}] ERREUR transfert FTP");
            }
        }
        if ($gdriveReady) {
            try {
                $fileId = gdriveUploadFile($gdriveToken, $zipPath, $GDRIVE_FOLDER_ID);
                logLine("[{$ref}] Upload Google Drive OK (fileId=$fileId)");
                $gdriveUploaded++;
            } catch(Exception $ge){
                logLine("[{$ref}] ERREUR Google Drive: ".$ge->getMessage());
                $gdriveErrors++;
            }
        }
    } catch(Exception $e) {
        logLine("[{$ref}] ERREUR: ".$e->getMessage());
        $errors++;
        continue;
    }
}

// Limiter à 5 derniers backups par compagnie (local)
try {
    $byCompany = [];
    if (is_dir($LOCAL_BASE_DIR)) {
        foreach (scandir($LOCAL_BASE_DIR) as $d) {
            if ($d==='.'||$d==='..') continue; $dayDir = $LOCAL_BASE_DIR.'/'.$d; if(!is_dir($dayDir)) continue;
            foreach (glob($dayDir.'/*.zip') as $zip) {
                $bn = basename($zip);
                if (preg_match('/^([A-Za-z0-9_-]+)_backup_/',$bn,$m)) {
                    $ref = $m[1];
                    $byCompany[$ref][] = $zip;
                }
            }
        }
        foreach ($byCompany as $ref=>$list) {
            usort($list, function($a,$b){ return filemtime($b) <=> filemtime($a); });
            $toDelete = array_slice($list, 5); // garder 5 premiers
            foreach ($toDelete as $old) {
                if (@unlink($old)) {
                    logLine("Retention(5): suppression ancienne archive $old");
                }
            }
        }
    }
} catch(Exception $e) { logLine('Warning retention(5) locale: '.$e->getMessage()); }

// (Optionnel) Retention FTP 5 derniers par compagnie (meilleur-effort)
if ($ftpConn) {
    try {
        // On liste chaque dossier date et recense les fichiers par compagnie
        $remoteDays = @ftp_nlist($ftpConn, $FTP_REMOTE_BASE);
        $remoteByCompany = [];
        if ($remoteDays) {
            foreach ($remoteDays as $dayPath) {
                $dayBase = basename($dayPath);
                if (!preg_match('/^\d{4}-\d{2}-\d{2}$/',$dayBase)) continue;
                $files = @ftp_nlist($ftpConn, $FTP_REMOTE_BASE.'/'.$dayBase);
                if (!$files) continue;
                foreach ($files as $f) {
                    if (substr($f,-4) !== '.zip') continue;
                    $bn = basename($f);
                    if (preg_match('/^([A-Za-z0-9_-]+)_backup_/',$bn,$m)) {
                        $ref = $m[1];
                        // ftp_mdtm peut échouer, on fallback sur ordre lexical (timestamp dans nom)
                        $mtime = @ftp_mdtm($ftpConn, $f);
                        if ($mtime < 0) {
                            // extraire timestamp du nom _backup_YYYY-MM-DD_HH-mm-ss
                            if (preg_match('/_backup_(\d{4}-\d{2}-\d{2})_(\d{2}-\d{2}-\d{2})/',$bn,$mm)) {
                                $mtime = strtotime(str_replace('-','',$mm[1]).' '.str_replace('-',':',$mm[2]));
                            } else {
                                $mtime = 0;
                            }
                        }
                        $remoteByCompany[$ref][] = ['path'=>$f,'mtime'=>$mtime,'day'=>$dayBase];
                    }
                }
            }
        }
        foreach ($remoteByCompany as $ref=>$entries) {
            usort($entries, function($a,$b){ return $b['mtime'] <=> $a['mtime']; });
            $extra = array_slice($entries, 5);
            foreach ($extra as $e) {
                if (@ftp_delete($ftpConn, $e['path'])) {
                    logLine("Retention FTP(5): suppression ".$e['path']);
                }
            }
        }
    } catch(Exception $e) { logLine('Warning retention(5) FTP: '.$e->getMessage()); }
}

// Retention locale par ancienneté de dossier (complément aux 5 derniers par compagnie)
try {
    $dirs = @scandir($LOCAL_BASE_DIR);
    if ($dirs) {
        foreach ($dirs as $d) {
            if (in_array($d, ['.','..'])) continue;
            $full = $LOCAL_BASE_DIR . '/' . $d;
            if (is_dir($full)) {
                $ageDays = (time() - filemtime($full)) / 86400;
                if ($ageDays > $RETENTION_DAYS) {
                    $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($full, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
                    foreach ($it as $f) {
                        $f->isDir() ? @rmdir($f->getPathname()) : @unlink($f->getPathname());
                    }
                    @rmdir($full);
                    logLine('Retention: dossier supprimé '.$full);
                }
            }
        }
    }
} catch(Exception $e) {
    logLine('Warning retention: '.$e->getMessage());
}

if ($ftpConn) @ftp_close($ftpConn);
if ($GDRIVE_ENABLE) {
    logLine("--- FIN BACKUP GENERAL --- Succès=$success, Erreurs=$errors, FTP Uploads=$ftpUploaded, Drive Uploads=$gdriveUploaded, Drive Err=$gdriveErrors");
} else {
    logLine("--- FIN BACKUP GENERAL --- Succès=$success, Erreurs=$errors, FTP Uploads=$ftpUploaded (Drive désactivé)");
}
if (defined('BACKUP_EMBED')) { return; } else { exit($errors>0 ? 2 : 0); }
