"""Tests para build_boxplots_figure (boxplots horizontales de Tukey, grupo eda).

Usa el backend Agg sin display; no muestra ni guarda figuras. Cada test cierra
explícitamente la Figure construida (matplotlib.pyplot.close) para no acumular
estado entre tests.
"""

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt  # noqa: E402
from matplotlib.figure import Figure  # noqa: E402

from build_boxplots_figure import build_boxplots_figure


def _box(name, q1, median, q3, mn, mx, low=False, high=False, fliers=None):
    """Construye una entrada {name, box, fliers} con un box estilo build_boxplot_stats."""
    iqr = q3 - q1
    return {
        "name": name,
        "box": {
            "q1": q1,
            "median": median,
            "q3": q3,
            "iqr": iqr,
            "lower_fence": q1 - 1.5 * iqr,
            "upper_fence": q3 + 1.5 * iqr,
            "whisker_lo": max(mn, q1 - 1.5 * iqr),
            "whisker_hi": min(mx, q3 + 1.5 * iqr),
            "min": mn,
            "max": mx,
            "has_low_outliers": low,
            "has_high_outliers": high,
            "n_outliers": 0,
        },
        "fliers": fliers,
    }


def test_returns_figure_with_axes():
    boxes = [
        _box("edad", 10.0, 25.0, 40.0, 1.0, 100.0, high=True),
        _box("ingresos", 100.0, 200.0, 300.0, 50.0, 400.0),
        _box("score", -1.0, 0.0, 1.0, -5.0, 5.0, low=True, high=True),
    ]
    fig = build_boxplots_figure(boxes, title="Boxplots", max_boxes=12)
    assert isinstance(fig, Figure)
    assert len(fig.axes) >= 1
    # Tres cajas -> tres etiquetas en el eje Y.
    ax = fig.axes[0]
    assert len(ax.get_yticks()) == 3
    plt.close(fig)


def test_empty_list_returns_placeholder_figure():
    fig = build_boxplots_figure([], title="vacío")
    assert isinstance(fig, Figure)
    assert len(fig.axes) >= 1
    plt.close(fig)


def test_invalid_box_is_skipped_not_raised():
    boxes = [
        {"name": "rota", "box": {"q1": None, "median": None, "q3": None}},
        {"name": "sin_box"},                         # falta la clave box.
        "no_es_dict",                                 # entrada no-dict.
        _box("buena", 1.0, 2.0, 3.0, 0.0, 10.0, high=True),
    ]
    fig = build_boxplots_figure(boxes)
    assert isinstance(fig, Figure)
    ax = fig.axes[0]
    # Solo la caja válida sobrevive al filtrado.
    assert len(ax.get_yticks()) == 1
    plt.close(fig)


def test_all_invalid_returns_placeholder():
    boxes = [
        {"name": "a", "box": {"q1": None, "median": 1.0, "q3": 2.0}},
        {"name": "b"},
    ]
    fig = build_boxplots_figure(boxes)
    assert isinstance(fig, Figure)
    assert len(fig.axes) >= 1
    plt.close(fig)


def test_raw_fliers_are_drawn():
    boxes = [
        _box("con_fliers", 10.0, 20.0, 30.0, 5.0, 200.0,
             high=True, fliers=[150.0, 180.0, 200.0]),
    ]
    fig = build_boxplots_figure(boxes)
    assert isinstance(fig, Figure)
    assert len(fig.axes) >= 1
    plt.close(fig)


def test_max_boxes_truncates_and_does_not_raise():
    boxes = [_box(f"c{i}", float(i), float(i + 1), float(i + 2),
                  float(i - 5), float(i + 10)) for i in range(20)]
    fig = build_boxplots_figure(boxes, title="muchos", max_boxes=5)
    assert isinstance(fig, Figure)
    ax = fig.axes[0]
    # Solo se dibujan las primeras 5 cajas.
    assert len(ax.get_yticks()) == 5
    plt.close(fig)
