<?php

namespace Tests\Feature\Environments;

use App\Livewire\Environments\CreateFromClient;
use App\Livewire\Environments\CreateWizard;
use App\Livewire\Environments\Edit;
use App\Livewire\LicenseTokens\Components\EnvironmentSelection;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Rules\DominioConHttps;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Validator;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * El dominio de un entorno lleva `https://` (PRODSECU-185).
 *
 * Se dieron de alta entornos con `http://`, o sin esquema, que dieron problemas con la
 * licencia, y el formulario no lo impedía: el ejemplo que enseñaba era `www.tresipunt.com`.
 * Los `local` quedan fuera, porque son Moodles de desarrollo sin certificado
 * (`http://moodle.test`), igual que ya lo estaban del dominio único (PRODSECU-192).
 */
class DominioConHttpsTest extends TestCase
{
    use RefreshDatabase;

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

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

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

        Cache::flush();

        return $user;
    }

    private function cliente(): Client
    {
        return Client::firstOrCreate(
            ['shortname' => 'https'],
            ['name' => 'Cliente https', 'actived' => true]
        );
    }

    private function pasa(string $dominio, ?string $env = 'pro'): bool
    {
        return Validator::make(['domain' => $dominio], ['domain' => [new DominioConHttps($env)]])->passes();
    }

    /** @return array<string, array{string}> */
    public static function dominiosQueNoValen(): array
    {
        return [
            'con http' => ['http://campus.cliente.com'],
            'sin esquema' => ['campus.cliente.com'],
            'con www y sin esquema' => ['www.tresipunt.com'],
            'solo el esquema' => ['https://'],
            'con un espacio dentro' => ['https://campus cliente.com'],
            'otro esquema' => ['ftp://campus.cliente.com'],
        ];
    }

    /**
     * @dataProvider dominiosQueNoValen
     */
    public function test_la_regla_rechaza_lo_que_no_es_una_url_https(string $malo): void
    {
        $this->assertFalse($this->pasa($malo));
    }

    public function test_la_regla_acepta_una_url_https(): void
    {
        $this->assertTrue($this->pasa('https://campus.cliente.com'));
        $this->assertTrue($this->pasa('HTTPS://Campus.Cliente.com/'));
        $this->assertTrue($this->pasa('https://cliente.com/moodle'));
        $this->assertTrue($this->pasa('  https://campus.cliente.com  '), 'el modelo quita los espacios al guardar');
    }

    public function test_local_puede_ir_por_http(): void
    {
        $this->assertTrue($this->pasa('http://moodle.test', 'local'));
    }

    public function test_sin_uso_se_exige(): void
    {
        // Lo seguro por defecto, igual que `UniqueEnvironmentDomain`.
        $this->assertFalse($this->pasa('http://campus.cliente.com', null));
    }

    public function test_el_mensaje_distingue_http_de_sin_esquema(): void
    {
        $conHttp = Validator::make(['d' => 'http://a.com'], ['d' => [new DominioConHttps('pro')]]);
        $sinEsquema = Validator::make(['d' => 'a.com'], ['d' => [new DominioConHttps('pro')]]);

        $this->assertStringContainsString('no con http://', $conHttp->errors()->first('d'));
        $this->assertStringContainsString('https://campus.cliente.com', $sinEsquema->errors()->first('d'));
    }

    public function test_el_alta_desde_un_cliente_no_guarda_un_http(): void
    {
        $this->admin();

        Livewire::test(CreateFromClient::class, ['client' => $this->cliente()])
            ->set('name', 'Sitio')
            ->set('env', 'pro')
            ->set('version', '5.1.1')
            ->set('domain', 'http://campus.cliente.com')
            ->call('save')
            ->assertHasErrors('domain');

        $this->assertDatabaseMissing('environments', ['domain' => 'http://campus.cliente.com']);
    }

    public function test_el_alta_desde_un_cliente_guarda_un_local_con_http(): void
    {
        $this->admin();

        Livewire::test(CreateFromClient::class, ['client' => $this->cliente()])
            ->set('name', 'Mi local')
            ->set('env', 'local')
            ->set('version', '5.1.1')
            ->set('domain', 'http://moodle.test')
            ->call('save')
            ->assertHasNoErrors('domain');

        $this->assertDatabaseHas('environments', ['domain' => 'http://moodle.test', 'env' => 'local']);
    }

    public function test_el_aviso_sale_al_salir_del_campo_sin_guardar(): void
    {
        $this->admin();

        Livewire::test(CreateFromClient::class, ['client' => $this->cliente()])
            ->set('env', 'pro')
            ->set('domain', 'campus.cliente.com')
            ->assertHasErrors('domain');

        $this->assertSame(0, Environment::count());
    }

    public function test_pasar_de_local_a_pro_vuelve_a_comprobar_el_dominio(): void
    {
        // El formulario arranca en `local`: se escribe el `http://` sin aviso, y al elegir
        // `pro` tiene que salir.
        $this->admin();

        Livewire::test(CreateFromClient::class, ['client' => $this->cliente()])
            ->set('domain', 'http://campus.cliente.com')
            ->assertHasNoErrors('domain')
            ->set('env', 'pro')
            ->assertHasErrors('domain');
    }

    public function test_editar_no_deja_un_http(): void
    {
        $this->admin();

        $entorno = Environment::create([
            'name' => 'Sitio',
            'domain' => 'https://campus.cliente.com',
            'version' => '5.1.1',
            'env' => 'pro',
            'active' => true,
            'client_id' => $this->cliente()->id,
        ]);

        Livewire::test(Edit::class, ['environment' => $entorno])
            ->set('domain', 'http://campus.cliente.com')
            ->call('update')
            ->assertHasErrors('domain');

        $this->assertSame('https://campus.cliente.com', $entorno->fresh()->domain);
    }

    public function test_el_asistente_de_entornos_avisa_al_salir_del_campo(): void
    {
        $this->admin();

        Livewire::test(CreateWizard::class)
            ->set('formData.env', 'pro')
            ->set('formData.domain', 'http://campus.cliente.com')
            ->assertHasErrors('formData.domain');
    }

    public function test_el_selector_del_asistente_de_licencias_avisa_al_salir_del_campo(): void
    {
        $this->admin();

        Livewire::test(EnvironmentSelection::class, ['clientId' => $this->cliente()->id, 'mode' => 'new'])
            ->set('newEnvironmentData.env', 'pro')
            ->set('newEnvironmentData.domain', 'campus.cliente.com')
            ->assertHasErrors('newEnvironmentData.domain');
    }

    public function test_ninguna_puerta_escribe_las_reglas_del_dominio_a_mano(): void
    {
        // Las siete puertas leen `Environment::reglasDelDominio()`. Con la regla copiada en
        // cada una, exigir el https:// habría sido tocar siete sitios y dejarse alguno.
        $sueltas = [];

        $ficheros = \Illuminate\Support\Facades\File::allFiles(app_path('Livewire'));

        foreach ($ficheros as $fichero) {
            if (str_contains($fichero->getContents(), 'new UniqueEnvironmentDomain(')) {
                $sueltas[] = $fichero->getRelativePathname();
            }
        }

        // Que haya recorrido algo: un directorio mal escrito haría pasar el test sin mirar nada.
        $this->assertGreaterThan(100, count($ficheros));
        $this->assertSame([], $sueltas, 'Usa Environment::reglasDelDominio() en lugar de la regla suelta.');
    }
}
