<?php

namespace App\Services;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\URL;

class FileStorageService
{
    private const DEFAULT_ALLOWED_TYPES = ['pdf', 'doc', 'docx', 'jpg', 'jpeg', 'png', 'webp', 'xlsx', 'xls', 'csv'];
    private const DEFAULT_MAX_SIZE_KB = 5120;

    /**
     * Sube un archivo al storage
     */
    public function upload(
        UploadedFile $file,
        string $disk,
        string $path,
        ?array $allowedTypes = null,
        ?int $maxSizeKB = null,
    ): array {
        try {
            $allowedTypes ??= self::DEFAULT_ALLOWED_TYPES;
            $maxSizeKB ??= self::DEFAULT_MAX_SIZE_KB;

            $this->validateFile($file, $allowedTypes, $maxSizeKB);

            $fileName = $this->generateUniqueFileName($file);
            $storedPath = Storage::disk($disk)->putFileAs(rtrim($path, '/'), $file, $fileName);

            return [
                'success' => true,
                'message' => 'Archivo subido exitosamente',
                'data' => [
                    'path' => $storedPath,
                    'file_name' => $fileName,
                    'original_name' => $file->getClientOriginalName(),
                    'extension' => $this->getFileExtension($file),
                    'size' => $file->getSize(),
                    'mime_type' => $file->getMimeType(),
                    'disk' => $disk,
                ],
            ];
        } catch (\Exception $e) {
            return [
                'success' => false,
                'message' => $e->getMessage(),
                'data' => null,
            ];
        }
    }

    /**
     * Actualiza un archivo (elimina anterior y sube nuevo)
     */
    public function update(
        UploadedFile $file,
        string $disk,
        string $path,
        string $oldFilePath,
        ?array $allowedTypes = null,
        ?int $maxSizeKB = null,
    ): array {
        try {
            $uploadResult = $this->upload($file, $disk, $path, $allowedTypes, $maxSizeKB);

            if (!$uploadResult['success']) {
                return $uploadResult;
            }

            // Eliminar archivo anterior si existe
            if (Storage::disk($disk)->exists($oldFilePath)) {
                Storage::disk($disk)->delete($oldFilePath);
            }

            $uploadResult['message'] = 'Archivo actualizado exitosamente';
            return $uploadResult;
        } catch (\Exception $e) {
            return [
                'success' => false,
                'message' => $e->getMessage(),
                'data' => null,
            ];
        }
    }

    /**
     * Elimina un archivo del storage
     */
    public function delete(string $path, string $disk = 'local'): array
    {
        try {
            if (!Storage::disk($disk)->exists($path)) {
                return [
                    'success' => false,
                    'message' => 'Archivo no encontrado',
                    'data' => null,
                ];
            }

            Storage::disk($disk)->delete($path);

            return [
                'success' => true,
                'message' => 'Archivo eliminado exitosamente',
                'data' => ['path' => $path],
            ];
        } catch (\Exception $e) {
            return [
                'success' => false,
                'message' => $e->getMessage(),
                'data' => null,
            ];
        }
    }

    /**
     * Verifica si un archivo existe
     */
    public function exists(string $path, string $disk = 'local'): bool
    {
        return Storage::disk($disk)->exists($path);
    }

    /**
     * Muestra archivo en el navegador
     */
    public function display(string $path, string $disk = 'local')
    {
        if (!Storage::disk($disk)->exists($path)) {
            abort(404, 'Archivo no encontrado');
        }

        return Storage::disk($disk)->response($path);
    }

    /**
     * Descarga un archivo
     */
    public function download(string $path, string $disk = 'local')
    {
        if (!Storage::disk($disk)->exists($path)) {
            abort(404, 'Archivo no encontrado');
        }

        return Storage::disk($disk)->download($path);
    }

    /**
     * Genera URL temporal firmada (expira en X minutos)
     */
    public function getTemporaryUrl(string $path, string $disk = 'local', int $minutes = 60): string
    {
        if (!Storage::disk($disk)->exists($path)) {
            throw new \Exception('Archivo no encontrado');
        }

        return URL::temporarySignedRoute(
            'files.display.signed',
            now()->addMinutes($minutes),
            ['path' => $path, 'disk' => $disk]
        );
    }

    /**
     * Obtiene extensión del archivo de forma robusta
     * Usa pathinfo() en lugar de extension() para manejar nombres complejos
     */
    private function getFileExtension(UploadedFile $file): string
    {
        $originalName = $file->getClientOriginalName();
        return strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
    }

    /**
     * Genera nombre único: {uuid}_{nombre-sanitizado}.{ext}
     */
    private function generateUniqueFileName(UploadedFile $file): string
    {
        $originalName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
        $extension = $this->getFileExtension($file);

        // Sanitizar nombre: slugify y limitar longitud
        $safeName = Str::slug($originalName, '_');
        $safeName = Str::limit($safeName, 50, '');

        return Str::uuid() . "_{$safeName}.{$extension}";
    }

    /**
     * Valida tipo, tamaño e integridad del archivo
     */
    private function validateFile(UploadedFile $file, array $allowedTypes, int $maxSizeKB): void
    {
        if (!$file->isValid()) {
            throw new \InvalidArgumentException('El archivo no es válido o está corrupto');
        }

        $extension = $this->getFileExtension($file);

        if (!in_array($extension, $allowedTypes)) {
            throw new \InvalidArgumentException(
                "Tipo de archivo no permitido. Permitidos: " . implode(', ', $allowedTypes)
            );
        }

        $fileSizeKB = round($file->getSize() / 1024, 2);
        if ($fileSizeKB > $maxSizeKB) {
            throw new \InvalidArgumentException(
                "El archivo excede el tamaño máximo de {$maxSizeKB}KB. Tamaño actual: {$fileSizeKB}KB"
            );
        }
    }
}

/*
|--------------------------------------------------------------------------
| DOCUMENTACIÓN - FileStorageService
|--------------------------------------------------------------------------
|
| Servicio para gestión de archivos en Laravel (subir, actualizar, eliminar, mostrar, descargar).
|
| DISCOS DISPONIBLES:
| - 'public': Accesibles vía URL (storage/app/public/)
| - 'local': Privados (storage/app/private/)
|
| MÉTODOS PRINCIPALES:
|
| upload(file, disk, path, ?allowedTypes, ?maxSizeKB)
|   - Sube archivo nuevo
|   - Retorna: ['success' => bool, 'message' => string, 'data' => array]
|
| update(file, disk, path, oldFilePath, ?allowedTypes, ?maxSizeKB)
|   - Reemplaza archivo existente
|
| delete(path, disk = 'local')
|   - Elimina archivo
|
| display(path, disk = 'local')
|   - Muestra archivo en navegador (StreamedResponse)
|
| download(path, disk = 'local')
|   - Descarga archivo (StreamedResponse)
|
| exists(path, disk = 'local')
|   - Verifica si existe (bool)
|
| getTemporaryUrl(path, disk = 'local', minutes = 60)
|   - URL temporal firmada (string)
|
| EJEMPLO DE USO:
|
| $result = $fileService->upload(
|     file: $request->file('document'),
|     disk: 'local',
|     path: 'students/documents',
|     allowedTypes: ['pdf', 'jpg'],
|     maxSizeKB: 5120
| );
|
| if ($result['success']) {
|     $student->update(['document_file' => $result['data']['path']]);
| }
|
*/