<?php

namespace Tests\Feature\ScssCdn;

use App\Livewire\ScssCdn\ZipUpload;
use App\Models\Auth\User;
use App\Models\Products\Product;
use App\Models\ScssCdn\ScssCdnBundle;
use App\Models\ScssCdn\ScssCdnFile;
use App\Services\ScssCdn\ScssCdnStorageService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Un segundo ZIP sobre el mismo bundle: qué pasa con lo que ya estaba (MGR-094).
 *
 * **El fallo era silencioso y el caso es el más común de todos: equivocarse y resubir.** La
 * subida recorría los ficheros del ZIP sobrescribiendo lo que coincidía y creando lo que no
 * estaba, y **no borraba nada**. Así que lo que había en el bundle y no venía en el ZIP nuevo
 * se quedaba, y seguía siendo servible si lo era.
 *
 * Si un fichero se renombró entre las dos pasadas, la versión vieja se queda **huérfana y
 * servible**, y el Moodle del cliente recibe las dos. Y no se veía en ninguna parte, porque
 * la pantalla del bundle no distingue lo que vino en la última subida de lo que había antes.
 *
 * La página del panel afirmaba además lo contrario —«un ZIP nuevo no se fusiona con el
 * anterior»—, que es la peor combinación: el comportamiento equivocado y documentado al
 * revés.
 */
class SegundoZipTest extends TestCase
{
    use RefreshDatabase;

    private function admin(): User
    {
        $rol = Role::firstOrCreate(['name' => 'Admin', 'guard_name' => 'web']);

        foreach (['admin.scss-cdn.bundle.upload', 'admin.scss-cdn.bundle.edit'] as $nombre) {
            $rol->givePermissionTo(Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'scss-cdn', 'description' => $nombre]
            ));
        }

        $user = User::factory()->create();
        $user->assignRole($rol);
        $this->actingAs($user);

        Cache::flush();

        return $user;
    }

    private function bundleCon(array $ficheros): array
    {
        // El slug es único en la tabla y algún test crea dos bundles.
        $slug = 'theme_fresk_' . Str::random(6);

        $producto = Product::create(['name' => 'Fresk', 'slug' => $slug, 'actived' => true]);

        $bundle = ScssCdnBundle::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'description' => 'Bundle de prueba',
        ]);

        foreach ($ficheros as $ruta => $contenido) {
            ScssCdnStorageService::saveFile($producto, $bundle, $ruta, $contenido);

            ScssCdnFile::create([
                'scss_cdn_bundle_id' => $bundle->id,
                'relative_path' => $ruta,
                'filename' => basename($ruta),
                'is_servable' => true,
                'secure_token' => Str::random(32),
            ]);
        }

        return [$producto, $bundle];
    }

    /**
     * Un ZIP de verdad, subido como lo subiría el navegador.
     *
     * Se devuelve como fichero **de prueba** de Laravel y no como `UploadedFile` a secas: el
     * arnés de Livewire lee `$file->name` al simular la subida, y un `UploadedFile` normal no
     * tiene esa propiedad. El contenido es un ZIP real, que es lo que importa.
     */
    private function zip(array $entradas): \Illuminate\Http\Testing\File
    {
        $ruta = tempnam(sys_get_temp_dir(), 'scss') . '.zip';

        $zip = new \ZipArchive;
        $zip->open($ruta, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);

        foreach ($entradas as $nombre => $contenido) {
            $zip->addFromString($nombre, $contenido);
        }

        $zip->close();

        return UploadedFile::fake()->createWithContent('bundle.zip', file_get_contents($ruta));
    }

    public function test_por_defecto_el_zip_nuevo_reemplaza_y_se_lleva_lo_que_ya_no_viene(): void
    {
        Storage::fake(config('filesystems.default'));

        [$producto, $bundle] = $this->bundleCon([
            'main.scss' => '/* el bueno */',
            'viejo.scss' => '/* el que se renombró */',
        ]);

        $this->admin();

        Livewire::test(ZipUpload::class, ['product' => $producto, 'bundle' => $bundle])
            ->assertSet('modo', ZipUpload::REEMPLAZAR)
            ->set('zipFile', $this->zip(['main.scss' => '/* la version nueva */']))
            ->call('uploadDirect');

        $this->assertNotNull(
            ScssCdnFile::where('scss_cdn_bundle_id', $bundle->id)->where('relative_path', 'main.scss')->first()
        );

        // **Lo que ya no viene se va.** Antes se quedaba servible y el cliente recibía las dos.
        $this->assertNull(
            ScssCdnFile::where('scss_cdn_bundle_id', $bundle->id)->where('relative_path', 'viejo.scss')->first(),
            'el fichero que ya no viene en el ZIP sigue en el bundle'
        );

        // Y del disco también: una fila borrada con el fichero detrás es un huérfano que
        // nadie ve y que sigue ocupando.
        $this->assertFalse(
            ScssCdnStorageService::fileExists($producto, $bundle, 'viejo.scss'),
            'el fichero físico sigue en el disco'
        );
    }

    public function test_lo_borrado_se_dice_y_con_su_nombre(): void
    {
        // Un borrado que no se nombra no se puede auditar: quien resube quiere saber qué se
        // ha llevado por delante su ZIP.
        Storage::fake(config('filesystems.default'));

        [$producto, $bundle] = $this->bundleCon([
            'main.scss' => '/* el bueno */',
            'viejo.scss' => '/* fuera */',
        ]);

        $this->admin();

        $componente = Livewire::test(ZipUpload::class, ['product' => $producto, 'bundle' => $bundle])
            ->set('zipFile', $this->zip(['main.scss' => '/* nueva */']))
            ->call('uploadDirect');

        $mensaje = $componente->get('uploadStatus')['message'];

        $this->assertStringContainsString('viejo.scss', $mensaje);
        $this->assertStringContainsString('ya no venían en el ZIP', $mensaje);
    }

    public function test_fusionar_conserva_lo_anterior_y_avisa_de_lo_que_supone(): void
    {
        // La fusión sigue estando para el caso legítimo —añadir a un bundle existente—, pero
        // **elegida a mano y diciendo el riesgo**: si un fichero se renombró, quedan las dos
        // versiones y el producto las recibe.
        Storage::fake(config('filesystems.default'));

        [$producto, $bundle] = $this->bundleCon([
            'main.scss' => '/* el bueno */',
            'viejo.scss' => '/* se queda */',
        ]);

        $this->admin();

        $componente = Livewire::test(ZipUpload::class, ['product' => $producto, 'bundle' => $bundle])
            ->set('modo', ZipUpload::FUSIONAR)
            ->set('zipFile', $this->zip(['main.scss' => '/* nueva */']))
            ->call('uploadDirect');

        $this->assertNotNull(
            ScssCdnFile::where('scss_cdn_bundle_id', $bundle->id)->where('relative_path', 'viejo.scss')->first()
        );

        $mensaje = $componente->get('uploadStatus')['message'];

        $this->assertStringContainsString('conserva', $mensaje);
        $this->assertStringContainsString('recibe las dos', $mensaje);
    }

    public function test_un_zip_que_no_deja_nada_no_vacia_el_bundle(): void
    {
        // **El peor resultado posible de una subida fallida.** Si el ZIP no ha guardado ni un
        // fichero, borrar «lo que ya no viene» sería borrar el bundle entero: el borrado va
        // detrás de haber guardado algo.
        Storage::fake(config('filesystems.default'));

        [$producto, $bundle] = $this->bundleCon(['main.scss' => '/* el bueno */']);

        $this->admin();

        Livewire::test(ZipUpload::class, ['product' => $producto, 'bundle' => $bundle])
            ->set('zipFile', $this->zip(['leeme.txt' => 'esto no es un scss']))
            ->call('uploadDirect');

        $this->assertNotNull(
            ScssCdnFile::where('scss_cdn_bundle_id', $bundle->id)->where('relative_path', 'main.scss')->first(),
            'una subida que no ha guardado nada se ha llevado el bundle por delante'
        );
    }

    public function test_la_pantalla_pregunta_solo_si_hay_algo_que_reemplazar(): void
    {
        Storage::fake(config('filesystems.default'));

        [$producto, $bundle] = $this->bundleCon([]);

        $this->admin();

        Livewire::test(ZipUpload::class, ['product' => $producto, 'bundle' => $bundle])
            ->assertViewHas('yaHay', 0)
            ->assertDontSee('Reemplazar el árbol');

        [$otroProducto, $conFicheros] = $this->bundleCon(['main.scss' => '/* algo */']);

        Livewire::test(ZipUpload::class, ['product' => $otroProducto, 'bundle' => $conFicheros])
            ->assertViewHas('yaHay', 1)
            ->assertSee('Reemplazar el árbol');
    }
}
