"""Tests para detect_latlon_columns."""

import os
import sys

sys.path.insert(0, os.path.dirname(__file__))

from detect_latlon_columns import detect_latlon_columns

# Keys that every result dict (success or failure) must expose.
_EXPECTED_KEYS = {"lat_col", "lon_col", "confidence", "reason"}


def _col(name, mn=None, mx=None, inferred="numeric"):
    """Build a minimal ColumnProfile-like dict for the tests."""
    col = {"name": name, "inferred_type": inferred}
    if mn is not None or mx is not None:
        col["numeric"] = {"min": mn, "max": mx}
    return col


def test_par_latitude_longitude_fuerte():
    """Golden: nombres latitude/longitude con rango valido -> par con confianza alta."""
    columns = [
        _col("id", mn=1, mx=9999, inferred="numeric"),
        _col("latitude", mn=-45.0, mx=45.0),
        _col("longitude", mn=-120.0, mx=120.0),
    ]
    res = detect_latlon_columns(columns)

    assert set(res.keys()) == _EXPECTED_KEYS
    assert res["lat_col"] == "latitude"
    assert res["lon_col"] == "longitude"
    # Nombre fuerte (0.6) + rango (0.4) en ambos lados -> 1.0.
    assert abs(res["confidence"] - 1.0) < 1e-9
    assert "rango valido" in res["reason"]


def test_par_lat_lon_abreviado():
    """Golden: nombres abreviados lat/lon tambien se detectan como fuertes."""
    columns = [
        _col("lat", mn=40.0, mx=43.0),
        _col("lon", mn=-4.0, mx=-1.0),
        _col("precio", mn=0.0, mx=500.0),
    ]
    res = detect_latlon_columns(columns)
    assert res["lat_col"] == "lat"
    assert res["lon_col"] == "lon"
    assert abs(res["confidence"] - 1.0) < 1e-9


def test_par_x_y_debil_con_rango_valido():
    """Edge: x/y genericos solo cuentan como par debil cuando el rango encaja."""
    columns = [
        _col("y_coord", mn=-10.0, mx=10.0),   # debil latitud
        _col("x_coord", mn=-150.0, mx=150.0),  # debil longitud
    ]
    res = detect_latlon_columns(columns)
    assert res["lat_col"] == "y_coord"
    assert res["lon_col"] == "x_coord"
    # Nombre debil (0.3) + rango (0.4) -> 0.7 en ambos lados.
    assert abs(res["confidence"] - 0.7) < 1e-9


def test_nombre_lat_lon_pero_rango_fuera_no_detecta():
    """Edge: nombre lat/lon con rango fuera de bounds -> NO es coordenada."""
    columns = [
        _col("latitude", mn=-200.0, mx=200.0),   # fuera de [-90, 90]
        _col("longitude", mn=-120.0, mx=120.0),  # valido, pero sin par lat
    ]
    res = detect_latlon_columns(columns)
    assert res["lat_col"] is None
    assert res["lon_col"] is None
    assert res["confidence"] == 0.0
    assert isinstance(res["reason"], str) and res["reason"]


def test_par_fuerte_prevalece_sobre_debil():
    """Edge: con candidatos fuertes y debiles, gana el par de mayor confianza."""
    columns = [
        _col("latitude", mn=-45.0, mx=45.0),    # fuerte lat
        _col("y", mn=-30.0, mx=30.0),           # debil lat
        _col("longitude", mn=-120.0, mx=120.0),  # fuerte lon
        _col("x", mn=-100.0, mx=100.0),         # debil lon
    ]
    res = detect_latlon_columns(columns)
    assert res["lat_col"] == "latitude"
    assert res["lon_col"] == "longitude"
    assert abs(res["confidence"] - 1.0) < 1e-9


def test_entradas_vacias_o_invalidas_no_lanzan():
    """Edge: sin columnas / vacio / no-lista / entradas no-dict -> dict None sin lanzar."""
    for bad in ([], None, "no soy lista", 42, [1, 2, 3], [{}], [{"foo": "bar"}]):
        res = detect_latlon_columns(bad)
        assert set(res.keys()) == _EXPECTED_KEYS
        assert res["lat_col"] is None
        assert res["lon_col"] is None
        assert res["confidence"] == 0.0
        assert isinstance(res["reason"], str)


def test_solo_latitud_sin_longitud_no_detecta():
    """Edge: solo hay latitud valida, falta la longitud -> sin par."""
    columns = [
        _col("latitude", mn=-45.0, mx=45.0),
        _col("temperatura", mn=-5.0, mx=40.0),
    ]
    res = detect_latlon_columns(columns)
    assert res["lat_col"] is None
    assert res["lon_col"] is None
    assert res["confidence"] == 0.0


def test_deteccion_por_samples_cuando_falta_numeric():
    """Edge: sin bloque numeric, el rango se valida con samples."""
    columns = [
        {"name": "lat"},  # sin numeric ni inferred_type
        {"name": "lon"},
    ]
    samples = {
        "lat": [10.5, 20.0, None, 30.25],   # todos dentro de [-90, 90]
        "lon": [-40.0, 50.5, 60.0],          # todos dentro de [-180, 180]
    }
    res = detect_latlon_columns(columns, samples)
    assert res["lat_col"] == "lat"
    assert res["lon_col"] == "lon"
    assert abs(res["confidence"] - 1.0) < 1e-9


def test_samples_fuera_de_rango_descarta():
    """Edge: samples fuera de bounds invalidan la columna pese al nombre fuerte."""
    columns = [{"name": "lat"}, {"name": "lon"}]
    samples = {
        "lat": [10.0, 95.0],   # 95 > 90 -> latitud invalida
        "lon": [-40.0, 50.0],
    }
    res = detect_latlon_columns(columns, samples)
    assert res["lat_col"] is None
    assert res["lon_col"] is None
    assert res["confidence"] == 0.0
