"""Tests del filtro `only` de build_document (selección de capítulos).

Verifican que:
  - only=None mantiene el comportamiento histórico (todos los capítulos).
  - only=[ids] restringe el CUERPO a esos ids, pero portada (primera) y glosario
    (última) están SIEMPRE presentes.
  - only=[] produce el documento mínimo (solo portada + glosario).
  - la selección también viaja por la clave reservada ctx['_only_chapters']
    (el canal que usan los renderers, que llaman build_document sin `only`), y
    esa clave nunca se filtra a los capítulos.
"""

import os
import sys

_HERE = os.path.dirname(os.path.abspath(__file__))
_FUNCTIONS = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))  # python/functions
if _FUNCTIONS not in sys.path:
    sys.path.insert(0, _FUNCTIONS)

from datascience.automatic_eda import build_document  # noqa: E402


def _profile_with_cat_and_num():
    """Perfil mínimo que hace construir cat_distr y num_distr (cuerpo no vacío)."""
    return {
        "table": "ventas", "n_rows": 120, "n_cols": 2, "quality_score": 91,
        "duplicate_pct": 1.5, "null_cell_pct": 0.8,
        "columns": [
            {"name": "region", "inferred_type": "categorical",
             "categorical": {
                 "top": [{"value": "norte", "count": 50, "pct": 0.42},
                         {"value": "sur", "count": 40, "pct": 0.33},
                         {"value": "este", "count": 30, "pct": 0.25}],
                 "mode": "norte", "n_distinct": 3, "entropy": 1.55,
                 "imbalance": 0.1}},
            {"name": "importe", "inferred_type": "numeric",
             "numeric": {"mean": 50.0, "median": 48.0, "std": 10.0,
                         "min": 10, "max": 99, "iqr": 15,
                         "histogram": [{"lo": 0, "hi": 50, "count": 40},
                                       {"lo": 50, "hi": 100, "count": 80}]}},
        ],
    }


def test_only_none_is_full_document():
    """Retro-compat: sin `only`, salen todos los capítulos aplicables."""
    chs = build_document(_profile_with_cat_and_num(), ctx={"dataset_name": "v"})
    ids = [c.id for c in chs]
    assert ids[0] == "portada"
    assert ids[-1] == "glosario"
    # El cuerpo trae las distribuciones (cat/num), no solo portada+glosario.
    assert "num_distr" in ids
    assert "cat_distr" in ids


def test_only_restricts_body_but_keeps_cover_and_glossary():
    # cat_distr registra el término "entropía" en el glosario, así que el
    # glosario (destino del término clicable) aparece — demuestra el contrato
    # "portada primera + capítulo + glosario última".
    chs = build_document(_profile_with_cat_and_num(),
                         ctx={"dataset_name": "v"}, only=["cat_distr"])
    ids = [c.id for c in chs]
    assert ids[0] == "portada", f"portada no es la primera: {ids}"
    assert ids[-1] == "glosario", f"glosario no es la última: {ids}"
    assert "cat_distr" in ids
    # num_distr quedó fuera de la selección.
    assert "num_distr" not in ids


def test_only_empty_yields_minimal_document():
    # only=[] -> cuerpo vacío. La portada está siempre; el glosario solo aparece
    # si algún capítulo registró términos (patrón preexistente: glosario vacío se
    # omite). Sin cuerpo no hay términos → documento mínimo = solo portada.
    chs = build_document(_profile_with_cat_and_num(),
                         ctx={"dataset_name": "v"}, only=[])
    ids = [c.id for c in chs]
    assert ids == ["portada"], \
        f"only=[] debe dar el documento mínimo (solo portada), no {ids}"


def test_selection_via_reserved_ctx_key():
    """La selección viaja por ctx['_only_chapters'] cuando no se pasa `only`."""
    chs = build_document(_profile_with_cat_and_num(),
                         ctx={"dataset_name": "v",
                              "_only_chapters": ["cat_distr"]})
    ids = [c.id for c in chs]
    assert "cat_distr" in ids
    assert "num_distr" not in ids
    assert ids[0] == "portada" and ids[-1] == "glosario"


def test_explicit_only_arg_wins_over_ctx_key():
    """Si se pasan ambos, el argumento `only` manda sobre la clave del ctx."""
    chs = build_document(_profile_with_cat_and_num(),
                         ctx={"dataset_name": "v",
                              "_only_chapters": ["cat_distr"]},
                         only=["num_distr"])
    ids = [c.id for c in chs]
    assert "num_distr" in ids
    assert "cat_distr" not in ids


def test_reserved_key_not_leaked_to_caller_ctx():
    """build_document no muta el ctx del caller (copia interna)."""
    ctx = {"dataset_name": "v", "_only_chapters": ["num_distr"]}
    build_document(_profile_with_cat_and_num(), ctx=ctx)
    # La clave reservada sigue en el dict del caller (no se mutó su copia).
    assert ctx["_only_chapters"] == ["num_distr"]
