"""
Puente para ejercitar el código de la interfaz desde la suite de Python.

El producto no tiene corredor de tests de JavaScript y no se agrega uno acá: lo
que hace falta es alcanzar unas pocas reglas de dominio escritas en TypeScript, y
para eso alcanza con Node, el `typescript` que ya está instalado y el gancho de
`loader.mjs`. Comprobarlas leyendo el archivo con una expresión regular sería
verificar el texto del arreglo en vez de su conducta, que es justo lo que estas
reglas necesitan que se verifique: el ancho del tramo que se dibuja y la oración
que sale abajo no se leen desde el código fuente.

Los pedidos se juntan y se resuelven en **una** corrida: levantar Node por cada
comprobación cuesta cerca de un segundo por vez, y la suite terminaría midiendo
el arranque de un proceso en vez de una regla del producto.

Si falta Node o falta `npm install`, los tests se saltean con el motivo escrito:
un entorno sin la mitad de JavaScript instalada no puede afirmar nada sobre ella,
y hacerlo fallar ahí escondería el resultado de los que sí corrieron.
"""

import json
import os
import shutil
import subprocess
from pathlib import Path

import pytest
from django.conf import settings

PROJECT_ROOT = Path(settings.BASE_DIR)
HERE = Path(__file__).parent


def _reason_it_cannot_run() -> str | None:
    if shutil.which('node') is None:
        return 'Node no está disponible: la mitad de JavaScript no se puede ejercitar.'
    if not (PROJECT_ROOT / 'node_modules' / 'typescript').exists():
        return 'Faltan las dependencias de JavaScript. Corré `npm install`.'
    return None


#: Marca para las pruebas que necesitan la mitad de JavaScript del proyecto.
needs_frontend = pytest.mark.skipif(
    _reason_it_cannot_run() is not None, reason=_reason_it_cannot_run() or ''
)


def phrase(key: str, locale: str = 'en', **params) -> str:
    """
    La frase que el catálogo tiene bajo una clave, con sus datos adentro.

    Existe para que ninguna prueba de pantalla escriba una oración del producto.
    Comparar contra prosa copiada ataba la suite al idioma que tenía el archivo
    el día que se escribió, y una redacción retocada la rompía sin que nada
    estuviera mal. Comparando contra el catálogo, el test dice qué frase espera
    —por su clave— y sigue valiendo en los dos idiomas.

    Falla fuerte ante una clave que no existe, igual que `t()` en el navegador:
    una prueba que compara contra la clave cruda pasa por casualidad.
    """
    path = PROJECT_ROOT / 'frontend' / 'locales' / f'{locale}.json'
    catalog = json.loads(path.read_text(encoding='utf-8'))

    if key not in catalog:
        raise AssertionError(f'La clave «{key}» no está en el catálogo de «{locale}».')

    line = catalog[key]
    for name, value in params.items():
        line = line.replace(f'{{{name}}}', str(value))
    return line


def render(module: str, component: str, **props) -> dict:
    """Pedido de dibujar un componente: lo que sale es el marcado que ve la persona."""
    return {'module': module, 'render': component, 'props': props}


def call(module: str, function: str, *args) -> dict:
    """Pedido de llamar a una función del módulo y quedarse con lo que contestó."""
    return {'module': module, 'call': function, 'args': list(args)}


def resolve(requests: dict[str, dict], locale: str = 'en') -> dict[str, object]:
    """
    Resuelve todos los pedidos en una sola corrida de Node, por nombre.

    El idioma es inglés salvo que se pida otro. Es el idioma fuente del producto
    y el que la suite defiende; pedir la traducción es lo excepcional, y sirve
    para comprobar que una pantalla habla en el idioma de la cuenta y no en el
    que quedó escrito en el archivo.
    """
    result = subprocess.run(  # noqa: S603
        [shutil.which('node') or 'node', '--import', './register.mjs', 'run.mjs'],
        cwd=HERE,
        input=json.dumps(requests),
        capture_output=True,
        text=True,
        encoding='utf-8',
        env={
            **os.environ,
            'PROJECT_ROOT': str(PROJECT_ROOT),
            'FRONTEND_ROOT': str(PROJECT_ROOT / 'frontend'),
            'LOCALE': locale,
        },
        timeout=180,
    )

    if result.returncode != 0:
        raise AssertionError(f'La interfaz no se pudo ejecutar:\n{result.stderr}')

    return json.loads(result.stdout)
