<?php

namespace Tests\Unit;

use App\Services\Api\Severidad;
use PHPUnit\Framework\TestCase;

/**
 * La clasificación de las respuestas de la API en severidades.
 *
 * **Es la norma del proyecto hecha código** (`norma-cero-errores.md`): el estado normal
 * de la API es cero errores, así que lo que no se puede arreglar no puede contar como
 * error. Cada caso de aquí sale de un error real del log, no de un supuesto.
 */
class SeveridadTest extends TestCase
{
    /* ===================== Lo que sí es un error ===================== */

    public function test_un_token_que_no_existe_es_un_error(): void
    {
        // Cinco de los catorce errores reales. Alguien llama con un token inventado:
        // hay que cortarlo o corregir la configuración de ese sitio.
        $this->assertSame(Severidad::ERROR, Severidad::de(401, 401, 'Token no válido'));
    }

    public function test_un_dominio_no_registrado_es_un_error(): void
    {
        // **Este fallaba en la primera versión del clasificador**: `2002` acaba en 2, y
        // la regla general dice que «acabar en 2» es «sin contenido». Pero la familia
        // `2xxx` no sigue esa convención: aquí el 2 es «entorno no encontrado», que es
        // un alta sin terminar y hay que arreglarla.
        $this->assertSame(
            Severidad::ERROR,
            Severidad::de(401, 2002, 'Environment not found for this host. Host searched: https://x.dev')
        );
    }

    public function test_un_campo_del_contrato_que_falta_es_un_error(): void
    {
        // El único de los cinco que apunta a un fallo de código en el plugin.
        $this->assertSame(
            Severidad::ERROR,
            Severidad::de(422, 400, 'El campo environment.version o site.version es obligatorio.')
        );
    }

    public function test_un_fichero_publicado_ilegible_es_un_error(): void
    {
        // `X003` con este mensaje es culpa nuestra: hemos publicado algo roto.
        $this->assertSame(
            Severidad::ERROR,
            Severidad::de(500, 3003, 'Invalid or unreadable setup configuration file')
        );
    }

    public function test_una_version_mal_formada_es_un_error(): void
    {
        // Mismo código que el anterior y **dueño distinto**: aquí es el plugin el que
        // manda mal la versión. Comparten `X003`, y eso es parte de lo que hay que
        // renumerar.
        $this->assertSame(
            Severidad::ERROR,
            Severidad::de(500, 4003, 'Invalid version format. Expected YYYYMMDDXX format (10 digits)')
        );
    }

    public function test_el_tope_de_peticiones_es_un_error(): void
    {
        // Algo está llamando de más: o el cliente está en bucle o el tope está bajo.
        // En los dos casos hay que mirarlo.
        $this->assertSame(Severidad::ERROR, Severidad::de(429, 429, 'Rate limit excedido'));
    }

    /* ===================== Lo que no hay ===================== */

    public function test_sin_version_compatible_no_es_un_error(): void
    {
        // Cinco de los catorce. Un producto sin features nunca va a tener features y su
        // plugin va a preguntar cada hora: contarlo como error es garantizar que el
        // porcentaje nunca baje de ahí.
        foreach ([3002, 4002, 5002] as $codigo) {
            $this->assertSame(
                Severidad::SIN_CONTENIDO,
                Severidad::de(404, $codigo, 'No compatible features version found for this product version'),
                'El código ' . $codigo . ' debería ser sin_contenido'
            );
        }
    }

    public function test_sin_ficheros_para_esa_version_tampoco(): void
    {
        $this->assertSame(
            Severidad::SIN_CONTENIDO,
            Severidad::de(404, 3002, 'No SCSS files found for this version')
        );
    }

    /* ===================== Lo que es una respuesta de negocio ===================== */

    public function test_una_licencia_que_no_incluye_el_plugin_es_negocio(): void
    {
        // Es **la respuesta correcta**, y el plugin la usa para enseñar la promoción
        // (MGR-033). Que cuente como error hace que el panel mienta.
        foreach ([2001, 3001, 4001, 5001] as $codigo) {
            $this->assertSame(
                Severidad::NEGOCIO,
                Severidad::de(401, $codigo, 'Licence does not include this plugin'),
                'El código ' . $codigo . ' debería ser negocio'
            );
        }
    }

    public function test_un_plugin_que_no_existe_no_es_negocio_sino_error(): void
    {
        // **Mismo código que el anterior, situación opuesta**: si el plugin pide un
        // producto que no está en el catálogo, hay un fallo en alguna parte. Hoy solo se
        // pueden distinguir por el texto, y eso es el argumento para partir el código.
        $this->assertSame(Severidad::ERROR, Severidad::de(401, 3001, 'Plugin not found'));
        $this->assertSame(Severidad::ERROR, Severidad::de(401, 2002, 'Plugin not found'));
    }

    public function test_un_contrato_terminado_es_negocio(): void
    {
        // Token inactivo, token expirado, cliente de baja, entorno inactivo: en los
        // cuatro casos el Manager está respondiendo bien y no hay nada que arreglar.
        $this->assertSame(Severidad::NEGOCIO, Severidad::de(403, 403, 'Cliente inactivo'));
        $this->assertSame(Severidad::NEGOCIO, Severidad::de(403, 403, 'Token expirado'));
    }

    /* ===================== Lo normal ===================== */

    public function test_una_respuesta_correcta_es_ok(): void
    {
        $this->assertSame(Severidad::OK, Severidad::de(200));
        $this->assertSame(Severidad::OK, Severidad::de(204, null, null));
        // Una redirección tampoco es un error.
        $this->assertSame(Severidad::OK, Severidad::de(304));
    }

    public function test_un_404_de_laravel_sin_codigo_propio_es_un_error(): void
    {
        // Sin código de familia no se puede saber más, y lo prudente es que llame la
        // atención: una ruta que no existe es algo que alguien está pidiendo mal.
        $this->assertSame(Severidad::ERROR, Severidad::de(404, 404, 'Recurso no encontrado'));
    }
}
