"""
Contrato de las sesiones abiertas.

Los tres endpoints existen para el mismo incidente: alguien sospecha que le
entraron y quiere echar al resto. Por eso lo que se verifica acá no es la forma
de la respuesta sino su efecto — que la sesión cerrada deje de servir — y que el
identificador de otra cuenta responda `404` y no un error que confirme que
existe.
"""

import pytest
from django.test import Client
from rest_framework.test import APIClient

from apps.accounts.models import ApiKey, Session
from tests.factories import create_account

pytestmark = pytest.mark.django_db

PASSWORD = 'contraseña-de-prueba'
FIREFOX = 'Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0'


@pytest.fixture
def account():
    return create_account()


@pytest.fixture
def client(account):
    """Un pipeline, autenticado por clave: no tiene sesión de navegador propia."""
    api = APIClient()
    _, plain_text = ApiKey.issue(account=account, name='pipeline')
    api.credentials(HTTP_AUTHORIZATION=f'Api-Key {plain_text}')
    return api


def browser(account, *, ip='190.12.44.7') -> Client:
    opened = Client(HTTP_USER_AGENT=FIREFOX, REMOTE_ADDR=ip)
    opened.post('/login', {'email': account.email, 'password': PASSWORD})
    return opened


def still_inside(client) -> bool:
    """
    La raíz alcanza para saber si la cookie todavía entra, y no depende de ningún listado.

    Se mira el código y no el destino de la redirección: desde que la raíz es la
    elección de herramienta, quien está adentro recibe la pantalla —200— y sólo
    quien no lo está se va redirigido al ingreso.
    """
    return client.get('/').status_code == 200


def test_the_listing_brings_the_data_that_allows_deciding(account, client):
    """
    FR-059: navegador, sistema, dirección de origen, última actividad e inicio.

    La dirección viaja tal como llegó. Traducirla a una ciudad la presentaría
    como un hecho cuando es una inferencia.
    """
    browser(account)

    data = client.get('/api/v1/sessions').json()

    assert len(data) == 1
    assert data[0]['ip_address'] == '190.12.44.7'
    assert data[0]['browser'] == 'Firefox'
    assert data[0]['operating_system'] == 'Linux'
    assert data[0]['user_agent'] == FIREFOX
    assert data[0]['last_activity_at'] and data[0]['created_at']
    # Sin sesión de navegador en curso, ninguna fila puede ser «ésta».
    assert data[0]['is_current'] is False


def test_an_unrecognizable_agent_is_not_made_up(account, client):
    """«Navegador no identificado» lo escribe la pantalla; el servidor no adivina."""
    Client(HTTP_USER_AGENT='', REMOTE_ADDR='45.5.5.5').post(
        '/login', {'email': account.email, 'password': PASSWORD}
    )

    data = client.get('/api/v1/sessions').json()

    assert data[0]['user_agent'] is None
    assert data[0]['browser'] is None
    assert data[0]['operating_system'] is None


def test_closing_a_session_leaves_it_out(account, client):
    opened = browser(account)
    session = Session.objects.get(account=account)

    response = client.delete(f'/api/v1/sessions/{session.id}')

    assert response.status_code == 204
    assert not still_inside(opened)


def test_closing_them_all_says_how_many_it_closed(account, client):
    """
    Con clave de API no hay ninguna sesión en curso que conservar.

    Es lo que se espera de un «cerrá todo» disparado desde un pipeline tras un
    incidente: no queda ninguna abierta.
    """
    browser(account)
    browser(account, ip='201.230.1.9')

    response = client.delete('/api/v1/sessions')

    assert response.status_code == 200
    assert response.json() == {'closed': 2}
    assert client.get('/api/v1/sessions').json() == []


def test_the_session_of_another_account_responds_that_it_does_not_exist(client):
    neighbor = create_account(email='vecina@ejemplo.test')
    opened = browser(neighbor)
    foreign = Session.objects.get(account=neighbor)

    response = client.delete(f'/api/v1/sessions/{foreign.id}')

    assert response.status_code == 404
    assert response.json()['error']['code'] == 'NOT_FOUND'
    foreign.refresh_from_db()
    assert foreign.revoked_at is None
    assert still_inside(opened)
