<?php

namespace Tests\Feature\Api;

use App\Models\Clients\Client;
use App\Models\Environments\Data;
use App\Models\Environments\Environment;
use App\Models\Environments\EnvironmentStat;
use App\Models\Products\LicenseToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

/**
 * La fecha de la telemetría diaria: `2026-09-04`, `2026-09-4` y `2026-9-4`.
 *
 * **Este test existe por un fallo que costó toda la telemetría de todos los sitios.** La
 * regla era `date_format:Y-m-d` y el plugin manda el día sin cero delante —`2026-09-4`—,
 * así que cada envío diario se iba con un **422 y el payload completo a la basura**: 38
 * claves con el perfil del sitio, los conteos y las medias. Por eso la tabla `data` estaba
 * medio vacía y no había ni una fila de histórico real.
 *
 * Y el detalle que lo hace peor: `DataAction::fechaDeLaFoto()` **ya estaba escrito para
 * sobrevivir a una fecha ilegible** —«no es motivo para perder la foto del día»—, pero la
 * validación cortaba antes de llegar ahí. Dos decisiones opuestas sobre lo mismo, y ganó
 * la que tiraba los datos.
 *
 * Ver MGR-086 y la norma de retrocompatibilidad de la release: **rechazar la telemetría de
 * un cliente por un cero de relleno no es validar, es perder datos.**
 */
class FechaDeLaTelemetriaTest extends TestCase
{
    use RefreshDatabase;

    private const TOKEN = '3IP-TEST-FECHA-001';
    private const DOMAIN = 'https://fecha.test';

    private Environment $entorno;

    protected function setUp(): void
    {
        parent::setUp();

        $cliente = Client::create([
            'name' => 'Cliente de la fecha',
            'shortname' => 'fecha',
            'actived' => true,
        ]);

        $token = LicenseToken::create([
            'client_id' => $cliente->id,
            'name' => 'Licencia de la fecha',
            'token' => self::TOKEN,
            'active' => true,
        ]);

        $this->entorno = Environment::create([
            'name' => 'Entorno de la fecha',
            'domain' => self::DOMAIN,
            'version' => '5.1.1',
            'env' => 'local',
            'client_id' => $cliente->id,
            'license_token_id' => $token->id,
            'active' => true,
        ]);
    }

    /** Un envío de la tarea diaria, con la fecha tal como la manda el plugin. */
    private function enviar(string $fecha): \Illuminate\Testing\TestResponse
    {
        return $this->withHeaders([
            'Authorization' => 'Bearer ' . self::TOKEN,
            'Accept' => 'application/json',
        ])->postJson('/api/v1', [
            'action' => 'data',
            'host' => self::DOMAIN,
            'plugin' => 'local_tresipunt',
            'version' => '2026090301',
            'date' => $fecha,
            'site' => [
                'version' => '2025100601.03',
                'release' => '5.1.1+ (Build: 20251219)',
                'type' => 'moodle',
                'env' => 'local',
            ],
            // Un puñado del payload real, con lo que el plugin sí manda.
            'data' => [
                'users' => 36,
                'courses' => 38,
                'enrolments' => 82,
                'language' => 'es',
                'countrycode' => 'ES',
                'policyagreed' => 0,
                'moodlerelease' => '5.1.1+ (Build: 20251219)',
            ],
        ]);
    }

    /**
     * @return list<array{string}>
     */
    public static function formasDeLaFecha(): array
    {
        return [
            'con los dos ceros' => ['2026-09-04'],
            'sin el cero del día' => ['2026-09-4'],
            'sin ningún cero' => ['2026-9-4'],
            'sin el cero del mes' => ['2026-9-04'],
        ];
    }

    /**
     * @dataProvider formasDeLaFecha
     */
    public function test_las_cuatro_formas_de_escribir_la_fecha_se_aceptan(string $fecha): void
    {
        $this->enviar($fecha)->assertOk();

        // Y la foto queda en el día que dice el envío, normalizado.
        $this->assertDatabaseHas('environment_stats', [
            'environment_id' => $this->entorno->id,
        ]);

        $foto = EnvironmentStat::where('environment_id', $this->entorno->id)->firstOrFail();

        $this->assertSame('2026-09-04', $foto->date->format('Y-m-d'),
            "la fecha «{$fecha}» tiene que guardarse como 2026-09-04");
    }

    public function test_el_payload_se_guarda_entero_y_no_solo_los_conteos(): void
    {
        // **Es lo que se perdía con el 422**: el perfil del sitio viaja en el mismo envío
        // que los conteos, así que rechazar el payload por la fecha se llevaba las dos
        // cosas.
        $this->enviar('2026-09-4')->assertOk();

        $datos = Data::where('environment_id', $this->entorno->id)->firstOrFail();

        $this->assertSame(36, (int) $datos->users);
        $this->assertSame('es', $datos->language);
        $this->assertSame('ES', $datos->countrycode);

        // Y el payload crudo, completo, en el histórico: es la única fuente de qué llegó.
        $foto = EnvironmentStat::where('environment_id', $this->entorno->id)->firstOrFail();

        $this->assertArrayHasKey('language', $foto->data);
        $this->assertArrayHasKey('policyagreed', $foto->data);
    }

    public function test_un_campo_que_viene_vacio_borra_el_anterior(): void
    {
        // **El caso que preguntó el usuario**: la ficha enseñaba un correo de contacto de
        // hacía cuatro meses como si fuera el de hoy. `mapDataFields()` usaba `isset()`,
        // que descarta los nulos, así que un campo que el sitio manda vacío no
        // sobrescribía nada.
        //
        // Y venir vacío **es una respuesta**: el sitio dice que no tiene ese dato.
        $this->enviarConDatos(['contactemail' => 'antiguo@cliente.test', 'users' => 10]);

        $datos = Data::where('environment_id', $this->entorno->id)->firstOrFail();
        $this->assertSame('antiguo@cliente.test', $datos->contactemail);

        // Segundo envío: el sitio ya no tiene correo de contacto.
        $this->enviarConDatos(['contactemail' => '', 'users' => 11]);

        $datos->refresh();

        // **Se guarda como `null` y no como `""`**: Laravel trae
        // `ConvertEmptyStringsToNull` en la pila de middleware por defecto, así que el
        // `""` que manda el plugin llega aquí ya convertido. Da igual para el caso —lo
        // que importa es que el valor viejo desaparece— pero explica por qué el payload
        // guardado en `environment_stats` enseña NULL donde el plugin puso una cadena
        // vacía.
        $this->assertNull($datos->contactemail, 'un vacío tiene que borrar el valor viejo');
        $this->assertSame(11, (int) $datos->users);
    }

    public function test_un_campo_nulo_tambien_borra(): void
    {
        // `null` es la otra forma de decir «no tengo eso», y es la que manda el plugin.
        $this->enviarConDatos(['contactname' => 'Marta Vidal', 'users' => 10]);
        $this->enviarConDatos(['contactname' => null, 'users' => 10]);

        $datos = Data::where('environment_id', $this->entorno->id)->firstOrFail();

        $this->assertNull($datos->contactname, 'un null tiene que borrar el valor viejo');
    }

    public function test_un_campo_que_no_viene_se_conserva(): void
    {
        // **El límite de la regla anterior, y es a propósito.** Que una clave no venga no
        // es el sitio diciendo «está vacío»: es una versión del plugin que no manda ese
        // campo. Borrar el dato bueno de un cliente porque su plugin es más antiguo sería
        // perder información por una bajada de versión —la misma decisión que el
        // `versiondb` en `SyncAction`—.
        //
        // Lo que evita que ese arrastre se enseñe como actual es la ficha, que solo
        // afirma lo que trae el último envío: dos defensas y no una.
        $this->enviarConDatos(['contactphone' => '934 210 587', 'users' => 10]);

        // Este envío no menciona `contactphone`.
        $this->enviarConDatos(['users' => 12]);

        $datos = Data::where('environment_id', $this->entorno->id)->firstOrFail();

        $this->assertSame('934 210 587', $datos->contactphone,
            'lo que no viene no se toca');
        $this->assertSame(12, (int) $datos->users);
    }

    /** Un envío de la tarea diaria con el bloque `data` que se le pase. */
    private function enviarConDatos(array $datos): \Illuminate\Testing\TestResponse
    {
        return $this->withHeaders([
            'Authorization' => 'Bearer ' . self::TOKEN,
            'Accept' => 'application/json',
        ])->postJson('/api/v1', [
            'action' => 'data',
            'host' => self::DOMAIN,
            'plugin' => 'local_tresipunt',
            'version' => '2026090400',
            'date' => now()->format('Y-m-d'),
            'site' => [
                'version' => '2025100601.03',
                'release' => '5.1.1+ (Build: 20251219)',
                'type' => 'moodle',
                'env' => 'local',
            ],
            'data' => $datos,
        ]);
    }

    public function test_una_fecha_ilegible_no_pierde_la_foto(): void
    {
        // La regla sigue exigiendo que sea una fecha: lo que no hace es exigir el formato
        // exacto. Y si algún día llega algo no reconocible, `fechaDeLaFoto()` usa hoy en
        // lugar de perder el envío —eso ya estaba escrito y ahora sí se ejecuta—.
        $this->enviar('no-es-una-fecha')->assertStatus(422);

        // Sin fecha ninguna, en cambio, sí se acepta: `date` es nullable.
        $this->withHeaders([
            'Authorization' => 'Bearer ' . self::TOKEN,
            'Accept' => 'application/json',
        ])->postJson('/api/v1', [
            'action' => 'data',
            'host' => self::DOMAIN,
            'plugin' => 'local_tresipunt',
            'version' => '2026090301',
            'site' => ['version' => '2025100601.03', 'release' => '5.1.1', 'type' => 'moodle', 'env' => 'local'],
            'data' => ['users' => 10],
        ])->assertOk();

        $foto = EnvironmentStat::where('environment_id', $this->entorno->id)->firstOrFail();

        $this->assertSame(now()->format('Y-m-d'), $foto->date->format('Y-m-d'),
            'sin fecha, la foto es de hoy');
    }
}
