<?php

namespace App\Livewire\ScssCdn;

use App\Support\LimiteDeSubida;
use App\Models\Products\Product;
use App\Models\ScssCdn\ScssCdnBundle;
use App\Models\ScssCdn\ScssCdnFile;
use App\Services\ScssCdn\PuntosDeEntrada;
use App\Services\ScssCdn\ScssCdnStorageService;
use App\Services\ScssCdn\ScssCdnZipService;
use App\Services\System\Settings;
use Illuminate\Support\Facades\Log;
use Livewire\Component;
use Livewire\WithFileUploads;
use Illuminate\Support\Str;

class ZipUpload extends Component
{
    use WithFileUploads;

    public Product $product;
    public ScssCdnBundle $bundle;
    public $zipFile;
    public $uploadStatus = null;
    public $processedFiles = [];

    /**
     * Qué hacer con lo que ya hay en el bundle: `reemplazar` o `fusionar`.
     *
     * **Antes no había elección: fusionaba siempre y sin decirlo.** Un ZIP nuevo
     * sobrescribía lo que coincidía, creaba lo que no estaba y **dejaba intacto lo que ya no
     * viene** — que es lo que pasa al renombrar un fichero entre dos subidas: la versión
     * vieja se queda huérfana, servible, y el cliente recibe las dos.
     *
     * `reemplazar` es el defecto porque es lo que espera quien vuelve a subir el árbol —«esto
     * es el bundle ahora»—, y porque el otro deja basura que nadie ve. La fusión sigue ahí
     * para el caso legítimo de añadir a un bundle existente, pero **elegida a mano**.
     */
    public string $modo = self::REEMPLAZAR;

    public const REEMPLAZAR = 'reemplazar';

    public const FUSIONAR = 'fusionar';

    /** Cuántas rutas se nombran en el mensaje antes de resumir con «y N más». */
    private const CUANTOS_SE_NOMBRAN = 5;

    public function mount(Product $product, ScssCdnBundle $bundle)
    {
        $this->product = $product;
        $this->bundle = $bundle->load('product');

        // Verificar que el bundle pertenezca al producto
        if ($this->bundle->product_id !== $this->product->id) {
            abort(404, 'El bundle no pertenece a este producto.');
        }
    }

    public function updatedZipFile()
    {
        $this->uploadStatus = null;
        $this->processedFiles = [];
        $this->resetErrorBag('zipFile');
    }

    public function uploadDirect()
    {
        // Método principal que llama a upload() internamente
        // Se usa uploadDirect() en lugar de upload() directamente porque
        // Livewire tiene problemas con métodos que tienen validación cuando se llaman directamente
        if (!$this->zipFile) {
            $this->uploadStatus = [
                'type' => 'error',
                'message' => 'Debe seleccionar un archivo ZIP.',
            ];
            return;
        }

        // Llamar al método upload() que contiene toda la lógica
        $this->upload();
    }


    public function upload()
    {
        // Único punto de paso: `uploadDirect()` acaba llamando aquí, así que con
        // comprobarlo en este método quedan cubiertas las dos entradas.
        // Ver known-issues MGR-005.
        $this->authorize('admin.scss-cdn.bundle.upload');

        try {
            // Validar que se haya seleccionado un archivo
            if (!$this->zipFile) {
                $this->uploadStatus = [
                    'type' => 'error',
                    'message' => 'Debe seleccionar un archivo ZIP.',
                ];
                return;
            }

            // Validar el archivo - usar reglas directamente
            $this->validate([
                // **La constante existía y esta regla no la usaba.** Se calcula con lo
                // que permite el PHP de la máquina: un ZIP de 50 MB no pasa por un
                // `post_max_size` de 8, y anunciarlo sería mentir (PRODSECU-156).
                // El tope sale de Ajustes del Manager, y `enKb()` sigue quedándose con el
                // menor de ese y los del PHP de la máquina: subir el ajuste no puede
                // saltarse el `upload_max_filesize`.
                'zipFile' => ['required', 'file', 'mimes:zip', 'max:' . LimiteDeSubida::enKb(app(Settings::class)->zipDeBundleEnKb())],
            ], [
                'zipFile.required' => 'Debe seleccionar un archivo ZIP.',
                'zipFile.mimes' => 'El archivo debe ser un ZIP.',
                'zipFile.max' => 'El archivo ZIP no puede exceder 50MB.',
            ]);
        } catch (\Illuminate\Validation\ValidationException $e) {
            $errorMessages = [];
            foreach ($e->errors() as $field => $messages) {
                $errorMessages = array_merge($errorMessages, $messages);
            }
            $this->uploadStatus = [
                'type' => 'error',
                'message' => 'Error de validación: ' . implode(', ', $errorMessages),
            ];
            return;
        } catch (\Exception $e) {
            Log::error('ZipUpload: Error en validación', [
                'message' => $e->getMessage(),
            ]);
            $this->uploadStatus = [
                'type' => 'error',
                'message' => 'Error: ' . $e->getMessage(),
            ];
            return;
        }

        try {
            // En Livewire, el archivo se sube automáticamente y está disponible
            // Obtener la ruta temporal del archivo
            $zipPath = $this->zipFile->getRealPath();

            // Si no está disponible, intentar obtenerlo del storage temporal
            if (!$zipPath || !file_exists($zipPath)) {
                // El archivo puede estar en el storage temporal de Livewire
                $zipPath = $this->zipFile->path();
            }

            if (!$zipPath || !file_exists($zipPath)) {
                Log::error('ZipUpload: No se pudo acceder al archivo', [
                    'realPath' => $this->zipFile->getRealPath(),
                    'path' => method_exists($this->zipFile, 'path') ? $this->zipFile->path() : 'N/A',
                ]);
                $this->addError('zipFile', 'No se pudo acceder al archivo ZIP. Por favor, intente subirlo nuevamente.');
                $this->uploadStatus = [
                    'type' => 'error',
                    'message' => 'No se pudo acceder al archivo ZIP. Por favor, intente subirlo nuevamente.',
                ];
                return;
            }

            // Validar estructura del ZIP
            $validation = ScssCdnZipService::validateZipStructure($zipPath);

            if (!$validation['valid']) {
                $this->addError('zipFile', implode(' ', $validation['errors']));
                return;
            }

            // Extraer archivos del ZIP
            $extractedFiles = ScssCdnZipService::extractZip($zipPath);

            // Procesar archivos extraídos
            $processedFiles = ScssCdnZipService::processExtractedFiles($extractedFiles);

            $savedCount = 0;
            $skippedCount = 0;
            $errors = [];

            // **Lo que había y ya no viene.** Se calcula antes de escribir nada, para poder
            // decirlo con número tanto si se borra como si se conserva: un bundle que
            // arrastra ficheros de una subida anterior no lo dice en ninguna pantalla.
            $rutasDelZip = array_column($processedFiles, 'relative_path');

            $sobrantes = ScssCdnFile::where('scss_cdn_bundle_id', $this->bundle->id)
                ->whereNotIn('relative_path', $rutasDelZip)
                ->get();

            foreach ($processedFiles as $fileData) {
                try {
                    // Verificar si el archivo ya existe
                    $existingFile = ScssCdnFile::where('scss_cdn_bundle_id', $this->bundle->id)
                        ->where('relative_path', $fileData['relative_path'])
                        ->first();

                    if ($existingFile) {
                        // Actualizar archivo existente
                        $existingFile->update([
                            'updated_by' => auth()->id(),
                        ]);
                    } else {
                        // Crear nuevo archivo
                        $existingFile = ScssCdnFile::create([
                            'scss_cdn_bundle_id' => $this->bundle->id,
                            'relative_path' => $fileData['relative_path'],
                            'filename' => $fileData['filename'],
                            'is_servable' => false,
                            'secure_token' => Str::random(32),
                            'created_by' => auth()->id(),
                            'updated_by' => auth()->id(),
                        ]);
                    }

                    // Guardar archivo físico
                    $saved = ScssCdnStorageService::saveFile(
                        $this->product,
                        $this->bundle,
                        $fileData['relative_path'],
                        $fileData['content']
                    );

                    if (!$saved) {
                        $errors[] = "Error al guardar el archivo físico: {$fileData['relative_path']}";
                        if (!$existingFile->wasRecentlyCreated) {
                            // Si era un archivo nuevo, eliminarlo de BD
                            $existingFile->delete();
                        }
                    } else {
                        $savedCount++;
                        $this->processedFiles[] = [
                            'path' => $fileData['relative_path'],
                            'size' => $fileData['size'],
                            'status' => $existingFile->wasRecentlyCreated ? 'created' : 'updated',
                        ];
                    }
                } catch (\Exception $e) {
                    $errors[] = "Error al procesar {$fileData['relative_path']}: " . $e->getMessage();
                }
            }

            // **Y se borran, si eso es lo que se ha pedido.** Va después de guardar y solo
            // si algo se guardó: si el ZIP no ha dejado nada, borrar el bundle entero por el
            // camino sería el peor resultado posible de una subida fallida.
            $borrados = [];

            if ($this->modo === self::REEMPLAZAR && $savedCount > 0) {
                foreach ($sobrantes as $sobrante) {
                    try {
                        ScssCdnStorageService::deleteFile(
                            $this->product,
                            $this->bundle,
                            $sobrante->relative_path
                        );

                        $borrados[] = $sobrante->relative_path;
                        $sobrante->delete();
                    } catch (\Exception $e) {
                        $errors[] = "No se pudo borrar {$sobrante->relative_path}: " . $e->getMessage();
                    }
                }
            }

            if ($savedCount > 0) {
                // **Marcar los puntos de entrada** (MGR-093). Antes todo entraba con
                // `is_servable = false` y sin detección, así que el bundle quedaba publicado
                // y vacío: la API respondía «no hay ficheros servibles», que no es un error
                // y por tanto no avisaba a nadie.
                $entradas = PuntosDeEntrada::marcar($this->bundle);

                $parte = "Se procesaron {$savedCount} archivo(s) correctamente."
                    . ($skippedCount > 0 ? " Se omitieron {$skippedCount} archivo(s)." : '');

                // **Y lo que ha pasado con lo que ya estaba, que es lo que no se decía.**
                // Un bundle que arrastra ficheros de una subida anterior no lo enseña en
                // ninguna pantalla: la lista no distingue lo que vino en la última subida.
                if ($borrados !== []) {
                    $parte .= ' Se ' . (count($borrados) === 1 ? 'borró 1 archivo' : 'borraron '
                        . count($borrados) . ' archivos') . ' que ya no venían en el ZIP: '
                        . implode(', ', array_slice($borrados, 0, self::CUANTOS_SE_NOMBRAN))
                        . (count($borrados) > self::CUANTOS_SE_NOMBRAN
                            ? ' y ' . (count($borrados) - self::CUANTOS_SE_NOMBRAN) . ' más'
                            : '') . '.';
                } elseif ($this->modo === self::FUSIONAR && $sobrantes->isNotEmpty()) {
                    $parte .= ' Se ' . ($sobrantes->count() === 1 ? 'conserva 1 archivo'
                        : 'conservan ' . $sobrantes->count() . ' archivos')
                        . ' de la subida anterior que no venían en este ZIP, porque has elegido '
                        . 'fusionar. Si alguno se renombró, la versión vieja sigue ahí y el '
                        . 'producto recibe las dos.';
                }

                if ($entradas['ninguno']) {
                    // No se ha podido deducir ninguno: o todos se importan entre sí, o son
                    // todos partials. Se dice, porque un bundle sin nada servible no sirve
                    // nada y eso no se ve en ninguna parte.
                    $this->uploadStatus = [
                        'type' => 'warning',
                        'message' => $parte . ' **Ningún archivo ha quedado marcado como '
                            . 'servible**: no se ha podido deducir cuál es el punto de entrada '
                            . 'porque todos se importan entre sí. Márcalo a mano, o el producto '
                            . 'no recibirá nada.',
                    ];
                } elseif ($entradas['ambiguo']) {
                    // **Hay más de un candidato: no se marca ninguno.** Un theme apunta
                    // a un punto de entrada, así que dos suelen ser un fichero suelto en
                    // el ZIP — y eso es lo que hay que enseñar. Marcarlo en silencio
                    // esconde el aviso.
                    $this->uploadStatus = [
                        'type' => 'warning',
                        'message' => $parte . ' **Ningún archivo ha quedado marcado como '
                            . 'servible**: hay ' . count($entradas['candidatos']) . ' archivos '
                            . 'a los que ningún otro importa, así que no está claro cuál es el '
                            . 'punto de entrada — ' . implode(', ', $entradas['candidatos'])
                            . '. Normalmente es uno solo: si alguno de esos sobra en el ZIP, '
                            . 'quítalo; si no, marca a mano el que sea la entrada.',
                    ];
                } else {
                    $this->uploadStatus = [
                        'type' => 'success',
                        'message' => $parte . ($entradas['marcados'] === 1
                            ? ' Marcado 1 de ' . $entradas['total'] . ' como servible: '
                                . 'es el único al que ningún otro archivo importa. Revísalo si '
                                . 'tu punto de entrada es otro.'
                            : ' El punto de entrada ya estaba marcado.'),
                    ];
                }
            }

            if (!empty($errors)) {
                $this->uploadStatus = [
                    'type' => 'warning',
                    'message' => "Se procesaron {$savedCount} archivo(s), pero hubo errores: " . implode(', ', array_slice($errors, 0, 3)),
                ];
            }

            // Recargar bundle
            $this->bundle->refresh();

            // Limpiar archivo temporal
            $this->zipFile = null;
        } catch (\Illuminate\Validation\ValidationException $e) {
            // Re-lanzar para que Livewire lo maneje
            throw $e;
        } catch (\Exception $e) {
            Log::error('ZipUpload: Error al procesar ZIP', [
                'message' => $e->getMessage(),
            ]);
            $this->addError('zipFile', 'Error al procesar el ZIP: ' . $e->getMessage());
            $this->uploadStatus = [
                'type' => 'error',
                'message' => 'Error al procesar el ZIP: ' . $e->getMessage(),
            ];
        }
    }

    public function render()
    {
        return view('livewire.scss-cdn.zip-upload', [
            'product' => $this->product,
            'bundle' => $this->bundle,
            // Cuántos ficheros hay ya: es lo que decide si la elección importa, y sin el
            // número el selector es una pregunta abstracta.
            'yaHay' => $this->bundle->files()->count(),
        ])->layout('layouts.clean');
    }
}
