"""Tests para generate_synthetic_eda_table."""

import os
import re
import statistics

import duckdb

from datascience.generate_synthetic_eda_table import generate_synthetic_eda_table

_EXPECTED_COLS = [
    "customer_id", "email", "iban", "phone", "income", "spending", "age",
    "risk_score", "tenure_months", "engagement_quad", "amount", "n_purchases",
    "country", "category", "plan", "review", "signup_date", "latitude", "longitude",
]
_PHONE_RE = re.compile(r"\+\d[\d\s()-]{6,}\d")


def _load(db_path, table="synthetic"):
    con = duckdb.connect(db_path, read_only=True)
    try:
        return con.execute(f'SELECT * FROM "{table}"').fetch_df()
    finally:
        con.close()


def test_genera_ok_y_columnas(tmp_path):
    db = str(tmp_path / "t.duckdb")
    res = generate_synthetic_eda_table(db, "synthetic", n_rows=500, seed=42)
    assert res["status"] == "ok"
    assert res["table"] == "synthetic"
    assert res["n_rows"] == 500
    assert res["columns"] == _EXPECTED_COLS
    assert os.path.exists(db)
    df = _load(db)
    assert list(df.columns) == _EXPECTED_COLS
    assert len(df) == 500


def test_determinismo_mismo_seed(tmp_path):
    db1 = str(tmp_path / "a.duckdb")
    db2 = str(tmp_path / "b.duckdb")
    generate_synthetic_eda_table(db1, "synthetic", n_rows=400, seed=7)
    generate_synthetic_eda_table(db2, "synthetic", n_rows=400, seed=7)
    df1 = _load(db1).astype(str)
    df2 = _load(db2).astype(str)
    # Misma semilla -> tabla identica fila a fila.
    assert df1.equals(df2)


def test_seeds_distintos_difieren(tmp_path):
    db1 = str(tmp_path / "a.duckdb")
    db2 = str(tmp_path / "b.duckdb")
    generate_synthetic_eda_table(db1, "synthetic", n_rows=400, seed=7)
    generate_synthetic_eda_table(db2, "synthetic", n_rows=400, seed=8)
    df1 = _load(db1).astype(str)
    df2 = _load(db2).astype(str)
    assert not df1.equals(df2)


def test_latlon_en_rango(tmp_path):
    db = str(tmp_path / "t.duckdb")
    generate_synthetic_eda_table(db, "synthetic", n_rows=500, seed=42)
    df = _load(db)
    assert df["latitude"].between(-90, 90).all()
    assert df["longitude"].between(-180, 180).all()


def test_plan_solo_niveles_validos(tmp_path):
    db = str(tmp_path / "t.duckdb")
    generate_synthetic_eda_table(db, "synthetic", n_rows=500, seed=42)
    df = _load(db)
    assert set(df["plan"].unique()) <= {"baja", "media", "alta"}


def test_income_spending_co_nulos(tmp_path):
    db = str(tmp_path / "t.duckdb")
    generate_synthetic_eda_table(db, "synthetic", n_rows=600, seed=42)
    df = _load(db)
    inc_null = df["income"].isna()
    sp_null = df["spending"].isna()
    # income y spending faltan exactamente en las MISMAS filas.
    assert (inc_null == sp_null).all()
    assert inc_null.sum() > 0


def test_review_mediana_palabras_y_signup_datetime(tmp_path):
    db = str(tmp_path / "t.duckdb")
    generate_synthetic_eda_table(db, "synthetic", n_rows=500, seed=42)
    df = _load(db)
    words = [len(str(r).split()) for r in df["review"].dropna()]
    assert statistics.median(words) >= 20
    # signup_date debe ser datetime/date en DuckDB (no VARCHAR).
    con = duckdb.connect(db, read_only=True)
    try:
        dtype = con.execute(
            "SELECT column_type FROM (DESCRIBE synthetic) WHERE column_name='signup_date'"
        ).fetchone()[0]
    finally:
        con.close()
    assert dtype.upper().startswith(("DATE", "TIMESTAMP"))


def test_phone_matchea_regex_internacional(tmp_path):
    db = str(tmp_path / "t.duckdb")
    generate_synthetic_eda_table(db, "synthetic", n_rows=500, seed=42)
    df = _load(db)
    phones = [p for p in df["phone"].tolist() if p is not None]
    assert all(_PHONE_RE.fullmatch(str(p)) for p in phones)


def test_outliers_y_correlaciones(tmp_path):
    db = str(tmp_path / "t.duckdb")
    generate_synthetic_eda_table(db, "synthetic", n_rows=800, seed=42)
    df = _load(db)
    # amount tiene cola con outliers altos evidentes.
    assert df["amount"].max() > df["amount"].median() * 20
    # correlacion positiva fuerte income~spending y negativa age~risk_score.
    sub = df[["income", "spending"]].dropna()
    assert sub["income"].corr(sub["spending"]) > 0.8
    sub2 = df[["age", "risk_score"]].dropna()
    assert sub2["age"].corr(sub2["risk_score"]) < -0.6


def test_tabla_invalida_devuelve_error(tmp_path):
    db = str(tmp_path / "t.duckdb")
    res = generate_synthetic_eda_table(db, "bad name;", n_rows=10, seed=42)
    assert res["status"] == "error"
    assert "invalido" in res["error"]
