<?php

namespace Tests\Feature\LicenseTokens;

use App\Livewire\LicenseTokens\ManageEnvironments;
use App\Livewire\LicenseTokens\Show;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Products\LicenseToken;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * Regla: un entorno solo puede vincularse a un token de licencia de SU MISMO
 * cliente. Vincularlo a otro token se lo quita al anterior y deja ese sitio sin
 * licencia, así que el cruce entre clientes no se permite. Ver known-issues MGR-004.
 */
class AssignEnvironmentsTest extends TestCase
{
    use RefreshDatabase;

    private const PERMISSION = 'admin.license-tokens.manage-environments';

    private function actingAsUserWithPermission(): User
    {
        $user = User::factory()->create();

        // Este proyecto añade a la tabla `permissions` dos columnas propias, y
        // `model` es NOT NULL, así que Permission::findOrCreate() no sirve.
        Permission::firstOrCreate(
            ['name' => self::PERMISSION, 'guard_name' => 'web'],
            ['model' => 'license_tokens', 'description' => 'Tokens de Licencia: Gestionar entornos']
        );

        $user->givePermissionTo(self::PERMISSION);

        $this->actingAs($user);

        return $user;
    }

    private function makeClient(string $name): Client
    {
        return Client::create([
            'name' => $name,
            'shortname' => strtolower($name),
            'actived' => true,
        ]);
    }

    private function makeToken(Client $client): LicenseToken
    {
        return LicenseToken::create([
            'client_id' => $client->id,
            'name' => 'Licencia ' . $client->shortname,
            'token' => '3IP-TEST-' . strtoupper($client->shortname),
            'active' => true,
        ]);
    }

    private function makeEnvironment(Client $client, string $domain, ?LicenseToken $token = null): Environment
    {
        return Environment::create([
            'name' => 'Entorno ' . $domain,
            'domain' => $domain,
            'version' => '5.1.1',
            'env' => 'local',
            'client_id' => $client->id,
            'license_token_id' => $token?->id,
            'active' => true,
        ]);
    }

    public function test_no_se_puede_vincular_el_entorno_de_otro_cliente(): void
    {
        $this->actingAsUserWithPermission();

        $clientA = $this->makeClient('alpha');
        $clientB = $this->makeClient('beta');

        $tokenA = $this->makeToken($clientA);
        $tokenB = $this->makeToken($clientB);
        $environmentB = $this->makeEnvironment($clientB, 'https://beta.test', $tokenB);

        Livewire::test(ManageEnvironments::class, ['license_token' => $tokenA])
            ->set('selectedEnvironments', [$environmentB->id])
            ->call('save');

        // El entorno de B sigue con su token: no se lo ha llevado A
        $this->assertSame(
            $tokenB->id,
            $environmentB->fresh()->license_token_id,
            'El entorno del cliente B ha cambiado de token: se le ha revocado la licencia.'
        );
    }

    public function test_si_se_vincula_el_entorno_del_mismo_cliente(): void
    {
        $this->actingAsUserWithPermission();

        $client = $this->makeClient('gamma');
        $token = $this->makeToken($client);
        $environment = $this->makeEnvironment($client, 'https://gamma.test');

        Livewire::test(ManageEnvironments::class, ['license_token' => $token])
            ->set('selectedEnvironments', [$environment->id])
            ->call('save');

        $this->assertSame($token->id, $environment->fresh()->license_token_id);
    }

    public function test_la_lista_de_candidatos_solo_trae_entornos_del_cliente_del_token(): void
    {
        $this->actingAsUserWithPermission();

        $clientA = $this->makeClient('delta');
        $clientB = $this->makeClient('epsilon');

        $tokenA = $this->makeToken($clientA);
        $propio = $this->makeEnvironment($clientA, 'https://delta.test');
        $ajeno = $this->makeEnvironment($clientB, 'https://epsilon.test');

        Livewire::test(ManageEnvironments::class, ['license_token' => $tokenA])
            ->assertViewHas('environments', function ($environments) use ($propio, $ajeno) {
                $ids = $environments->pluck('id')->all();

                return in_array($propio->id, $ids) && !in_array($ajeno->id, $ids);
            });
    }

    public function test_la_pantalla_de_detalle_tambien_rechaza_el_cruce(): void
    {
        $this->actingAsUserWithPermission();

        $clientA = $this->makeClient('zeta');
        $clientB = $this->makeClient('eta');

        $tokenA = $this->makeToken($clientA);
        $tokenB = $this->makeToken($clientB);
        $environmentB = $this->makeEnvironment($clientB, 'https://eta.test', $tokenB);

        Livewire::test(Show::class, ['license_token' => $tokenA])
            ->call('saveEnvironments', [$environmentB->id]);

        $this->assertSame($tokenB->id, $environmentB->fresh()->license_token_id);
    }

    public function test_sin_permiso_no_se_puede_guardar(): void
    {
        $this->actingAs(User::factory()->create());

        $client = $this->makeClient('theta');
        $token = $this->makeToken($client);
        $environment = $this->makeEnvironment($client, 'https://theta.test');

        Livewire::test(ManageEnvironments::class, ['license_token' => $token])
            ->set('selectedEnvironments', [$environment->id])
            ->call('save')
            ->assertForbidden();

        $this->assertNull($environment->fresh()->license_token_id);
    }
}
