<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;

/**
 * Commande de backup automatique de la base de données
 * 
 * Exécutée quotidiennement à 02:00 via le scheduler.
 * 
 * Fonctionnalités:
 * - Export SQL complet de la base de données
 * - Compression GZIP pour économiser l'espace
 * - Rotation automatique (garde les X derniers backups)
 * - Notification par email en cas d'échec
 * 
 * Usage:
 * php artisan db:backup                    # Backup simple
 * php artisan db:backup --compress         # Avec compression
 * php artisan db:backup --tables=users,orders  # Tables spécifiques
 */
class BackupDatabase extends Command
{
    protected $signature = 'db:backup 
                            {--compress : Compresser le fichier backup}
                            {--tables= : Tables spécifiques à sauvegarder (séparées par des virgules)}
                            {--keep=7 : Nombre de backups à conserver}
                            {--notify : Envoyer une notification en cas d\'échec}';

    protected $description = 'Créer une sauvegarde de la base de données';

    protected string $backupPath = 'backups/database';

    public function handle(): int
    {
        $this->info('=== Backup Base de Données ===');
        $this->info('Date: ' . now()->format('d/m/Y H:i:s'));
        
        try {
            // Créer le dossier de backup s'il n'existe pas
            $this->ensureBackupDirectoryExists();
            
            // Générer le nom du fichier
            $filename = $this->generateFilename();
            
            // Exécuter le backup
            $this->info('📦 Création du backup...');
            $filepath = $this->createBackup($filename);
            
            if (!$filepath) {
                throw new \Exception('Échec de la création du backup');
            }
            
            // Compression si demandée
            if ($this->option('compress')) {
                $this->info('🗜️ Compression du fichier...');
                $filepath = $this->compressBackup($filepath);
            }
            
            // Afficher les informations
            $size = $this->getFileSize($filepath);
            $this->info("✅ Backup créé: {$filepath}");
            $this->info("📊 Taille: {$size}");
            
            // Rotation des anciens backups
            $this->rotateBackups();
            
            // Logger le succès
            Log::info('Backup base de données réussi', [
                'file' => $filepath,
                'size' => $size,
            ]);
            
            return Command::SUCCESS;
            
        } catch (\Exception $e) {
            $this->error('❌ Erreur: ' . $e->getMessage());
            
            Log::error('Échec du backup base de données', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);
            
            // Notification si demandée
            if ($this->option('notify')) {
                $this->sendFailureNotification($e);
            }
            
            return Command::FAILURE;
        }
    }

    /**
     * Créer le dossier de backup
     */
    protected function ensureBackupDirectoryExists(): void
    {
        $path = storage_path('app/' . $this->backupPath);
        
        if (!file_exists($path)) {
            mkdir($path, 0755, true);
        }
    }

    /**
     * Générer le nom du fichier backup
     */
    protected function generateFilename(): string
    {
        $database = config('database.connections.mysql.database');
        $timestamp = now()->format('Y-m-d_His');
        
        return "{$database}_{$timestamp}.sql";
    }

    /**
     * Créer le backup SQL
     */
    protected function createBackup(string $filename): ?string
    {
        $filepath = storage_path("app/{$this->backupPath}/{$filename}");
        
        // Configuration de la base de données
        $host = config('database.connections.mysql.host');
        $port = config('database.connections.mysql.port', 3306);
        $database = config('database.connections.mysql.database');
        $username = config('database.connections.mysql.username');
        $password = config('database.connections.mysql.password');
        
        // Tables spécifiques ou toutes
        $tables = $this->option('tables');
        $tablesArg = $tables ? str_replace(',', ' ', $tables) : '';
        
        // Utiliser mysqldump si disponible
        $mysqldump = $this->findMysqldump();
        
        if ($mysqldump) {
            return $this->createBackupWithMysqldump($mysqldump, $filepath, $host, $port, $database, $username, $password, $tablesArg);
        }
        
        // Fallback: backup via PHP
        return $this->createBackupWithPHP($filepath, $tables ? explode(',', $tables) : null);
    }

    /**
     * Trouver l'exécutable mysqldump
     */
    protected function findMysqldump(): ?string
    {
        $paths = [
            'mysqldump',
            '/usr/bin/mysqldump',
            '/usr/local/bin/mysqldump',
            'C:\\xampp\\mysql\\bin\\mysqldump.exe',
            'C:\\xampp2\\mysql\\bin\\mysqldump.exe',
            'C:\\wamp64\\bin\\mysql\\mysql8.0.31\\bin\\mysqldump.exe',
        ];
        
        foreach ($paths as $path) {
            if (@is_executable($path) || (PHP_OS_FAMILY === 'Windows' && @file_exists($path))) {
                return $path;
            }
        }
        
        // Tester si mysqldump est dans le PATH
        $output = [];
        $returnCode = 0;
        @exec('mysqldump --version 2>&1', $output, $returnCode);
        
        if ($returnCode === 0) {
            return 'mysqldump';
        }
        
        return null;
    }

    /**
     * Backup avec mysqldump
     */
    protected function createBackupWithMysqldump(string $mysqldump, string $filepath, string $host, int $port, string $database, string $username, string $password, string $tables): ?string
    {
        $command = sprintf(
            '"%s" --host=%s --port=%d --user=%s --password=%s --single-transaction --quick --lock-tables=false %s %s > "%s" 2>&1',
            $mysqldump,
            escapeshellarg($host),
            $port,
            escapeshellarg($username),
            escapeshellarg($password),
            escapeshellarg($database),
            $tables,
            $filepath
        );
        
        $output = [];
        $returnCode = 0;
        exec($command, $output, $returnCode);
        
        if ($returnCode !== 0 || !file_exists($filepath) || filesize($filepath) === 0) {
            $this->warn('mysqldump a échoué, utilisation du fallback PHP');
            @unlink($filepath);
            return $this->createBackupWithPHP($filepath, $tables ? explode(' ', trim($tables)) : null);
        }
        
        return $filepath;
    }

    /**
     * Backup via PHP (fallback)
     */
    protected function createBackupWithPHP(string $filepath, ?array $specificTables = null): ?string
    {
        $this->info('  Utilisation du backup PHP...');
        
        $handle = fopen($filepath, 'w');
        
        if (!$handle) {
            return null;
        }
        
        // En-tête du fichier
        fwrite($handle, "-- Backup généré par BAMBOU EKO\n");
        fwrite($handle, "-- Date: " . now()->format('Y-m-d H:i:s') . "\n");
        fwrite($handle, "-- Base de données: " . config('database.connections.mysql.database') . "\n\n");
        fwrite($handle, "SET FOREIGN_KEY_CHECKS=0;\n\n");
        
        // Récupérer les tables
        $tables = $specificTables ?? $this->getAllTables();
        
        $progressBar = $this->output->createProgressBar(count($tables));
        $progressBar->start();
        
        foreach ($tables as $table) {
            // Structure de la table
            $createTable = DB::select("SHOW CREATE TABLE `{$table}`");
            if (!empty($createTable)) {
                fwrite($handle, "-- Table: {$table}\n");
                fwrite($handle, "DROP TABLE IF EXISTS `{$table}`;\n");
                fwrite($handle, $createTable[0]->{'Create Table'} . ";\n\n");
            }
            
            // Données de la table
            $rows = DB::table($table)->get();
            
            if ($rows->count() > 0) {
                $columns = array_keys((array) $rows->first());
                $columnsList = '`' . implode('`, `', $columns) . '`';
                
                foreach ($rows->chunk(100) as $chunk) {
                    $values = [];
                    foreach ($chunk as $row) {
                        $rowValues = [];
                        foreach ((array) $row as $value) {
                            if (is_null($value)) {
                                $rowValues[] = 'NULL';
                            } else {
                                $rowValues[] = "'" . addslashes((string) $value) . "'";
                            }
                        }
                        $values[] = '(' . implode(', ', $rowValues) . ')';
                    }
                    
                    fwrite($handle, "INSERT INTO `{$table}` ({$columnsList}) VALUES\n");
                    fwrite($handle, implode(",\n", $values) . ";\n\n");
                }
            }
            
            $progressBar->advance();
        }
        
        fwrite($handle, "SET FOREIGN_KEY_CHECKS=1;\n");
        fclose($handle);
        
        $progressBar->finish();
        $this->newLine();
        
        return $filepath;
    }

    /**
     * Récupérer toutes les tables
     */
    protected function getAllTables(): array
    {
        $tables = DB::select('SHOW TABLES');
        $key = 'Tables_in_' . config('database.connections.mysql.database');
        
        return array_map(fn($table) => $table->{$key}, $tables);
    }

    /**
     * Compresser le fichier backup
     */
    protected function compressBackup(string $filepath): string
    {
        $gzFilepath = $filepath . '.gz';
        
        $fp = fopen($filepath, 'rb');
        $gzfp = gzopen($gzFilepath, 'wb9');
        
        while (!feof($fp)) {
            gzwrite($gzfp, fread($fp, 1024 * 512));
        }
        
        fclose($fp);
        gzclose($gzfp);
        
        // Supprimer le fichier non compressé
        unlink($filepath);
        
        return $gzFilepath;
    }

    /**
     * Rotation des anciens backups
     */
    protected function rotateBackups(): void
    {
        $keep = (int) $this->option('keep');
        $path = storage_path('app/' . $this->backupPath);
        
        $files = glob($path . '/*.sql*');
        
        // Trier par date de modification (plus récent en premier)
        usort($files, fn($a, $b) => filemtime($b) - filemtime($a));
        
        // Supprimer les fichiers excédentaires
        $toDelete = array_slice($files, $keep);
        
        foreach ($toDelete as $file) {
            unlink($file);
            $this->info("🗑️ Ancien backup supprimé: " . basename($file));
        }
        
        if (count($toDelete) > 0) {
            $this->info("📋 {$keep} backup(s) conservé(s), " . count($toDelete) . " supprimé(s)");
        }
    }

    /**
     * Obtenir la taille formatée du fichier
     */
    protected function getFileSize(string $filepath): string
    {
        $bytes = filesize($filepath);
        
        if ($bytes >= 1073741824) {
            return number_format($bytes / 1073741824, 2) . ' GB';
        } elseif ($bytes >= 1048576) {
            return number_format($bytes / 1048576, 2) . ' MB';
        } elseif ($bytes >= 1024) {
            return number_format($bytes / 1024, 2) . ' KB';
        }
        
        return $bytes . ' bytes';
    }

    /**
     * Envoyer une notification d'échec
     */
    protected function sendFailureNotification(\Exception $e): void
    {
        $admins = \App\Models\User::where('role', 'admin')
            ->where('is_active', true)
            ->get();
        
        if ($admins->isEmpty()) {
            return;
        }
        
        \Illuminate\Support\Facades\Notification::send(
            $admins,
            new \App\Notifications\SystemAlertNotification([
                [
                    'type' => 'danger',
                    'title' => 'Échec du backup base de données',
                    'message' => 'Le backup automatique a échoué: ' . $e->getMessage(),
                    'action' => null,
                ]
            ])
        );
    }
}
