<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Symfony\Component\Process\Process;
use ZipArchive;

class BackupRun extends Command
{
    protected $signature = 'backup:run {--keep=7 : Nombre de sauvegardes à conserver par type}';

    protected $description = 'Sauvegarde la base de données (mysqldump) et les fichiers uploadés (zip), avec rotation.';

    public function handle(): int
    {
        $keep = max(1, (int) $this->option('keep'));
        $backupDir = storage_path('app/backups');
        File::ensureDirectoryExists($backupDir);

        $date = now()->format('Y-m-d_His');

        $this->backupDatabase($backupDir, $date);
        $this->backupFiles($backupDir, $date);

        $this->rotate($backupDir, 'db-', $keep);
        $this->rotate($backupDir, 'files-', $keep);

        $this->info('Sauvegarde terminée.');

        return self::SUCCESS;
    }

    private function backupDatabase(string $backupDir, string $date): void
    {
        $config = config('database.connections.'.config('database.default'));

        if (($config['driver'] ?? null) !== 'mysql') {
            $this->warn('Sauvegarde BDD ignorée : seul MySQL est pris en charge (driver actuel : '.($config['driver'] ?? 'inconnu').').');

            return;
        }

        $target = $backupDir.DIRECTORY_SEPARATOR.'db-'.$date.'.sql';

        $command = [
            'mysqldump',
            '--host='.($config['host'] ?? '127.0.0.1'),
            '--port='.($config['port'] ?? '3306'),
            '--user='.($config['username'] ?? 'root'),
        ];

        if (! empty($config['password'])) {
            $command[] = '--password='.$config['password'];
        }

        $command[] = '--single-transaction';
        $command[] = '--skip-lock-tables';
        $command[] = $config['database'];

        $process = new Process($command);
        $process->setTimeout(600);

        $handle = fopen($target, 'w');
        try {
            $process->run(function ($type, $buffer) use ($handle) {
                if ($type === Process::OUT) {
                    fwrite($handle, $buffer);
                }
            });
        } finally {
            fclose($handle);
        }

        if (! $process->isSuccessful()) {
            @unlink($target);
            $this->error('Échec du mysqldump : '.trim($process->getErrorOutput()));

            return;
        }

        $this->info('Base de données sauvegardée : '.basename($target));
    }

    private function backupFiles(string $backupDir, string $date): void
    {
        $source = storage_path('app/private');

        if (! is_dir($source)) {
            $this->warn('Aucun dossier de fichiers privés à sauvegarder ('.$source.').');

            return;
        }

        $target = $backupDir.DIRECTORY_SEPARATOR.'files-'.$date.'.zip';

        $zip = new ZipArchive();
        if ($zip->open($target, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
            $this->error('Impossible de créer l\'archive : '.$target);

            return;
        }

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($source, \FilesystemIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::LEAVES_ONLY
        );

        foreach ($iterator as $file) {
            if ($file->isDir()) {
                continue;
            }
            $relativePath = ltrim(str_replace($source, '', $file->getPathname()), DIRECTORY_SEPARATOR);
            $zip->addFile($file->getPathname(), str_replace('\\', '/', $relativePath));
        }

        $zip->close();

        $this->info('Fichiers sauvegardés : '.basename($target));
    }

    private function rotate(string $backupDir, string $prefix, int $keep): void
    {
        $files = collect(File::files($backupDir))
            ->filter(fn ($file) => str_starts_with($file->getFilename(), $prefix))
            ->sortByDesc(fn ($file) => $file->getMTime())
            ->values();

        $files->slice($keep)->each(function ($file) {
            @unlink($file->getPathname());
        });
    }
}
