<?php

namespace App\Services;

use Carbon\Carbon;
use Illuminate\Support\Facades\File;
use RuntimeException;
use Symfony\Component\Process\Process;

class DatabaseBackupService
{
    /**
     * @return array{path: string, filename: string, size_bytes: int, compressed: bool, created_at: string}
     */
    public function createBackup(?bool $gzip = null): array
    {
        $this->assertMysql();

        $gzip = $gzip ?? (bool) config('ranch.backup.gzip', true);
        $dir = $this->backupDirectory();
        File::ensureDirectoryExists($dir);

        $connection = config('database.connections.mysql');
        $database = $connection['database'] ?? null;
        if (! $database) {
            throw new RuntimeException('DB_DATABASE no está configurada.');
        }

        $sql = $this->runMysqldump($connection, $database);

        $timestamp = Carbon::now()->format('Y-m-d_His');
        $extension = $gzip ? '.sql.gz' : '.sql';
        $filename = "ganaderia_{$database}_{$timestamp}{$extension}";
        $path = $dir.DIRECTORY_SEPARATOR.$filename;

        file_put_contents($path, $gzip ? gzencode($sql, 9) : $sql);

        $this->pruneOldBackups($dir, (int) config('ranch.backup.retention_days', 14));

        return $this->describeFile($path);
    }

    /**
     * @return list<array{filename: string, size_bytes: int, compressed: bool, created_at: string}>
     */
    public function listBackups(): array
    {
        $dir = $this->backupDirectory();
        if (! File::isDirectory($dir)) {
            return [];
        }

        $files = collect(File::glob($dir.DIRECTORY_SEPARATOR.'ganaderia_*.sql*') ?: [])
            ->filter(fn ($path) => is_file($path))
            ->sortByDesc(fn ($path) => filemtime($path))
            ->values();

        return $files->map(fn ($path) => $this->describeFile($path))->all();
    }

    /**
     * @return array{path: string, filename: string, size_bytes: int, compressed: bool, created_at: string}
     */
    public function resolveDownload(?string $filename = null, bool $fresh = false): array
    {
        if ($fresh || ! $filename) {
            return $this->createBackup();
        }

        $safeName = basename($filename);
        if (! preg_match('/^ganaderia_.+\.sql(\.gz)?$/', $safeName)) {
            throw new RuntimeException('Nombre de archivo de respaldo inválido.');
        }

        $path = $this->backupDirectory().DIRECTORY_SEPARATOR.$safeName;
        if (! is_file($path)) {
            throw new RuntimeException('El respaldo solicitado no existe.');
        }

        return $this->describeFile($path);
    }

    private function assertMysql(): void
    {
        if (config('database.default') !== 'mysql') {
            throw new RuntimeException('El respaldo automático solo está disponible con MySQL.');
        }
    }

    private function backupDirectory(): string
    {
        return (string) config('ranch.backup.path', storage_path('app/backups'));
    }

    /**
     * @param  array<string, mixed>  $connection
     */
    private function runMysqldump(array $connection, string $database): string
    {
        $mysqldump = (string) config('ranch.backup.mysqldump_path', 'mysqldump');
        $defaultsFile = tempnam(sys_get_temp_dir(), 'mysql_');
        if ($defaultsFile === false) {
            throw new RuntimeException('No se pudo preparar la conexión a MySQL.');
        }

        file_put_contents($defaultsFile, implode("\n", [
            '[client]',
            'user='.$connection['username'],
            'password='.$connection['password'],
            'host='.$connection['host'],
            'port='.($connection['port'] ?? 3306),
        ]));

        $process = new Process([
            $mysqldump,
            '--defaults-extra-file='.$defaultsFile,
            '--single-transaction',
            '--routines',
            '--triggers',
            $database,
        ]);
        $process->setTimeout(600);
        $process->run();
        @unlink($defaultsFile);

        if (! $process->isSuccessful()) {
            $error = trim($process->getErrorOutput() ?: $process->getOutput() ?: 'mysqldump falló');
            throw new RuntimeException(
                'No se pudo generar el respaldo. Verifica que mysqldump esté instalado en el servidor '
                ."(RANCH_MYSQLDUMP_PATH). Detalle: {$error}"
            );
        }

        return $process->getOutput();
    }

    private function pruneOldBackups(string $dir, int $retentionDays): void
    {
        if ($retentionDays <= 0) {
            return;
        }

        $cutoff = Carbon::now()->subDays($retentionDays)->getTimestamp();
        foreach (File::glob($dir.DIRECTORY_SEPARATOR.'ganaderia_*.sql*') ?: [] as $file) {
            if (is_file($file) && filemtime($file) < $cutoff) {
                File::delete($file);
            }
        }
    }

    /**
     * @return array{path: string, filename: string, size_bytes: int, compressed: bool, created_at: string}
     */
    private function describeFile(string $path): array
    {
        $filename = basename($path);

        return [
            'path' => $path,
            'filename' => $filename,
            'size_bytes' => (int) filesize($path),
            'compressed' => str_ends_with($filename, '.gz'),
            'created_at' => Carbon::createFromTimestamp((int) filemtime($path))->toIso8601String(),
        ];
    }
}
