"""
El punto de atención del tablero, y la regla que lo hace confiable.

Un punto de color contesta de un vistazo la única pregunta que alguien le hace al
tablero al abrirlo: ¿está todo bien con mi sitio? Por eso su valor entero depende
de una cosa —**que no pueda contradecir a la palabra que tiene al lado**—. Un
punto verde arriba de un badge que dice «acceso perdido» es peor que no tener
punto: enseña a no mirarlo.

La forma de garantizarlo no es comparar dos mapas: es que haya **uno solo**.
`accessStateTone()` es ese lugar, y lo que se prueba acá es que el badge salga de
ahí, para los cinco estados y en los dos modos de la cuenta.
"""

import re

import pytest

from tests.frontend.harness import call, needs_frontend, render, resolve

ACCESS_BADGE = '@/components/AccessStateBadge'
ATTENTION_DOT = '@/components/AttentionDot'

pytestmark = needs_frontend

#: Los cinco estados de acceso que el producto sabe dibujar.
STATES = ('AWAITING_ACCESS', 'OPERATIONAL', 'ACCESS_LOST', 'ACCESS_REVOKED', 'SUSPENDED')

#: Los cuatro tonos que el punto puede tomar. `neutral` y `unknown` comparten el
#: gris a propósito: las dos son ausencia de afirmación.
TONES = ('positive', 'attention', 'critical', 'neutral', 'unknown')

REQUESTS: dict[str, dict] = {}

for state in STATES:
    for can_operate in (True, False):
        suffix = 'able' if can_operate else 'unable'
        REQUESTS[f'badge_{state}_{suffix}'] = render(
            ACCESS_BADGE, 'AccessStateBadge', state=state, canOperate=can_operate
        )
        REQUESTS[f'tone_{state}_{suffix}'] = call(
            ACCESS_BADGE, 'accessStateTone', state, can_operate
        )

for tone in TONES:
    REQUESTS[f'dot_{tone}'] = render(ATTENTION_DOT, 'AttentionDot', tone=tone)


@pytest.fixture(scope='module')
def screen() -> dict:
    return resolve(REQUESTS)


def classes(markup: str) -> str:
    """Las clases con las que se dibuja, que es lo que decide el color."""
    found = re.search(r'class="([^"]*)"', markup)
    assert found, f'La pieza salió sin clases: {markup[:120]}'
    return found.group(1)


def tone_of(markup: str) -> str:
    """El tono que el marcado declara, sin tener que leer la lista de clases."""
    found = re.search(r'data-tone="([a-z]+)"', markup)
    assert found, f'La pieza no declaró su tono en el marcado: {markup[:120]}'
    return found.group(1)


@pytest.mark.parametrize('state', STATES)
@pytest.mark.parametrize('can_operate', [True, False], ids=['can-operate', 'cannot-operate'])
def test_the_dot_and_the_word_come_from_the_same_criterion(screen, state, can_operate):
    """
    Es la regla completa: un solo criterio, dos dibujos.

    El caso que la hace falta es `cannot-operate`, y no es teórico: sin
    credencial que sirva, un sitio operativo no se puede afirmar operativo
    (RT-18). Un mapa copiado en el punto se olvidaría de esa regla y dejaría el
    verde encendido sobre un badge que ya bajó a «sin confirmar».
    """
    suffix = 'able' if can_operate else 'unable'

    assert screen[f'tone_{state}_{suffix}'] == tone_of(screen[f'badge_{state}_{suffix}'])


def test_an_unknown_state_does_not_paint_itself_as_good_or_bad(screen):
    """
    Un estado que el frente no conoce cae en `neutral`.

    Es lo que evita que un estado agregado mañana en el servidor herede el color
    del último del mapa y afirme algo sobre él sin que nadie lo haya decidido.
    """
    assert screen['tone_SUSPENDED_able'] in ('neutral', 'unknown')


def test_the_dot_never_speaks_on_its_own(screen):
    """
    `aria-hidden`, y no es un descuido (RT-04).

    La palabra del estado ya está en la tarjeta: un lector de pantalla que además
    anuncie el punto diría el estado dos veces. Que el color no llegue a quien no
    ve la pantalla es correcto **porque el color nunca es la única señal**.
    """
    for tone in TONES:
        assert 'aria-hidden' in screen[f'dot_{tone}'], f'El punto {tone} se anuncia solo.'


def test_the_three_colours_that_mean_something_are_told_apart(screen):
    """
    Verde, amarillo y rojo tienen que ser tres clases distintas.

    Sin esto, dos tonos podrían compartir color por un copiar y pegar y el punto
    diría lo mismo para «operativo» y para «acceso perdido».
    """
    palettes = {
        tone: classes(screen[f'dot_{tone}']) for tone in ('positive', 'attention', 'critical')
    }

    assert len(set(palettes.values())) == 3, 'Dos tonos del punto se dibujan igual.'


def test_the_two_tones_without_an_answer_share_the_grey(screen):
    """
    `neutral` y `unknown` se dibujan igual, y es deliberado.

    Las dos son ausencia de afirmación —una no tiene nada que decir, la otra no
    lo pudo comprobar— y un punto no tiene dónde poner el borde punteado que las
    distingue en un badge. Esa diferencia la hace la palabra que va al lado.

    Se comparan las **clases** y no el marcado entero: `data-tone` los distingue
    a propósito, y es justamente lo que permite comprobar desde afuera qué
    familia le tocó a cada estado.
    """
    assert classes(screen['dot_neutral']) == classes(screen['dot_unknown'])
