"""Tests para correlation_matrix_duckdb."""

import os
import sys

import duckdb

# Permitir importar funciones del registry (from infra import ..., from datascience import ...).
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "functions"))

from datascience.correlation_matrix_duckdb import correlation_matrix_duckdb


def _make_db(tmp_name: str) -> str:
    """Crea una DuckDB en /tmp con col_a, col_b=2*col_a (corr ~1) y col_c aleatoria."""
    db = os.path.join("/tmp", tmp_name)
    if os.path.exists(db):
        os.remove(db)
    con = duckdb.connect(db)
    # col_b = 2*col_a => correlacion de Pearson exactamente 1.0.
    # col_c usa un patron pseudo-aleatorio acotado, no perfectamente correlado.
    con.execute(
        "CREATE TABLE m AS "
        "SELECT i AS col_a, 2*i AS col_b, (i*7 + 3) % 11 AS col_c "
        "FROM range(200) t(i)"
    )
    con.close()
    return db


def test_correla_dos_columnas_linealmente_dependientes_y_aparece_en_strong():
    db = _make_db("corr_test_strong.duckdb")
    res = correlation_matrix_duckdb(db, "m")
    assert res["status"] == "ok", res
    # col_a y col_b son linealmente dependientes -> corr ~1.0.
    assert abs(res["matrix"]["col_a"]["col_b"] - 1.0) < 1e-9
    assert abs(res["matrix"]["col_b"]["col_a"] - 1.0) < 1e-9
    # El par (a, b) debe aparecer en strong (abs(corr) >= 0.7).
    strong_pairs = {frozenset((p["a"], p["b"])) for p in res["strong"]}
    assert frozenset(("col_a", "col_b")) in strong_pairs
    # strong ordenado por abs(corr) descendente.
    abs_vals = [abs(p["corr"]) for p in res["strong"]]
    assert abs_vals == sorted(abs_vals, reverse=True)
    os.remove(db)


def test_columna_constante_no_rompe_y_queda_fuera_de_strong():
    db = os.path.join("/tmp", "corr_test_const.duckdb")
    if os.path.exists(db):
        os.remove(db)
    con = duckdb.connect(db)
    # col_k es constante => corr() = NULL para cualquier par que la incluya.
    con.execute(
        "CREATE TABLE m AS "
        "SELECT i AS col_a, 2*i AS col_b, 42 AS col_k "
        "FROM range(50) t(i)"
    )
    con.close()

    res = correlation_matrix_duckdb(db, "m")
    assert res["status"] == "ok", res
    # La columna constante produce corr None, no rompe.
    assert res["matrix"]["col_a"]["col_k"] is None
    assert res["matrix"]["col_k"]["col_b"] is None
    # Diagonal sigue siendo 1.0.
    assert res["matrix"]["col_k"]["col_k"] == 1.0
    # Ningun par con corr None entra en strong.
    for p in res["strong"]:
        assert p["corr"] is not None
    # El par correlado a-b sigue presente en strong.
    strong_pairs = {frozenset((p["a"], p["b"])) for p in res["strong"]}
    assert frozenset(("col_a", "col_b")) in strong_pairs
    os.remove(db)


def test_menos_de_dos_columnas_numericas_devuelve_error():
    db = os.path.join("/tmp", "corr_test_few.duckdb")
    if os.path.exists(db):
        os.remove(db)
    con = duckdb.connect(db)
    con.execute("CREATE TABLE m AS SELECT i AS col_a, 'x' AS label FROM range(10) t(i)")
    con.close()

    res = correlation_matrix_duckdb(db, "m")
    assert res["status"] == "error", res
    assert "at least 2 numeric columns" in res["error"]
    os.remove(db)


def test_columns_explicitas_respetan_orden_y_matriz_simetrica():
    db = _make_db("corr_test_explicit.duckdb")
    res = correlation_matrix_duckdb(db, "m", columns=["col_c", "col_a", "col_b"])
    assert res["status"] == "ok", res
    assert res["columns"] == ["col_c", "col_a", "col_b"]
    # Matriz simetrica.
    for a in res["columns"]:
        for b in res["columns"]:
            assert res["matrix"][a][b] == res["matrix"][b][a]
    # pairs contiene cada pareja i<j una sola vez: C(3,2) = 3.
    assert len(res["pairs"]) == 3
    os.remove(db)


def test_tabla_invalida_devuelve_error():
    res = correlation_matrix_duckdb("/tmp/nope.duckdb", "drop table; --")
    assert res["status"] == "error"
    assert "invalid table identifier" in res["error"]
