<?php

namespace Tests\Feature\Products;

use App\Livewire\Products\CambioDeEstadoModal;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Environments\Plugin;
use App\Models\Products\LicenseToken;
use App\Models\Products\Product;
use App\Models\Setups\Setup;
use App\Support\ImpactoDePublicarVersion;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Publicar una versión también es un cambio en producción.
 *
 * **Por qué hacía falta.** Durante un rato la modal no calculaba nada al publicar, con el
 * argumento de que nadie pierde contenido. Es verdad y es irrelevante: a los entornos que
 * venían recibiendo la versión anterior **les cambia el contenido igual**, en su siguiente
 * petición y sin haber pedido nada.
 *
 * Y hay un caso que **no se ve en ninguna otra pantalla**: publicar una versión cuyo número
 * está por encima del plugin de todos los sitios. Se queda publicada y no le llega a nadie.
 * No falla nada, no avisa nada, y la lista la enseña igual que a las demás.
 */
class ImpactoDePublicarTest extends TestCase
{
    use RefreshDatabase;

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

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

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

        return $usuario;
    }

    private function producto(): Product
    {
        return Product::create(['name' => 'Tema', 'slug' => 'theme_pub', 'shortname' => 'pub']);
    }

    /** Un entorno con licencia del producto y el plugin instalado en la versión dada. */
    private function cliente(Product $producto, string $nombre, ?string $instalada): Environment
    {
        $cliente = Client::firstOrCreate(
            ['shortname' => 'pub'],
            ['name' => 'Cliente Publicar', 'actived' => true]
        );

        $token = LicenseToken::create([
            'token' => '3IP-PUBL-' . str_pad((string) (LicenseToken::count() + 1), 4, '0', STR_PAD_LEFT) . '-PUBL-PUBL',
            'client_id' => $cliente->id,
            'active' => true,
        ]);
        $token->products()->attach($producto->id);

        $entorno = Environment::create([
            'name' => $nombre,
            'domain' => 'https://' . str()->slug($nombre) . '.pub.test',
            'env' => 'pro',
            'version' => '5.1.1',
            'client_id' => $cliente->id,
            'license_token_id' => $token->id,
            'active' => true,
            'refresh_at' => now()->subHour(),
        ]);

        if ($instalada !== null) {
            Plugin::create([
                'environment_id' => $entorno->id,
                'name' => $producto->name,
                'component' => $producto->slug,
                'type' => explode('_', $producto->slug)[0],
                'versiondisk' => $instalada,
                'versiondb' => $instalada,
                'versionrequires' => '2024100700',
                'release' => '1.0.0',
                'has_updates' => false,
                'has_dependencies' => false,
            ]);
        }

        return $entorno;
    }

    private function unSetup(Product $producto, string $version, string $estado): Setup
    {
        return Setup::create([
            'product_id' => $producto->id,
            'version' => $version,
            'yaml' => "a: 1\n",
            'status' => $estado,
            'created_by' => auth()->id(),
            'updated_by' => auth()->id(),
        ]);
    }

    private function modal(Product $producto, Setup $setup, string $estado = 'active')
    {
        return Livewire::test(CambioDeEstadoModal::class, [
            'tipo' => 'setups',
            'productId' => $producto->id,
            'versionId' => $setup->id,
            'estado' => $estado,
        ]);
    }

    /* ===================== El cálculo ===================== */

    public function test_publicar_cuenta_quien_pasa_a_recibirla_y_desde_que_version(): void
    {
        $this->admin();
        $producto = $this->producto();

        $vieja = $this->unSetup($producto, '2026010100', 'active');
        $nueva = $this->unSetup($producto, '2026060100', 'draft');

        // Su plugin alcanza las dos, así que ahora recibe la vieja y pasaría a la nueva.
        $this->cliente($producto, 'Alcanza', '2026070100');

        $impacto = ImpactoDePublicarVersion::de($producto, $nueva, Setup::class);

        $this->assertCount(1, $impacto['afectados']);
        $this->assertSame('2026010100', $impacto['afectados'][0]['veniaDe']);
        $this->assertFalse($impacto['noLaRecibeNadie']);
        $this->assertSame(0, $impacto['sinContenidoAntes']);

        // Y la vieja sigue existiendo: publicar no borra nada.
        $this->assertSame('active', $vieja->fresh()->status);
    }

    public function test_quien_no_recibia_nada_se_cuenta_aparte(): void
    {
        // Es la buena noticia de publicar, y merece decirse: hasta ahora la API le
        // respondía «sin contenido», que no es un error y por tanto no avisaba de nada.
        $this->admin();
        $producto = $this->producto();

        $primera = $this->unSetup($producto, '2026010100', 'draft');
        $this->cliente($producto, 'Sinnada', '2026050100');

        $impacto = ImpactoDePublicarVersion::de($producto, $primera, Setup::class);

        $this->assertSame(1, $impacto['sinContenidoAntes']);
        $this->assertNull($impacto['afectados'][0]['veniaDe']);
        $this->assertTrue($impacto['eraLaPrimera']);
    }

    public function test_una_version_por_encima_del_plugin_de_todos_no_le_llega_a_nadie(): void
    {
        // **El caso silencioso.** Se publica, se ve publicada, y no sale por la API a
        // ningún sitio hasta que alguien actualice su plugin, que puede ser nunca.
        $this->admin();
        $producto = $this->producto();

        $alta = $this->unSetup($producto, '2027010100', 'draft');
        $this->cliente($producto, 'Viejo', '2026050100');

        $impacto = ImpactoDePublicarVersion::de($producto, $alta, Setup::class);

        $this->assertTrue($impacto['noLaRecibeNadie']);
        $this->assertSame(1, $impacto['entornosConPlugin']);
    }

    public function test_un_entorno_sin_el_plugin_no_cuenta(): void
    {
        // No pide contenido de este producto: meterlo en la lista solo la llenaría de
        // entornos que no tienen que ver con la decisión.
        $this->admin();
        $producto = $this->producto();

        $version = $this->unSetup($producto, '2026010100', 'draft');
        $this->cliente($producto, 'Sinplugin', null);

        $impacto = ImpactoDePublicarVersion::de($producto, $version, Setup::class);

        $this->assertSame(0, $impacto['entornosConPlugin']);
        $this->assertTrue($impacto['noLaRecibeNadie']);
    }

    public function test_una_publicada_mas_alta_le_gana_y_no_cambia_nada(): void
    {
        // Publicar una versión intermedia no siempre mueve a alguien: si ya hay una
        // publicada por encima que el plugin también alcanza, el cliente sigue igual.
        $this->admin();
        $producto = $this->producto();

        $this->unSetup($producto, '2026090100', 'active');
        $media = $this->unSetup($producto, '2026050100', 'draft');

        $this->cliente($producto, 'Alto', '2026120100');

        $impacto = ImpactoDePublicarVersion::de($producto, $media, Setup::class);

        $this->assertTrue($impacto['noLaRecibeNadie']);
    }

    /* ===================== La pantalla ===================== */

    public function test_la_modal_lo_dice_antes_de_publicar(): void
    {
        $this->admin();
        $producto = $this->producto();

        $this->unSetup($producto, '2026010100', 'active');
        $nueva = $this->unSetup($producto, '2026060100', 'draft');
        $this->cliente($producto, 'Alcanza', '2026070100');

        $this->modal($producto, $nueva)
            ->assertSee('1 entorno pasará a recibirla')
            ->assertSee('plugin')
            ->assertSee('Alcanza')
            ->assertSee('2026010100')
            ->assertSee('Publicar y mover');
    }

    public function test_la_modal_avisa_de_que_no_le_llegaria_a_nadie(): void
    {
        $this->admin();
        $producto = $this->producto();

        $alta = $this->unSetup($producto, '2027010100', 'draft');
        $this->cliente($producto, 'Viejo', '2026050100');

        $this->modal($producto, $alta)
            ->assertSee('Publicar de todos modos')
            ->assertSee('ahora mismo no le')
            ->assertSee('Publicar de todos modos');
    }

    public function test_publicar_desde_la_modal_deja_la_version_publicada(): void
    {
        $this->admin();
        $producto = $this->producto();

        $version = $this->unSetup($producto, '2026010100', 'draft');

        $this->modal($producto, $version)->call('cambiar');

        $this->assertSame('active', $version->fresh()->status);
    }

    /* ===================== El reparto ===================== */

    public function test_una_version_en_borrador_no_cuenta_como_recibida(): void
    {
        // Es lo que se rompió al añadir los estados: el reparto se calculaba sobre todas
        // las filas, así que un borrador más alto que la publicada se llevaba el recuento
        // de «la reciben N» y la pantalla decía que le estaba llegando a gente.
        $this->admin();
        $producto = $this->producto();

        $this->unSetup($producto, '2026010100', 'active');
        $this->unSetup($producto, '2026060100', 'draft');
        $this->cliente($producto, 'Alcanza', '2026070100');

        $reparto = \App\Support\RepartoDeVersiones::reparto(
            $producto,
            Setup::where('product_id', $producto->id)->active()->pluck('version')
        );

        $this->assertSame(1, $reparto['porVersion']['2026010100']);
        $this->assertArrayNotHasKey('2026060100', $reparto['porVersion']);
    }
}
