<?php

namespace Tests\Feature\Api\Admin;

use App\Models\Environments\Environment;
use App\Models\Monitoring\Log;

/**
 * Entornos en la API de administración (PRODSECU-215).
 */
class EntornosTest extends ApiDeAdministracionTestCase
{
    public function test_crear_entorno(): void
    {
        $moodle = $this->tipo('moodle');
        $cliente = $this->cliente();
        $licencia = $this->licencia($cliente);

        $respuesta = $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'Campus virtual',
            'domain' => 'https://campus.uejemplo.es/',
            'env' => 'pro',
            'type_id' => $moodle->id,
            'license_token_id' => $licencia->id,
            'version' => '4.5',
            // Se ignoran:
            'active' => false,
            'moodletoken' => 'secreto',
        ], $this->token(['admin.environments.write']));

        $respuesta->assertCreated()->assertExactJson(['data' => [
            'id' => $respuesta->json('data.id'),
            'client_id' => $cliente->id,
            'license_token_id' => $licencia->id,
            'type_id' => $moodle->id,
            'env' => 'pro',
            'name' => 'Campus virtual',
            'domain' => 'https://campus.uejemplo.es',
            'description' => null,
            'key' => null,
            'version' => '4.5',
            'login_url' => null,
            'active' => true,
        ]]);

        $entorno = Environment::firstOrFail();
        $this->assertNull($entorno->moodletoken);
        $this->assertTrue($entorno->active);
        $this->assertSame(1, Log::where('code', '16050')->count());
    }

    public function test_busqueda_aproximada_por_nombre_y_dominio_sin_protocolo(): void
    {
        $cliente = $this->cliente();
        $campus = $this->entorno($cliente, 'https://campus.uejemplo.es', ['name' => 'Campus virtual']);
        $pre = $this->entorno($cliente, 'https://pre-campus.uejemplo.es', ['name' => 'Campus pre']);
        $otro = $this->entorno($cliente, 'https://www.formacion.otro.es', ['name' => 'Formación']);
        $token = $this->token(['admin.environments.read']);

        // Con protocolo, www y barra final da igual: el host exacto va primero.
        $this->api('GET', 'environments?search=' . urlencode('HTTP://www.campus.uejemplo.es/'), [], $token)
            ->assertOk()
            ->assertJsonPath('data.0.id', $campus->id);

        // Un trozo del host trae los dos campus, y no el otro.
        $ids = collect($this->api('GET', 'environments?search=campus', [], $token)->json('data'))->pluck('id')->all();
        $this->assertEqualsCanonicalizing([$campus->id, $pre->id], $ids);

        // Por el nombre, y por palabras en cualquier orden.
        $this->api('GET', 'environments?search=' . urlencode('virtual campus'), [], $token)
            ->assertJsonPath('data.0.id', $campus->id);

        // Sin www en lo buscado encuentra el que lo tiene guardado.
        $this->api('GET', 'environments?search=formacion.otro.es', [], $token)
            ->assertJsonPath('data.0.id', $otro->id);

        // Con una errata, sugiere el parecido.
        $this->api('GET', 'environments?search=campus.uejempol.es', [], $token)
            ->assertOk()
            ->assertJsonPath('data.0.id', $campus->id);

        // Se combina con los demás filtros y pagina.
        $this->api('GET', 'environments?search=campus&per_page=1', [], $token)
            ->assertJsonCount(1, 'data')
            ->assertJsonPath('meta.total', 2);

        $this->api('GET', 'environments?search=campus&client_id=999999', [], $token)
            ->assertJsonCount(0, 'data');
    }

    public function test_un_dominio_ocupado_es_409_con_el_existente(): void
    {
        $moodle = $this->tipo('moodle');
        $cliente = $this->cliente();
        $existente = $this->entorno($cliente, 'https://campus.test');

        $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'Duplicado', 'domain' => 'https://CAMPUS.test/', 'env' => 'pro', 'type_id' => $moodle->id, 'version' => '4.5',
        ], $this->token(['admin.environments.write']))
            ->assertStatus(409)
            ->assertJsonPath('data.id', $existente->id)
            ->assertJsonMissingPath('data.moodletoken');

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

    public function test_crear_entorno_con_los_campos_de_la_ficha(): void
    {
        $moodle = $this->tipo('moodle');
        $cliente = $this->cliente();
        $token = $this->token(['admin.environments.write']);

        $respuesta = $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'Campus', 'domain' => 'https://campus.uejemplo.es', 'env' => 'pro', 'type_id' => $moodle->id,
            'description' => 'Campus principal', 'key' => 'UEJ', 'version' => '4.5.2',
            'login_url' => 'https://campus.uejemplo.es/login/sso.php',
            'moodletoken' => 'no-se-acepta', 'active' => false,
        ], $token)->assertCreated();

        $entorno = Environment::findOrFail($respuesta->json('data.id'));
        $this->assertSame('Campus principal', $entorno->description);
        $this->assertSame('UEJ', $entorno->key);
        $this->assertSame('4.5.2', $entorno->version);
        $this->assertSame('https://campus.uejemplo.es/login/sso.php', $entorno->login_url);
        $this->assertNull($entorno->moodletoken);
        $this->assertTrue((bool) $entorno->active);

        // Lo que se escribe, se devuelve.
        $respuesta->assertJsonPath('data.description', 'Campus principal')
            ->assertJsonPath('data.key', 'UEJ')
            ->assertJsonPath('data.version', '4.5.2')
            ->assertJsonPath('data.login_url', 'https://campus.uejemplo.es/login/sso.php')
            ->assertJsonMissingPath('data.moodletoken');

        $this->api('PATCH', 'environments/' . $entorno->id, ['login_url' => 'no-es-url'], $token)
            ->assertStatus(422)->assertJsonValidationErrors('login_url');

        // La versión es obligatoria, como en el formulario del panel.
        $this->api('PATCH', 'environments/' . $entorno->id, ['version' => null], $token)
            ->assertStatus(422)->assertJsonValidationErrors('version');

        $this->api('PATCH', 'environments/' . $entorno->id, ['description' => 'Otra'], $token)
            ->assertOk()
            ->assertJsonPath('data.description', 'Otra');
    }

    /** Si el dominio es de un entorno de otro cliente, el 409 no lo enseña. */
    public function test_un_dominio_de_otro_cliente_es_409_sin_el_entorno(): void
    {
        $moodle = $this->tipo('moodle');
        $this->entorno($this->cliente('otro'), 'https://campus.test', ['name' => 'Campus del otro']);
        $cliente = $this->cliente();

        $respuesta = $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'Duplicado', 'domain' => 'https://campus.test', 'env' => 'pro', 'type_id' => $moodle->id, 'version' => '4.5',
        ], $this->token(['admin.environments.write']));

        $respuesta->assertStatus(409)
            ->assertJsonPath('message', 'Ese dominio ya lo usa un entorno de otro cliente. Se revisa desde el panel.')
            ->assertJsonMissingPath('data');
        $this->assertStringNotContainsString('Campus del otro', $respuesta->getContent());
        $this->assertSame(1, Environment::count());
    }

    public function test_los_local_pueden_repetir_dominio(): void
    {
        $moodle = $this->tipo('moodle');
        $cliente = $this->cliente();
        $this->entorno($cliente, 'http://moodle.test', ['env' => 'local']);

        $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'Mi local', 'domain' => 'http://moodle.test', 'env' => 'local', 'type_id' => $moodle->id, 'version' => '4.5',
        ], $this->token(['admin.environments.write']))->assertCreated();
    }

    public function test_validaciones_del_alta(): void
    {
        $cliente = $this->cliente();
        $token = $this->token(['admin.environments.write']);

        $respuesta = $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'X', 'domain' => 'http://sin-https.test', 'env' => 'produccion', 'type_id' => 999,
        ], $token);

        $respuesta->assertStatus(422)->assertJsonStructure(['errors' => ['domain', 'env', 'type_id']]);
        $this->assertStringContainsString('pro', $respuesta->json('errors.env.0'));
        $this->assertStringContainsString('local', $respuesta->json('errors.env.0'));

        // Lo obligatorio es lo del formulario del panel: la versión sí, la plataforma no.
        $sinVersion = $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'X', 'domain' => 'https://bien.test', 'env' => 'pro',
        ], $token);
        $sinVersion->assertStatus(422)->assertJsonStructure(['errors' => ['version']]);
        $this->assertArrayNotHasKey('type_id', $sinVersion->json('errors'));

        $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'X', 'domain' => 'https://bien.test', 'env' => 'pro', 'version' => '4.5',
        ], $token)->assertCreated()->assertJsonPath('data.type_id', null);
    }

    public function test_una_licencia_de_otro_cliente_es_422(): void
    {
        $moodle = $this->tipo('moodle');
        $cliente = $this->cliente();
        $ajena = $this->licencia($this->cliente('otro'));

        $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'X', 'domain' => 'https://campus.test', 'env' => 'pro', 'type_id' => $moodle->id, 'version' => '4.5',
            'license_token_id' => $ajena->id,
        ], $this->token(['admin.environments.write']))
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['license_token_id']]);

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

    public function test_una_licencia_con_productos_incompatibles_es_422(): void
    {
        $moodle = $this->tipo('moodle');
        $workplace = $this->tipo('workplace');
        $cliente = $this->cliente();
        $licencia = $this->licencia($cliente);
        $licencia->products()->attach($this->producto('solo-moodle', [$moodle])->id, ['status' => 'active']);

        $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'X', 'domain' => 'https://wp.test', 'env' => 'pro', 'type_id' => $workplace->id, 'version' => '4.5',
            'license_token_id' => $licencia->id,
        ], $this->token(['admin.environments.write']))
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['license_token_id']]);
    }

    public function test_no_se_crean_entornos_en_un_cliente_de_baja(): void
    {
        $moodle = $this->tipo('moodle');
        $cliente = $this->cliente('debaja', ['actived' => false]);

        $this->api('POST', 'clients/' . $cliente->id . '/environments', [
            'name' => 'X', 'domain' => 'https://campus.test', 'env' => 'pro', 'type_id' => $moodle->id, 'version' => '4.5',
        ], $this->token(['admin.environments.write']))->assertStatus(409);
    }

    public function test_editar_no_toca_active_ni_el_cliente(): void
    {
        $cliente = $this->cliente();
        $otro = $this->cliente('otro');
        $entorno = $this->entorno($cliente, 'https://campus.test');

        $this->api('PATCH', 'environments/' . $entorno->id, [
            'name' => 'Nuevo nombre', 'active' => false, 'client_id' => $otro->id,
        ], $this->token(['admin.environments.write']))
            ->assertOk()
            ->assertJsonPath('data.name', 'Nuevo nombre')
            ->assertJsonPath('data.active', true)
            ->assertJsonPath('data.client_id', $cliente->id);

        $entorno->refresh();
        $this->assertTrue($entorno->active);
        $this->assertSame($cliente->id, $entorno->client_id);
    }

    public function test_asignar_licencia_a_un_entorno_sin_licencia(): void
    {
        $cliente = $this->cliente();
        $licencia = $this->licencia($cliente);
        $entorno = $this->entorno($cliente, 'https://campus.test');

        $this->api('PATCH', 'environments/' . $entorno->id, ['license_token_id' => $licencia->id], $this->token(['admin.environments.write']))
            ->assertOk()
            ->assertJsonPath('data.license_token_id', $licencia->id);
    }

    public function test_mover_un_entorno_de_licencia_es_409(): void
    {
        $cliente = $this->cliente();
        $actual = $this->licencia($cliente, '3IP-A');
        $otra = $this->licencia($cliente, '3IP-B');
        $entorno = $this->entorno($cliente, 'https://campus.test', ['license_token_id' => $actual->id]);
        $token = $this->token(['admin.environments.write']);

        $this->api('PATCH', 'environments/' . $entorno->id, ['license_token_id' => $otra->id], $token)->assertStatus(409);
        $this->api('PATCH', 'environments/' . $entorno->id, ['license_token_id' => null], $token)->assertStatus(409);

        // La misma que ya tiene no es un cambio.
        $this->api('PATCH', 'environments/' . $entorno->id, ['license_token_id' => $actual->id], $token)->assertOk();

        $this->assertSame($actual->id, $entorno->fresh()->license_token_id);
    }

    public function test_cambiar_el_dominio_deja_el_rastro_del_panel(): void
    {
        $cliente = $this->cliente();
        $entorno = $this->entorno($cliente, 'https://campus.test');
        $this->entorno($cliente, 'https://ocupado.test');
        $token = $this->token(['admin.environments.write']);

        $this->api('PATCH', 'environments/' . $entorno->id, ['domain' => 'https://ocupado.test'], $token)->assertStatus(409);

        $this->api('PATCH', 'environments/' . $entorno->id, ['domain' => 'https://nuevo.test'], $token)
            ->assertOk()
            ->assertJsonPath('data.domain', 'https://nuevo.test');

        $this->assertSame(1, Log::where('code', '16020')->count());
    }

    public function test_pasar_de_local_a_pro_valida_el_dominio_que_tiene(): void
    {
        $cliente = $this->cliente();
        $entorno = $this->entorno($cliente, 'http://moodle.test', ['env' => 'local']);

        $this->api('PATCH', 'environments/' . $entorno->id, ['env' => 'pro'], $this->token(['admin.environments.write']))
            ->assertStatus(422)
            ->assertJsonStructure(['errors' => ['domain']]);
    }

    public function test_buscar_entornos_con_todas_sus_propiedades_menos_las_credenciales(): void
    {
        $moodle = $this->tipo('moodle');
        $cliente = $this->cliente();
        $this->entorno($cliente, 'https://campus.test', [
            'type_id' => $moodle->id, 'moodletoken' => 'secreto-del-moodle', 'key' => 'PROJ', 'description' => 'Interna',
        ]);
        $this->entorno($cliente, 'https://pre.test', ['env' => 'pre']);

        $token = $this->token(['admin.environments.read']);

        $respuesta = $this->api('GET', 'environments?domain=https://campus.test/', [], $token);
        $respuesta->assertOk()->assertJsonCount(1, 'data');
        $this->assertSame(
            ['id', 'client_id', 'license_token_id', 'type_id', 'env', 'name', 'domain', 'description', 'key', 'version', 'login_url', 'active'],
            array_keys($respuesta->json('data.0'))
        );
        $respuesta->assertJsonPath('data.0.key', 'PROJ')->assertJsonPath('data.0.description', 'Interna');

        // La credencial del Moodle no sale nunca.
        foreach (['secreto-del-moodle', 'moodletoken'] as $prohibido) {
            $this->assertStringNotContainsString($prohibido, $respuesta->getContent());
        }

        $this->api('GET', 'environments?env=pre', [], $token)->assertJsonCount(1, 'data');
        $this->api('GET', 'environments?type_id=' . $moodle->id, [], $token)->assertJsonCount(1, 'data');
        $this->api('GET', 'environments?client_id=' . $cliente->id, [], $token)->assertJsonCount(2, 'data');
    }
}
