<?php

namespace Tests\Feature\Features;

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

/**
 * El límite de subida que se anuncia es el que se aplica (PRODSECU-156).
 *
 * **El bug era una incoherencia entre capas, no un número mal calculado.** El formulario
 * decía «5 MB» porque el 5120 estaba escrito a mano en el mensaje, y ese es el tope de la
 * **última de cuatro** capas: delante están el servidor web, el PHP de la máquina y
 * Livewire. De ahí el ticket: *«el aviso dice 5MB pero al subir una de 3 no se pudo
 * subir»*.
 *
 * Lo que se prueba es que el número **se calcula** con las tres capas que PHP puede ver, y
 * que dice la verdad también cuando la configuración de la máquina es más estricta que la
 * nuestra — que es el caso que dio el ticket y el que no se podía probar antes, porque
 * `upload_max_filesize` es `PHP_INI_PERDIR` y `ini_set()` no lo toca. De ahí
 * `LimiteDeSubida::fingirIni()`.
 */
class LimiteDeSubidaTest extends TestCase
{
    protected function tearDown(): void
    {
        // Es estado estático: sin esto, un test contagia al siguiente.
        LimiteDeSubida::dejarDeFingir();

        parent::tearDown();
    }

    public function test_sin_restriccion_del_servidor_manda_el_tope_del_formulario(): void
    {
        // El caso normal: el PHP de la máquina es generoso, así que el límite es el nuestro
        // y el aviso dice 5 MB, como siempre.
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '2G', 'post_max_size' => '2G']);

        $this->assertSame(LimiteDeSubida::IMAGENES_KB, LimiteDeSubida::enKb());
        $this->assertSame('5 MB', LimiteDeSubida::comoTexto());
        $this->assertFalse(LimiteDeSubida::loRecortaElServidor());
    }

    public function test_si_el_php_es_mas_estricto_gana_el_php(): void
    {
        // **El caso del ticket, exacto.** Con `upload_max_filesize` en 2M, prometer 5 MB es
        // mentira: una imagen de 3 MB no se puede subir por mucho que la regla la acepte.
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '2M', 'post_max_size' => '2G']);

        $this->assertSame(2048, LimiteDeSubida::enKb());
        $this->assertSame('2 MB', LimiteDeSubida::comoTexto());
        $this->assertTrue(LimiteDeSubida::loRecortaElServidor(),
            'hay que poder decir que el tope no lo pone el panel');
    }

    public function test_al_post_max_size_se_le_deja_margen(): void
    {
        // El cuerpo de la petición lleva más que el fichero —los demás campos, las
        // cabeceras y el sobre multipart—, así que con el tope justo un fichero de
        // exactamente ese tamaño se rechaza.
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '2G', 'post_max_size' => '3M']);

        $this->assertSame(3072 - 512, LimiteDeSubida::enKb());
    }

    public function test_manda_el_mas_estricto_de_los_dos_ajustes(): void
    {
        // No basta con mirar uno: el que corta puede ser cualquiera de los dos.
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '4M', 'post_max_size' => '10M']);
        $this->assertSame(4096, LimiteDeSubida::enKb());

        LimiteDeSubida::fingirIni(['upload_max_filesize' => '10M', 'post_max_size' => '4M']);
        $this->assertSame(4096 - 512, LimiteDeSubida::enKb());
    }

    public function test_un_php_sin_limite_no_deja_el_tope_en_cero(): void
    {
        // `0` en estos dos ajustes significa **sin límite**, no «cero bytes». Tratarlo
        // como un tope dejaría el límite efectivo en nada y no se podría subir nada: el
        // formulario diría «la imagen no puede superar 1 KB».
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '0', 'post_max_size' => '']);

        $this->assertSame(LimiteDeSubida::IMAGENES_KB, LimiteDeSubida::enKb());
        $this->assertFalse(LimiteDeSubida::loRecortaElServidor());
    }

    /**
     * @dataProvider tamanos
     */
    public function test_el_texto_se_lee_como_se_habla(string $ini, string $esperado): void
    {
        LimiteDeSubida::fingirIni(['upload_max_filesize' => $ini, 'post_max_size' => '2G']);

        $this->assertSame($esperado, LimiteDeSubida::comoTexto());
    }

    /** @return array<string, array{string, string}> */
    public static function tamanos(): array
    {
        return [
            'menos de un mega va en KB' => ['800K', '800 KB'],
            'redondo, sin decimales' => ['2M', '2 MB'],
            'con decimal, y con coma' => ['1536K', '1,5 MB'],
            'bytes sueltos, sin unidad' => ['1048576', '1 MB'],
        ];
    }

    /**
     * @dataProvider tamanosGrandes
     */
    public function test_a_partir_de_mil_megas_se_dice_en_gb(int $kb, string $esperado): void
    {
        // **Esto no es cosmética, es el mismo bug otra vez.** En español el punto es
        // separador de millar, así que `2.048 MB` —que son 2 GB— se lee como «2 MB»: el
        // número dice justo lo contrario de lo que vale. Y salió en la pantalla de
        // Configuraciones, que existe **para aclarar los límites**: ahí un número ambiguo
        // es peor que no poner nada, porque el `upload_max_filesize` de la máquina parecía
        // ser el que estaba recortando el formulario.
        $this->assertSame($esperado, LimiteDeSubida::formatear($kb));
    }

    /** @return array<string, array{int, string}> */
    public static function tamanosGrandes(): array
    {
        return [
            'justo por debajo sigue en MB' => [1023 * 1024, '1023 MB'],
            'mil veinticuatro megas es un giga' => [1024 * 1024, '1 GB'],
            'el caso real de la maquina' => [2 * 1024 * 1024, '2 GB'],
            'y con decimal' => [(int) (1.5 * 1024 * 1024), '1,5 GB'],
        ];
    }

    public function test_el_formateador_no_lleva_separador_de_millar(): void
    {
        // La regla que evita que vuelva: **ningún tamaño lleva punto de millar**. Con la
        // escala en GB ya no debería haber números de cuatro cifras, y si alguno se cuela,
        // que no se pueda confundir con un decimal.
        foreach ([512, 1024, 5120, 51200, 1048576, 2097152] as $kb) {
            $this->assertStringNotContainsString('.', LimiteDeSubida::formatear($kb),
                "«" . LimiteDeSubida::formatear($kb) . "» lleva un punto: se lee como un decimal");
        }
    }

    public function test_el_diagnostico_dice_que_lo_de_nginx_no_se_puede_saber(): void
    {
        // **`null` ahí es la respuesta correcta, no un hueco.** `client_max_body_size` no es
        // medible desde PHP: cuando nginx corta, PHP no se ejecuta. Poner un número
        // inventado sería peor que decir que no se sabe — misma lección que MGR-076 y
        // MGR-017.
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '8M', 'post_max_size' => '8M']);

        $diagnostico = LimiteDeSubida::diagnostico();

        $this->assertArrayHasKey('client_max_body_size_kb', $diagnostico);
        $this->assertNull($diagnostico['client_max_body_size_kb']);

        $this->assertSame(LimiteDeSubida::IMAGENES_KB, $diagnostico['validacion_kb']);
        $this->assertSame(8192, $diagnostico['upload_max_filesize_kb']);
        $this->assertSame(8192, $diagnostico['post_max_size_kb']);
        $this->assertSame(LimiteDeSubida::enKb(), $diagnostico['efectivo_kb']);
    }

    public function test_el_diagnostico_avisa_cuando_el_servidor_recorta(): void
    {
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '1M', 'post_max_size' => '1M']);

        $this->assertTrue(LimiteDeSubida::diagnostico()['lo_recorta_el_servidor']);
    }

    public function test_el_zip_del_scss_cdn_tiene_su_propio_orden_de_magnitud(): void
    {
        // 50 MB, no 5: un bundle de SCSS-CDN no es una miniatura. Se calcula igual pero
        // parte de otro tope, y con un PHP normal es él quien manda.
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '2G', 'post_max_size' => '2G']);

        $this->assertSame(LimiteDeSubida::ZIP_KB, LimiteDeSubida::enKb(LimiteDeSubida::ZIP_KB));
        $this->assertSame('50 MB', LimiteDeSubida::comoTexto(LimiteDeSubida::ZIP_KB));

        // Y con un PHP de 8 MB, un ZIP de 50 no cabe: hay que decirlo.
        LimiteDeSubida::fingirIni(['upload_max_filesize' => '8M', 'post_max_size' => '8M']);

        $this->assertTrue(LimiteDeSubida::loRecortaElServidor(LimiteDeSubida::ZIP_KB));
    }

    public function test_sin_fingir_lee_el_php_de_verdad(): void
    {
        // Que el camino de producción sigue siendo `ini_get()`: si `fingirIni()` se
        // quedara pegado, el panel anunciaría el límite de un test.
        LimiteDeSubida::dejarDeFingir();

        $this->assertSame(
            LimiteDeSubida::diagnostico()['upload_max_filesize_kb'] !== null,
            trim((string) ini_get('upload_max_filesize')) !== '' && ini_get('upload_max_filesize') !== '0'
        );
    }
}
