"""Tests para build_eda_render_ctx.

Self-contained: crea un DuckDB temporal pequeño con una columna fecha, varias
numericas y un par lat/lon, construye un TableProfile minimo a mano (con la forma
de columnas del grupo `eda`: name / inferred_type / numeric.{min,max} /
semantic_type) y verifica que el ctx producido contiene las claves de datos que
consumen los capitulos del AutomaticEDA.
"""

import os
import sys

# El test importa funciones del registry como una app del registry: inserta el
# directorio raiz `python/functions` en sys.path y luego `from datascience import`.
_FUNCTIONS_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
if _FUNCTIONS_ROOT not in sys.path:
    sys.path.insert(0, _FUNCTIONS_ROOT)

import duckdb  # noqa: E402

from datascience import build_eda_render_ctx  # noqa: E402

_TABLE = "ventas_geo"
# Filas: fecha creciente, 2 columnas numericas (ventas, unidades) y un par lat/lon
# (Madrid -> lat ~40, lon ~-3, dentro de [-90,90] y [-180,180]).
_ROWS = [
    ("2024-01-01", 1200.5, 12, 40.41, -3.70),
    ("2024-01-02", 980.0, 9, 41.38, 2.17),
    ("2024-01-03", 1500.25, 15, 37.39, -5.99),
    ("2024-01-04", 1100.0, 11, 39.47, -0.38),
    ("2024-01-05", 1750.75, 18, 43.26, -2.93),
]


def _make_db(tmp_path):
    """Crea un DuckDB temporal con la tabla de prueba y devuelve su ruta."""
    db_path = os.path.join(str(tmp_path), "eda_ctx.duckdb")
    con = duckdb.connect(db_path)
    try:
        con.execute(
            f'CREATE TABLE "{_TABLE}" '
            "(fecha DATE, ventas DOUBLE, unidades INTEGER, lat DOUBLE, lon DOUBLE)"
        )
        con.executemany(
            f'INSERT INTO "{_TABLE}" VALUES (?, ?, ?, ?, ?)', _ROWS
        )
    finally:
        con.close()
    return db_path


def _profile_with_date():
    """TableProfile minimo con columna fecha + numericas + lat/lon."""
    return {
        "columns": [
            {"name": "fecha", "inferred_type": "datetime", "semantic_type": "datetime_iso"},
            {
                "name": "ventas",
                "inferred_type": "numeric",
                "semantic_type": "decimal",
                "numeric": {"min": 980.0, "max": 1750.75},
            },
            {
                "name": "unidades",
                "inferred_type": "numeric",
                "semantic_type": "integer",
                "numeric": {"min": 9, "max": 18},
            },
            {
                "name": "lat",
                "inferred_type": "numeric",
                "semantic_type": "decimal",
                "numeric": {"min": 37.39, "max": 43.26},
            },
            {
                "name": "lon",
                "inferred_type": "numeric",
                "semantic_type": "decimal",
                "numeric": {"min": -5.99, "max": 2.17},
            },
        ]
    }


def _profile_without_date():
    """Mismo perfil pero SIN columna temporal (solo numericas)."""
    prof = _profile_with_date()
    prof["columns"] = [c for c in prof["columns"] if c["name"] != "fecha"]
    return prof


def test_db_path_y_table_en_ctx(tmp_path):
    db_path = _make_db(tmp_path)
    ctx = build_eda_render_ctx(db_path, _TABLE, _profile_with_date())
    assert ctx["db_path"] == db_path
    assert ctx["table"] == _TABLE


def test_raw_numeric_con_columnas_numericas(tmp_path):
    db_path = _make_db(tmp_path)
    ctx = build_eda_render_ctx(db_path, _TABLE, _profile_with_date())
    raw = ctx["raw_numeric"]
    # Las 4 columnas numericas (ventas, unidades, lat, lon), listas no vacias y
    # alineadas por fila (misma longitud == nº de filas).
    for col in ("ventas", "unidades", "lat", "lon"):
        assert col in raw
        assert len(raw[col]) == len(_ROWS)
    assert raw["ventas"][0] == 1200.5
    assert raw["unidades"][0] == 12.0  # int promovido a float


def test_timeseries_raw_con_fecha(tmp_path):
    db_path = _make_db(tmp_path)
    ctx = build_eda_render_ctx(db_path, _TABLE, _profile_with_date())
    ts = ctx["timeseries_raw"]
    assert ts["time_col"] == "fecha"
    assert len(ts["t"]) == len(_ROWS)  # fechas ISO no vacias
    # Las numericas aparecen como series paralelas a t.
    for col in ("ventas", "unidades", "lat", "lon"):
        assert col in ts["series"]
        assert len(ts["series"][col]) == len(_ROWS)


def test_geo_points_con_latlon(tmp_path):
    db_path = _make_db(tmp_path)
    ctx = build_eda_render_ctx(db_path, _TABLE, _profile_with_date())
    geo = ctx["geo_points"]
    assert len(geo["lats"]) == len(_ROWS)
    assert len(geo["lons"]) == len(_ROWS)
    # Listas alineadas, ya floats, leidas de raw_numeric.
    assert geo["lats"][0] == 40.41
    assert geo["lons"][0] == -3.70


def test_sin_fecha_no_hay_timeseries(tmp_path):
    db_path = _make_db(tmp_path)
    ctx = build_eda_render_ctx(db_path, _TABLE, _profile_without_date())
    assert "timeseries_raw" not in ctx
    # raw_numeric y geo_points siguen presentes (no dependen de la fecha).
    assert "raw_numeric" in ctx
    assert "geo_points" in ctx


def test_base_ctx_preservado(tmp_path):
    db_path = _make_db(tmp_path)
    base = {"dataset_name": "ventas_geo_demo", "source_origin": "test"}
    ctx = build_eda_render_ctx(db_path, _TABLE, _profile_with_date(), base_ctx=base)
    # Las claves de presentacion del base_ctx no se pisan.
    assert ctx["dataset_name"] == "ventas_geo_demo"
    assert ctx["source_origin"] == "test"
    # Y las de datos se añaden encima.
    assert ctx["db_path"] == db_path
    assert "raw_numeric" in ctx
