<?php

namespace Tests\Feature\Products;

use App\Livewire\Products\DuplicarVersionModal;
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 Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Duplicar una versión: el número se decide antes, no después.
 *
 * **Lo que arregla.** Era un `confirm()` del navegador y el número de la versión nueva se
 * generaba solo sumando uno, sin verlo. El número no es una etiqueta: la API sirve la versión
 * más alta que no supere el plugin instalado en cada sitio, así que **el número es lo que
 * determina a quién le llegará**. Con el número por encima del plugin de toda la flota, la
 * versión se publica y no le llega a nadie, sin que falle ni avise nada.
 */
class DuplicarVersionTest extends TestCase
{
    use RefreshDatabase;

    private function admin(array $permisos = ['admin.setups.index', 'admin.setups.edit', 'admin.setups.create']): User
    {
        $rol = Role::firstOrCreate(['name' => 'Admin dup', 'guard_name' => 'web']);

        foreach ($permisos 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(string $slug = 'theme_dup'): Product
    {
        return Product::create(['name' => 'Tema ' . $slug, 'slug' => $slug, 'shortname' => substr($slug, -3)]);
    }

    private function unSetup(Product $producto, string $version, string $estado = 'active'): 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 cliente(Product $producto, string $nombre, ?string $instalada): Environment
    {
        $cliente = Client::firstOrCreate(
            ['shortname' => 'dup'],
            ['name' => 'Cliente Duplicar', 'actived' => true]
        );

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

        $entorno = Environment::create([
            'name' => $nombre,
            'domain' => 'https://' . str()->slug($nombre) . '.dup.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 modal(Product $producto, Setup $setup)
    {
        return Livewire::test(DuplicarVersionModal::class, [
            'tipo' => 'setups',
            'productId' => $producto->id,
            'versionId' => $setup->id,
        ]);
    }

    public function test_propone_el_siguiente_numero_y_se_ve(): void
    {
        $this->admin();
        $producto = $this->producto();
        $setup = $this->unSetup($producto, '2026010100');

        $this->modal($producto, $setup)->assertSet('numero', '2026010101');
    }

    public function test_el_numero_se_puede_cambiar_y_es_el_que_se_crea(): void
    {
        // Es la razón de que esta modal exista: el número decide a quién le llegará, así
        // que tiene que poder ponerse a mano.
        $this->admin();
        $producto = $this->producto();
        $setup = $this->unSetup($producto, '2026010100');

        $this->modal($producto, $setup)
            ->set('numero', '2025120100')
            ->call('duplicar');

        $this->assertTrue(
            Setup::where('product_id', $producto->id)->where('version', '2025120100')->exists(),
            'Debía crearse con el número escrito.'
        );
    }

    public function test_el_duplicado_nace_sin_publicar(): void
    {
        // Se duplica para preparar la siguiente, no para soltarla a medias: si naciera
        // publicada le llegaría a los clientes a medio escribir.
        $this->admin();
        $producto = $this->producto();
        $setup = $this->unSetup($producto, '2026010100');

        $this->modal($producto, $setup)->call('duplicar');

        $nueva = Setup::where('product_id', $producto->id)->where('version', '2026010101')->first();

        $this->assertNotNull($nueva);
        $this->assertSame('draft', $nueva->status);
    }

    public function test_un_numero_que_ya_existe_no_crea_nada(): void
    {
        $this->admin();
        $producto = $this->producto();
        $setup = $this->unSetup($producto, '2026010100');
        $this->unSetup($producto, '2026050100');

        $this->modal($producto, $setup)
            ->set('numero', '2026050100')
            ->assertSee('Ya existe una versión')
            ->call('duplicar');

        $this->assertSame(2, Setup::where('product_id', $producto->id)->count());
    }

    public function test_un_numero_que_no_son_diez_digitos_no_crea_nada(): void
    {
        $this->admin();
        $producto = $this->producto();
        $setup = $this->unSetup($producto, '2026010100');

        $this->modal($producto, $setup)
            ->set('numero', '2026')
            ->call('duplicar')
            ->assertHasErrors('numero');

        $this->assertSame(1, Setup::where('product_id', $producto->id)->count());
    }

    public function test_avisa_cuando_el_numero_no_alcanza_a_ningun_entorno(): void
    {
        // **El caso silencioso**, y el único sitio donde se dice antes de crear nada.
        $this->admin();
        $producto = $this->producto();
        $setup = $this->unSetup($producto, '2027010100');
        $this->cliente($producto, 'Viejo', '2026050100');

        $this->modal($producto, $setup)->assertSee('no le llegaría a ningún entorno');
    }

    public function test_dice_a_cuantos_podria_llegarles(): void
    {
        $this->admin();
        $producto = $this->producto();
        $setup = $this->unSetup($producto, '2026010100');
        $this->cliente($producto, 'Alcanza', '2026070100');

        $this->modal($producto, $setup)->assertSee('al publicarla podría llegarle a');
    }

    public function test_sin_permiso_no_se_abre(): void
    {
        // El gate de la ruta no se reaplica en /livewire/update (MGR-005), y esto crea una
        // fila nueva.
        $this->admin(['admin.setups.index']);
        $producto = $this->producto();
        $setup = $this->unSetup($producto, '2026010100');

        $this->modal($producto, $setup)->assertForbidden();
    }

    public function test_no_se_puede_duplicar_la_version_de_otro_producto(): void
    {
        // MGR-006: el id viaja por el navegador, y el duplicado se creaba dentro del otro
        // producto.
        $this->admin();
        $mio = $this->producto('theme_mio');
        $ajeno = $this->producto('theme_aje');
        $suyo = $this->unSetup($ajeno, '2026010100');

        try {
            $this->modal($mio, $suyo);
            $this->fail('Se esperaba que el setup de otro producto no se encontrara.');
        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException) {
            // Es el resultado correcto.
        }

        $this->assertSame(1, Setup::where('product_id', $ajeno->id)->count());
        $this->assertSame(0, Setup::where('product_id', $mio->id)->count());
    }
}
