<?php

namespace Tests\Feature\Api\Admin;

use App\Models\ApiRequestLog;
use App\Models\Auth\User;
use App\Support\ApiDeAdministracion;
use Laravel\Sanctum\PersonalAccessToken;

/**
 * Quién puede entrar en la API de administración, y qué queda registrado (PRODSECU-215).
 */
class AutenticacionTest extends ApiDeAdministracionTestCase
{
    public function test_sin_token_es_401_en_json(): void
    {
        $this->api('GET', 'types')
            ->assertStatus(401)
            ->assertExactJson(['message' => 'Unauthenticated.']);
    }

    public function test_un_token_inventado_es_401(): void
    {
        $this->api('GET', 'types', [], '999|inventado')->assertStatus(401);
    }

    public function test_un_token_caducado_es_401(): void
    {
        $token = ApiDeAdministracion::usuarioDeServicio()
            ->createToken('caducado', ['admin.catalogs.read'], now()->subMinute())
            ->plainTextToken;

        $this->api('GET', 'types', [], $token)->assertStatus(401);
    }

    public function test_un_token_revocado_es_401(): void
    {
        $token = $this->token(['admin.catalogs.read']);
        $this->api('GET', 'types', [], $token)->assertOk();

        PersonalAccessToken::query()->delete();

        $this->api('GET', 'types', [], $token)->assertStatus(401);
    }

    public function test_sin_el_permiso_de_la_ruta_es_403(): void
    {
        $token = $this->token(['admin.clients.read']);

        $this->api('GET', 'types', [], $token)
            ->assertStatus(403)
            ->assertJsonStructure(['message']);
    }

    /** `*` es lo que pone Sanctum si se crea un token sin lista: no abre nada. */
    public function test_el_comodin_no_da_permisos(): void
    {
        $token = ApiDeAdministracion::usuarioDeServicio()->createToken('todo')->plainTextToken;

        $this->api('GET', 'types', [], $token)->assertStatus(403);
    }

    public function test_un_token_de_otro_usuario_es_403(): void
    {
        $token = User::factory()->create()->createToken('ajeno', ['admin.catalogs.read'])->plainTextToken;

        $this->api('GET', 'types', [], $token)->assertStatus(403);
    }

    /**
     * Con la sesión del panel, Sanctum devuelve el usuario con un `TransientToken`, que dice
     * que sí a cualquier permiso. Aquí no vale.
     */
    public function test_la_sesion_del_panel_no_abre_la_api(): void
    {
        $this->actingAs(ApiDeAdministracion::usuarioDeServicio());

        $this->getJson('/api/admin/v1/types')->assertStatus(401);

        $this->actingAs(User::factory()->create());

        $this->getJson('/api/admin/v1/clients')->assertStatus(401);
    }

    /** Sin token no se responde 404: no se puede ir probando qué ids existen. */
    public function test_sin_token_un_id_que_no_existe_es_401_y_no_404(): void
    {
        $this->api('GET', 'clients/999')->assertStatus(401);
    }

    public function test_un_404_no_dice_el_nombre_de_la_clase(): void
    {
        $respuesta = $this->api('GET', 'clients/999', [], $this->tokenCompleto());

        $respuesta->assertStatus(404)->assertExactJson(['message' => 'No existe.']);
        $this->assertStringNotContainsString('App\\', $respuesta->getContent());
    }

    public function test_cada_peticion_queda_en_el_log_con_el_nombre_de_la_ruta(): void
    {
        $cliente = $this->cliente();
        $token = $this->tokenCompleto();

        $this->api('GET', 'clients/' . $cliente->id, [], $token)->assertOk();

        $fila = ApiRequestLog::latest('id')->first();
        $this->assertSame('api.admin.v1.clients.show', $fila->action);
        $this->assertSame('GET', $fila->method);
        $this->assertSame($cliente->id, $fila->client_id);
        $this->assertSame(200, $fila->http_status);
        $this->assertNull($fila->error);
    }

    public function test_los_errores_quedan_en_el_log_con_su_mensaje(): void
    {
        $this->api('POST', 'clients', ['name' => 'Sin shortname'], $this->tokenCompleto())->assertStatus(422);

        $fila = ApiRequestLog::latest('id')->first();
        $this->assertSame('api.admin.v1.clients.store', $fila->action);
        $this->assertSame(422, $fila->http_status);
        $this->assertSame('Los datos no son válidos.', $fila->error);
    }

    /** El logger va antes de autenticar: un 401 es lo que se busca cuando algo deja de ir. */
    public function test_un_401_tambien_queda_en_el_log(): void
    {
        $this->api('GET', 'types')->assertStatus(401);

        $fila = ApiRequestLog::latest('id')->first();
        $this->assertNotNull($fila);
        $this->assertSame('api.admin.v1.types.index', $fila->action);
        $this->assertSame(401, $fila->http_status);
    }

    public function test_el_limite_es_por_token(): void
    {
        $token = $this->token(['admin.catalogs.read']);

        for ($i = 0; $i < 60; $i++) {
            $this->api('GET', 'envs', [], $token)->assertOk();
        }

        $this->api('GET', 'envs', [], $token)->assertStatus(429);

        // Otro token tiene su propio cupo.
        $this->api('GET', 'envs', [], $this->token(['admin.catalogs.read'], 'otro'))->assertOk();
    }
}
