"""Tests para summarize_outlier_dims."""

from isolation_forest_outliers import isolation_forest_outliers
from summarize_outlier_dims import summarize_outlier_dims


# Dataset compartido: 3 columnas, 13 filas. La fila ORIGINAL 6 tiene None en "a"
# (se descarta), de modo que la fila ORIGINAL 10 -- con un valor extremo en "c"
# -- queda en el indice VALIDO 9 (no 10). Esto verifica el salto de None.
A = [0.1, 0.2, -0.1, 0.0, 0.3, -0.2, None, 0.15, -0.05, 0.25, 0.2, -0.3, 0.1]
B = [1.0, 1.1, 0.9, 1.2, 0.8, 1.0, 1.3, 1.1, 0.95, 1.05, 0.9, 1.15, 1.0]
C = [5.0, 5.2, 4.8, 5.1, 4.9, 5.0, 5.3, 4.95, 5.05, 4.9, 500.0, 5.1, 5.0]
RAW = {"a": A, "b": B, "c": C}

# Mapa original -> valido (saltando original 6):
#   orig: 0 1 2 3 4 5 7 8 9 10 11 12
#  valid: 0 1 2 3 4 5 6 7 8  9 10 11
# => el extremo en "c" (original 10) esta en el indice valido 9.
EXTREME_VALID_INDEX = 9


def test_row_index_skips_none_rows():
    # Mapeo directo (sin depender de la aleatoriedad de IsolationForest): el
    # indice valido 9 debe corresponder a la fila con c == 500 -> el None de la
    # fila original 6 se salto correctamente.
    summary = summarize_outlier_dims(
        RAW, [{"row_index": EXTREME_VALID_INDEX, "score": -0.5}], top_k=3
    )
    assert len(summary) == 1
    entry = summary[0]
    assert entry["row_index"] == EXTREME_VALID_INDEX
    assert entry["score"] == -0.5
    # La dimension dominante es "c", con su valor extremo y |z| alto.
    top = entry["dims"][0]
    assert top["col"] == "c"
    assert top["value"] == 500.0
    assert abs(top["z"]) > 2.0
    # top_k respetado: como mucho 3 dims.
    assert len(entry["dims"]) <= 3


def test_extreme_row_flagged_via_isolation():
    # Integracion real: detectar outliers y explicarlos.
    result = isolation_forest_outliers(RAW, contamination=0.1)
    assert "note" not in result
    outlier_rows = result["outlier_rows"]
    assert outlier_rows  # al menos un outlier

    summary = summarize_outlier_dims(RAW, outlier_rows, top_k=3)
    # Paralela a outlier_rows (todos los indices estan en rango).
    assert len(summary) == len(outlier_rows)

    by_index = {e["row_index"]: e for e in summary}
    # El punto extremo debe estar entre los outliers detectados...
    assert EXTREME_VALID_INDEX in by_index
    # ...y su dimension top debe ser "c" (donde se desvia ~muchas sigmas).
    extreme = by_index[EXTREME_VALID_INDEX]
    assert extreme["dims"][0]["col"] == "c"
    assert abs(extreme["dims"][0]["z"]) > 2.0


def test_out_of_range_row_index_is_ignored():
    # Indices fuera de rango se omiten en lugar de petar.
    summary = summarize_outlier_dims(
        RAW,
        [
            {"row_index": 999, "score": -1.0},
            {"row_index": -1, "score": -1.0},
            {"row_index": EXTREME_VALID_INDEX, "score": -0.5},
        ],
        top_k=2,
    )
    # Solo sobrevive el indice valido; los otros dos se descartan.
    assert len(summary) == 1
    assert summary[0]["row_index"] == EXTREME_VALID_INDEX
    assert len(summary[0]["dims"]) <= 2


def test_degrades_to_empty_on_invalid_inputs():
    # raw_numeric vacio + outlier_rows vacio.
    assert summarize_outlier_dims({}, [], 3) == []
    # raw_numeric no es dict.
    assert summarize_outlier_dims("not a dict", [{"row_index": 0}], 3) == []
    # outlier_rows no es lista.
    assert summarize_outlier_dims(RAW, "not a list", 3) == []
    # Sin columnas numericas (todas con strings) -> [].
    assert summarize_outlier_dims(
        {"s": ["x", "y", "z"]}, [{"row_index": 0, "score": -1.0}], 3
    ) == []
    # Entradas malformadas dentro de outlier_rows se ignoran (no petan).
    assert summarize_outlier_dims(
        RAW, ["nope", 42, {"no_row_index": 1}], 3
    ) == []
