<?php

namespace Tests\Feature\Features;

use App\Support\LimiteDeSubida;
use Livewire\Livewire;
use Tests\TestCase;

/**
 * Ningún tope de subida está escrito a mano (PRODSECU-156).
 *
 * **Esto es lo que se me pasó la primera vez.** Se arregló el de las imágenes y quedaron
 * cuatro más —documentos, YAML, ZIP y el del formulario de tipos de entorno— diciendo
 * «Máximo 10MB» y «Tamaño máximo: 50MB» a mano. Con tres números distintos repartidos en
 * ocho sitios, arreglar uno no se nota: los otros siguen mintiendo exactamente igual.
 *
 * Y había un caso peor: `LimiteDeSubida::ZIP_KB` **existía y la regla del ZIP no lo
 * usaba**. La constante puesta, y nadie leyéndola.
 *
 * Así que el test no comprueba un número: **recorre el código buscando topes escritos a
 * mano**. Es la única forma de que el sexto formulario que alguien añada mañana no vuelva a
 * empezar la cuenta.
 */
class NingunTopeEscritoAManoTest extends TestCase
{
    protected function tearDown(): void
    {
        LimiteDeSubida::dejarDeFingir();

        parent::tearDown();
    }

    /** Los ficheros donde puede aparecer un tope: reglas y textos de ayuda. */
    private function ficherosQueMirar(): array
    {
        $raiz = base_path();

        $ficheros = [];

        foreach ([
            $raiz . '/app/Livewire',
            $raiz . '/app/Http/Controllers',
            $raiz . '/resources/views/livewire',
        ] as $carpeta) {
            $iterador = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($carpeta, \FilesystemIterator::SKIP_DOTS)
            );

            foreach ($iterador as $fichero) {
                if ($fichero->isFile() && in_array($fichero->getExtension(), ['php'], true)) {
                    $ficheros[] = $fichero->getPathname();
                }
            }
        }

        return $ficheros;
    }

    public function test_ninguna_regla_lleva_el_tope_escrito(): void
    {
        // Los cinco números que estaban repartidos. Si aparece cualquiera de ellos junto a
        // un `max:`, es un tope a mano.
        $sospechosos = ['max:5120', 'max:10240', 'max:51200', 'max:2048', 'max:1024'];

        $encontrados = [];

        foreach ($this->ficherosQueMirar() as $fichero) {
            $contenido = file_get_contents($fichero);

            foreach ($sospechosos as $sospechoso) {
                if (str_contains($contenido, $sospechoso)) {
                    $encontrados[] = str_replace(base_path() . DIRECTORY_SEPARATOR, '', $fichero)
                        . ' → ' . $sospechoso;
                }
            }
        }

        $this->assertSame([], $encontrados,
            "hay topes de subida escritos a mano; tienen que salir de `LimiteDeSubida`:\n  "
            . implode("\n  ", $encontrados));
    }

    public function test_ningun_texto_de_ayuda_anuncia_un_tope_fijo(): void
    {
        // «Máximo 5MB» en la ayuda del campo es la mitad del bug: el usuario lee ese
        // número, no la regla.
        $sospechosos = ['Máximo 5MB', 'Máximo 10MB', 'máximo 10MB', 'máximo: 50MB', 'Máximo 5 MB'];

        $encontrados = [];

        foreach ($this->ficherosQueMirar() as $fichero) {
            $contenido = file_get_contents($fichero);

            foreach ($sospechosos as $sospechoso) {
                if (str_contains($contenido, $sospechoso)) {
                    $encontrados[] = str_replace(base_path() . DIRECTORY_SEPARATOR, '', $fichero)
                        . ' → «' . $sospechoso . '»';
                }
            }
        }

        $this->assertSame([], $encontrados,
            "hay textos de ayuda con el tope escrito a mano:\n  " . implode("\n  ", $encontrados));
    }

    public function test_cada_tipo_de_fichero_tiene_su_constante(): void
    {
        // Que existan y sean distintas: un PDF no es una miniatura y un ZIP de SCSS-CDN no
        // es un PDF. Si alguien las igualara «para simplificar», el ZIP dejaría de caber.
        $this->assertSame(5120, LimiteDeSubida::IMAGENES_KB);
        $this->assertSame(10240, LimiteDeSubida::DOCUMENTOS_KB);
        $this->assertSame(10240, LimiteDeSubida::YAML_KB);
        $this->assertSame(51200, LimiteDeSubida::ZIP_KB);
    }

    public function test_el_zip_pasa_por_el_calculo_y_no_por_la_constante(): void
    {
        // **El caso de la constante sin usar.** `ZIP_KB` estaba definida y la regla del ZIP
        // llevaba el 51200 a pelo, así que con un PHP de 8 MB la regla decía que aceptaba
        // 50 y el fichero no llegaba nunca.
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '8M', 'post_max_size' => '8M']);

        // 8 MB menos los 512 KB de margen del `post_max_size`: el cuerpo de la petición
        // lleva más que el archivo.
        $this->assertSame(8192 - 512, LimiteDeSubida::enKb(LimiteDeSubida::ZIP_KB));
        $this->assertTrue(LimiteDeSubida::loRecortaElServidor(LimiteDeSubida::ZIP_KB));
    }

    public function test_el_formulario_de_una_feature_anuncia_el_tope_real(): void
    {
        // **La pantalla que dio pie a esto**: si el PHP deja 2 MB, no puede decir 5.
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '2M', 'post_max_size' => '2G']);

        $vista = $this->blade('<x-limite-de-subida formatos="jpg, png, webp" />');

        $vista->assertSee('Máximo 2 MB', escape: false);
        $vista->assertDontSee('Máximo 5 MB', escape: false);
        $vista->assertSee('Ese tope lo impone el servidor', escape: false);
        // Y se dice cuánto pediría el formulario, para que se vea el hueco.
        $vista->assertSee('admitiría hasta', escape: false);
    }

    public function test_sin_recorte_el_aviso_no_sale(): void
    {
        // No se avisa por avisar: cuando el servidor cumple, la frase sobra.
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '2G', 'post_max_size' => '2G']);

        $vista = $this->blade('<x-limite-de-subida />');

        $vista->assertSee('Máximo 5 MB', escape: false);
        $vista->assertDontSee('lo impone el servidor', escape: false);
    }

    public function test_el_componente_dice_el_tope_de_cada_tipo(): void
    {
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '2G', 'post_max_size' => '2G']);

        $this->blade('<x-limite-de-subida :kb="\App\Support\LimiteDeSubida::DOCUMENTOS_KB" />')
            ->assertSee('Máximo 10 MB', escape: false);

        $this->blade('<x-limite-de-subida :kb="\App\Support\LimiteDeSubida::ZIP_KB" />')
            ->assertSee('Máximo 50 MB', escape: false);
    }
}
