"""Tests para suggest_intratable_fk_candidates (funcion pura, sin I/O)."""

from suggest_intratable_fk_candidates import suggest_intratable_fk_candidates


def _col(name, inferred_type="numeric", physical_type="BIGINT", distinct_count=10,
         unique_pct=0.1, flags=None):
    """Construye un ColumnProfile minimo a mano (el dict que emite profile_table)."""
    return {
        "name": name,
        "inferred_type": inferred_type,
        "physical_type": physical_type,
        "semantic_type": "",
        "distinct_count": distinct_count,
        "unique_pct": unique_pct,
        "null_count": 0,
        "null_pct": 0.0,
        "flags": list(flags) if flags else [],
    }


def test_golden_customer_id_detectado_otras_no():
    # Tabla tipo titanic: customer_id es FK N:1; id es la PK; amount es medida;
    # name es categorica sin sufijo de id. Solo customer_id debe aparecer.
    profile = {
        "n_rows": 891,
        "key_candidates": ["id"],
        "columns": [
            _col("id", inferred_type="numeric", physical_type="BIGINT",
                 distinct_count=891, unique_pct=1.0, flags=["possible_id"]),
            _col("customer_id", inferred_type="numeric", physical_type="BIGINT",
                 distinct_count=137, unique_pct=0.15, flags=[]),
            _col("amount", inferred_type="numeric", physical_type="DOUBLE",
                 distinct_count=400, unique_pct=0.45),
            _col("name", inferred_type="categorical", physical_type="VARCHAR",
                 distinct_count=700, unique_pct=0.78),
        ],
    }
    out = suggest_intratable_fk_candidates(profile)
    assert isinstance(out, list)
    assert [c["column"] for c in out] == ["customer_id"]
    cand = out[0]
    assert cand["ref_table_guess"] == "customer"
    assert cand["distinct_count"] == 137
    assert cand["unique_pct"] == 0.15
    assert cand["inferred_type"] == "numeric"
    assert cand["physical_type"] == "BIGINT"
    # La razon deja claro que es heuristica + cita el sufijo y la tabla.
    assert "customer" in cand["reason"]
    assert "_id" in cand["reason"]


def test_camelcase_albumid_detectado():
    # AlbumId (camelCase, VARCHAR) -> detectada, ref_table_guess "album".
    profile = {
        "n_rows": 3503,
        "key_candidates": ["TrackId"],
        "columns": [
            _col("AlbumId", inferred_type="categorical", physical_type="VARCHAR",
                 distinct_count=347, unique_pct=0.10),
        ],
    }
    out = suggest_intratable_fk_candidates(profile)
    # TrackId es PK candidata (en key_candidates), AlbumId no -> AlbumId aparece.
    assert [c["column"] for c in out] == ["AlbumId"]
    assert out[0]["ref_table_guess"] == "album"


def test_constante_status_id_no_aparece():
    # status_id constante (flag "constant", distinct_count 1) NO es FK util.
    profile = {
        "n_rows": 1000,
        "key_candidates": [],
        "columns": [
            _col("status_id", inferred_type="numeric", physical_type="INTEGER",
                 distinct_count=1, unique_pct=0.001, flags=["constant"]),
        ],
    }
    out = suggest_intratable_fk_candidates(profile)
    assert out == []


def test_profile_vacio_y_none_devuelven_lista_vacia():
    # Lectura defensiva: ni {} ni None lanzan; devuelven [].
    assert suggest_intratable_fk_candidates({}) == []
    assert suggest_intratable_fk_candidates(None) == []
    # profile sin columns o con columns no-lista tampoco lanza.
    assert suggest_intratable_fk_candidates({"n_rows": 10}) == []
    assert suggest_intratable_fk_candidates({"columns": "no-soy-lista"}) == []


def test_category_id_casi_unico_parece_pk_no_aparece():
    # unique_pct 0.999 -> parece PK (no N:1) -> NO se sugiere como FK.
    profile = {
        "n_rows": 891,
        "key_candidates": [],
        "columns": [
            _col("category_id", inferred_type="numeric", physical_type="BIGINT",
                 distinct_count=890, unique_pct=0.999),
        ],
    }
    out = suggest_intratable_fk_candidates(profile)
    assert out == []


def test_ref_table_guess_multitoken_y_orden_por_distinct():
    # manager_staff_id conserva los underscores del stem -> "manager_staff".
    # Ademas, con varias candidatas, se ordenan por distinct_count descendente.
    profile = {
        "n_rows": 10000,
        "key_candidates": ["staff_id"],  # staff_id es PK aqui, no debe aparecer
        "columns": [
            _col("staff_id", inferred_type="numeric", physical_type="BIGINT",
                 distinct_count=10000, unique_pct=1.0, flags=["possible_id"]),
            _col("store_id", inferred_type="numeric", physical_type="INTEGER",
                 distinct_count=2, unique_pct=0.0002),
            _col("manager_staff_id", inferred_type="numeric", physical_type="INTEGER",
                 distinct_count=40, unique_pct=0.004),
        ],
    }
    out = suggest_intratable_fk_candidates(profile)
    cols = [c["column"] for c in out]
    # staff_id excluida (PK); las otras dos ordenadas por distinct desc.
    assert cols == ["manager_staff_id", "store_id"]
    refs = {c["column"]: c["ref_table_guess"] for c in out}
    assert refs["manager_staff_id"] == "manager_staff"
    assert refs["store_id"] == "store"


def test_max_candidates_corta_la_lista():
    # max_candidates limita el numero de sugerencias devueltas.
    profile = {
        "n_rows": 10000,
        "key_candidates": [],
        "columns": [
            _col("a_id", distinct_count=300, unique_pct=0.03),
            _col("b_id", distinct_count=200, unique_pct=0.02),
            _col("c_id", distinct_count=100, unique_pct=0.01),
        ],
    }
    out = suggest_intratable_fk_candidates(profile, max_candidates=2)
    assert [c["column"] for c in out] == ["a_id", "b_id"]


def test_id_generico_solo_nunca_es_fk():
    # 'id'/'Id'/'ID' a secas (sin stem) jamas se sugieren como FK.
    profile = {
        "n_rows": 500,
        "key_candidates": [],
        "columns": [
            _col("id", distinct_count=500, unique_pct=1.0),
            _col("Id", distinct_count=120, unique_pct=0.24),
            _col("ID", distinct_count=80, unique_pct=0.16),
        ],
    }
    out = suggest_intratable_fk_candidates(profile)
    assert out == []
