"""
La compuerta de salida del único dato irrepetible del producto.

`SecretRevealDialog` se dibuja en un portal, y un portal no existe fuera del
navegador: de un render estático no sale nada. Por eso la regla que hay que
defender vive en una función pura —igual que el orden del menú de una fila o la
apertura de un formulario desde la dirección— y es ésa la que se ejercita acá.

Lo que se fija es una sola cosa, dicha de todas las maneras en que podría
fallar: **el valor no se puede perder sin decidirlo**. Con la clave sin copiar,
ninguna de las tres salidas —el botón, `Escape`, el «Atrás» del navegador—
cierra el diálogo; todas abren la misma pregunta. La única que cierra sin copiar
es «Cerrar igual», que es el segundo clic de esa pregunta. Copiada, en cambio,
las tres cierran de una: la fricción cae sobre el caso de pérdida y sobre ningún
otro.
"""

import pytest

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

SECRET_REVEAL = '@/components/SecretRevealDialog'

pytestmark = needs_frontend

#: Recién abierto: la clave está en pantalla y nadie la copió.
FRESH = {'copied': False, 'gate': 'holding', 'clipboardFailed': False}

#: Con la clave en el portapapeles.
COPIED = {'copied': True, 'gate': 'holding', 'clipboardFailed': False}

#: Con la pregunta de salida a la vista.
ASKED = {'copied': False, 'gate': 'leaving', 'clipboardFailed': False}

#: El portapapeles falló: la clave sigue en pantalla y nadie la tiene.
FAILED = {'copied': False, 'gate': 'holding', 'clipboardFailed': True}

#: Las tres formas de salir que la modalidad no cubre por sí sola.
WAYS_OUT = ['save-confirmed', 'escape', 'back']


def _gate(state: dict, event: str) -> dict:
    return call(SECRET_REVEAL, 'nextSecretRevealState', state, event)


REQUESTS = {
    **{f'fresh_{event}': _gate(FRESH, event) for event in WAYS_OUT},
    **{f'copied_{event}': _gate(COPIED, event) for event in WAYS_OUT},
    **{f'asked_{event}': _gate(ASKED, event) for event in WAYS_OUT},
    'fresh_leave_anyway': _gate(FRESH, 'leave-anyway'),
    'asked_leave_anyway': _gate(ASKED, 'leave-anyway'),
    'asked_stay': _gate(ASKED, 'stay'),
    'fresh_copied': _gate(FRESH, 'copied'),
    'fresh_copy_failed': _gate(FRESH, 'copy-failed'),
    'failed_then_out': _gate(FAILED, 'save-confirmed'),
    'failed_then_copied': _gate(FAILED, 'copied'),
}


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


class TestNothingClosesTheDialogWhileTheValueIsUncopied:
    @pytest.mark.parametrize('way_out', WAYS_OUT)
    def test_no_way_out_closes_it_on_its_own(self, gate, way_out):
        assert gate[f'fresh_{way_out}']['closes'] is False, (
            f'«{way_out}» cerró el diálogo con la clave sin copiar: el dato se perdió sin que '
            'nadie lo decidiera.'
        )

    @pytest.mark.parametrize('way_out', WAYS_OUT)
    def test_every_way_out_opens_the_same_question(self, gate, way_out):
        """
        Una sola compuerta para las tres. Si `Escape` o el «Atrás» del navegador
        salieran por otro lado, la protección del botón no protegería nada.
        """
        assert gate[f'fresh_{way_out}']['state']['gate'] == 'leaving'

    def test_the_only_way_out_without_copying_is_the_second_click(self, gate):
        assert gate['asked_leave_anyway']['closes'] is True

    def test_that_second_click_exists_only_after_the_question(self, gate):
        """
        `leave-anyway` es lo que dice «Cerrar igual», y ese botón sólo se dibuja
        con la pregunta en pantalla: el evento no llega antes.
        """
        assert gate['fresh_leave_anyway']['closes'] is True
        assert gate['fresh_leave_anyway']['state']['gate'] == 'holding'

    def test_going_back_from_the_question_keeps_the_value_on_screen(self, gate):
        assert gate['asked_stay']['closes'] is False
        assert gate['asked_stay']['state']['gate'] == 'holding'
        assert gate['asked_stay']['state']['copied'] is False

    @pytest.mark.parametrize('way_out', WAYS_OUT)
    def test_asking_twice_does_not_close_it_either(self, gate, way_out):
        """
        Apretar «Ya la guardé» dos veces, o `Escape` dos veces, no puede ser un
        atajo a la salida: la pregunta se contesta con su propio botón.
        """
        assert gate[f'asked_{way_out}']['closes'] is False


class TestCopyingIsWhatOpensTheExit:
    @pytest.mark.parametrize('way_out', WAYS_OUT)
    def test_with_the_value_copied_any_way_out_closes_in_one_step(self, gate, way_out):
        """
        Un clic si hiciste lo correcto, dos si no. La fricción tiene que caer
        sobre el caso de pérdida y sobre ningún otro, o se vuelve paisaje.
        """
        assert gate[f'copied_{way_out}']['closes'] is True

    def test_copying_never_closes_the_dialog_by_itself(self, gate):
        """
        Cerrarse al copiar sería decidir por la persona que ya lo pegó donde
        tenía que pegarlo.
        """
        assert gate['fresh_copied']['closes'] is False
        assert gate['fresh_copied']['state']['copied'] is True


class TestAClipboardThatFailedIsNotAValueThatWasSaved:
    """
    El caso que parece un detalle y es el que pierde la clave.

    Antes el fallo del portapapeles se tragaba en silencio: el icono no cambiaba
    y no había forma de distinguir «no lo apretaste» de «no se pudo». Si además
    contara como copiado, la salida se abriría de un clic sobre un valor que
    nadie tiene.
    """

    def test_a_failed_copy_does_not_count_as_copied(self, gate):
        assert gate['fresh_copy_failed']['state']['copied'] is False

    def test_a_failed_copy_says_so(self, gate):
        assert gate['fresh_copy_failed']['state']['clipboardFailed'] is True

    def test_a_failed_copy_does_not_close_the_dialog(self, gate):
        assert gate['fresh_copy_failed']['closes'] is False

    def test_after_a_failure_the_exit_is_still_gated(self, gate):
        assert gate['failed_then_out']['closes'] is False
        assert gate['failed_then_out']['state']['gate'] == 'leaving'

    def test_copying_after_a_failure_clears_the_warning(self, gate):
        """
        El aviso tiene que irse cuando deja de ser cierto: un mensaje de error
        que sobrevive al arreglo hace dudar de todo lo demás de la pantalla.
        """
        assert gate['failed_then_copied']['state']['copied'] is True
        assert gate['failed_then_copied']['state']['clipboardFailed'] is False
