"""
Claves de API: dos listas en una dirección, y un secreto que se dice una vez.

Las dos cosas que este archivo defiende son las dos que la pantalla no puede
comprobar sola. La primera es que las revocadas paginen: no se borran nunca
(FR-061), así que esa lista sólo crece y es exactamente el tipo de lista que
RT-09 obliga a paginar en el servidor. La segunda es que las dos listas convivan
en la misma querystring sin pisarse, que es lo que resuelve el prefijo: sin él,
pasar a la página 2 de una manda a la página 2 de la otra y ordenar una
desordena las dos.

Y la tercera, la que más caro sale si falla: **el valor en claro no aparece en
las props de ninguna pantalla**. Puesto ahí quedaría guardado en el historial de
navegación de Inertia y el botón «Atrás» volvería a mostrar un secreto que el
contrato promete irrepetible (FR-041, RT-06).
"""

import json

import pytest
from django.test import Client

from apps.accounts.models import ApiKey
from apps.core.tables import PER_PAGE
from tests.factories import create_account

pytestmark = pytest.mark.django_db

PASSWORD = 'contraseña-de-prueba'


@pytest.fixture(autouse=True)
def screen_routes(settings):
    """Las rutas de las pantallas, que el urlconf de producción monta aparte."""
    settings.ROOT_URLCONF = 'tests.urlconfs'


def log_in(account) -> Client:
    client = Client()
    response = client.post('/login', {'email': account.email, 'password': PASSWORD})

    assert response.status_code == 302, 'el ingreso de la prueba falló'
    return client


def screen(client) -> dict:
    return client.get('/api-keys', HTTP_X_INERTIA='true').json()['props']


def issue_many(account, count: int, *, revoked: bool = False) -> None:
    for index in range(count):
        key, _plaintext = ApiKey.issue(account=account, name=f'clave-{index:04d}')
        if revoked:
            key.revoke()


# --- Las dos listas ---------------------------------------------------------


def test_the_two_lists_arrive_as_two_tables_with_their_own_prefix():
    account = create_account()
    client = log_in(account)
    issue_many(account, 2)
    issue_many(account, 3, revoked=True)

    props = screen(client)

    assert props['keys']['count'] == 2
    assert props['revoked']['count'] == 3
    assert props['keys']['sort'] == '-created'
    assert props['revoked']['sort'] == '-revoked'


def test_the_revoked_ones_paginate_because_they_never_go_away():
    """
    Una clave revocada no se borra: la fila queda para poder auditarla. La lista
    sólo crece, así que la pantalla no puede recibirla entera (RT-09).
    """
    account = create_account()
    client = log_in(account)
    issue_many(account, PER_PAGE + 7, revoked=True)

    revoked = screen(client)['revoked']

    assert revoked['count'] == PER_PAGE + 7
    assert len(revoked['rows']) == PER_PAGE
    assert revoked['pages'] == 2


def test_moving_one_list_leaves_the_other_where_it_was():
    """
    El problema que el prefijo resuelve, dicho en datos: la posición de una lista
    no puede ser la posición de la otra.
    """
    account = create_account()
    client = log_in(account)
    issue_many(account, PER_PAGE + 3)
    issue_many(account, PER_PAGE + 5, revoked=True)

    props = client.get('/api-keys?revoked_page=2', HTTP_X_INERTIA='true').json()['props']

    assert props['revoked']['page'] == 2
    assert props['keys']['page'] == 1, 'Mover una lista movió también la otra.'


def test_each_list_carries_the_position_of_the_other_in_its_links():
    """
    La otra mitad de convivir: si los enlaces de una no arrastraran la posición
    de la otra, pasar de página en una devolvería la otra a su principio.
    """
    account = create_account()
    client = log_in(account)
    issue_many(account, PER_PAGE + 3)
    issue_many(account, PER_PAGE + 5, revoked=True)

    props = client.get('/api-keys?revoked_page=2', HTTP_X_INERTIA='true').json()['props']

    assert props['keys']['query']['revoked_page'] == '2'


def test_ordering_one_list_only_declares_its_own_keys():
    account = create_account()
    client = log_in(account)
    issue_many(account, 1)
    issue_many(account, 1, revoked=True)

    props = screen(client)

    assert sorted(props['keys']['sortable']) == ['created', 'name', 'used']
    assert sorted(props['revoked']['sortable']) == ['name', 'revoked', 'used']


# --- La revocación ----------------------------------------------------------


def test_revoking_moves_the_row_to_the_other_list_and_says_so():
    """
    RT-11: la acción redirige, así que el resultado lo anuncia la pantalla de
    destino, y dice adónde se fue la fila. Sin esa segunda mitad la clave parece
    haberse borrado.
    """
    account = create_account()
    client = log_in(account)
    key, _plaintext = ApiKey.issue(account=account, name='deploy-produccion')

    client.post(f'/api-keys/{key.id}/revoke')
    props = screen(client)

    assert props['revoked_name'] == 'deploy-produccion'
    assert props['keys']['count'] == 0
    assert [row['name'] for row in props['revoked']['rows']] == ['deploy-produccion']


def test_revoking_something_already_revoked_announces_nothing():
    """El mensaje diría «revocamos» sobre algo que quedó revocado hace una semana."""
    account = create_account()
    client = log_in(account)
    key, _plaintext = ApiKey.issue(account=account, name='deploy-produccion')
    key.revoke()

    client.post(f'/api-keys/{key.id}/revoke')

    assert screen(client)['revoked_name'] is None


# --- El secreto -------------------------------------------------------------


def test_the_plaintext_value_comes_back_once_and_only_in_the_response_to_the_creation():
    account = create_account()
    client = log_in(account)

    created = client.post(
        '/api-keys/new',
        data=json.dumps({'name': 'deploy-produccion'}),
        content_type='application/json',
    ).json()

    assert created['key'].startswith('irk_')
    assert created['prefix'] in created['key']


def test_the_screen_props_never_carry_the_value():
    """
    Una prop de Inertia queda guardada en el historial del navegador: el botón
    «Atrás» volvería a mostrar un secreto que el contrato promete irrepetible.
    """
    account = create_account()
    client = log_in(account)
    created = client.post(
        '/api-keys/new',
        data=json.dumps({'name': 'deploy-produccion'}),
        content_type='application/json',
    ).json()

    props = json.dumps(screen(client))

    assert created['key'] not in props
    assert 'hashed_key' not in props
    assert created['prefix'] in props, 'El prefijo sí tiene que estar: es cómo se reconoce la fila.'
