"""Tests para fdr_correction (correccion de comparaciones multiples).

Importa el modulo hoja directamente (`datascience.fdr_correction`) para no
depender de que el paquete reexporte la funcion en su __init__ (lo integra el
orquestador al cerrar el grupo eda).
"""

from datascience.fdr_correction import fdr_correction


def test_bh_golden_rechaza_dos_de_tres():
    # Dos p-valores fuertes y uno claramente no significativo.
    # BH (step-up) sobre [0.01, 0.02, 0.5], m=3, alpha=0.05:
    #   q3 = 0.5*3/3 = 0.50
    #   q2 = min(0.50, 0.02*3/2=0.03) = 0.03
    #   q1 = min(0.03, 0.01*3/1=0.03) = 0.03
    # reject = [q<=0.05] -> [True, True, False]
    out = fdr_correction([0.01, 0.02, 0.5], alpha=0.05, method="bh")
    assert out["reject"] == [True, True, False]
    assert out["n_rejected"] == 2
    assert out["n_tests"] == 3
    assert out["method"] == "bh"
    # q-valores esperados.
    adj = out["p_values_adjusted"]
    assert abs(adj[0] - 0.03) < 1e-9
    assert abs(adj[1] - 0.03) < 1e-9
    assert abs(adj[2] - 0.50) < 1e-9


def test_bonferroni_mas_conservador_que_bh():
    pvalues = [0.01, 0.02, 0.5]
    bh = fdr_correction(pvalues, alpha=0.05, method="bh")
    bon = fdr_correction(pvalues, alpha=0.05, method="bonferroni")
    # Bonferroni nunca rechaza mas que BH.
    assert bon["n_rejected"] <= bh["n_rejected"]
    # p ajustado = min(1, p*m): [0.03, 0.06, 1.0] -> solo el primero pasa.
    assert bon["reject"] == [True, False, False]
    assert abs(bon["p_values_adjusted"][0] - 0.03) < 1e-9
    assert abs(bon["p_values_adjusted"][1] - 0.06) < 1e-9
    assert bon["p_values_adjusted"][2] == 1.0


def test_p_values_adjusted_alineados_y_en_rango():
    pvalues = [0.001, 0.2, 0.04, 0.6, 0.9]
    out = fdr_correction(pvalues, method="bh")
    assert len(out["p_values_adjusted"]) == len(pvalues)
    assert len(out["reject"]) == len(pvalues)
    for q in out["p_values_adjusted"]:
        assert q is not None and 0.0 <= q <= 1.0
    # El p-valor ajustado nunca es menor que el crudo (la correccion solo sube).
    for p, q in zip(pvalues, out["p_values_adjusted"]):
        assert q >= p - 1e-12


def test_none_se_propaga_alineado():
    # Posicion central sin test disponible: se propaga como None / False y no
    # cuenta como prueba (m=2, no 3).
    out = fdr_correction([0.001, None, 0.9], alpha=0.05, method="bh")
    assert out["n_tests"] == 2
    assert out["p_values_adjusted"][1] is None
    assert out["reject"][1] is False
    assert out["reject"][0] is True
    assert len(out["reject"]) == 3


def test_lista_vacia_devuelve_note():
    out = fdr_correction([])
    assert out["p_values_adjusted"] == []
    assert out["reject"] == []
    assert out["n_tests"] == 0
    assert out["n_rejected"] == 0
    assert "note" in out


def test_solo_none_devuelve_note():
    out = fdr_correction([None, None, float("nan")])
    assert out["n_tests"] == 0
    assert out["n_rejected"] == 0
    assert out["reject"] == [False, False, False]
    assert out["p_values_adjusted"] == [None, None, None]
    assert "note" in out


def test_metodo_desconocido_devuelve_note():
    # 'holm' ya es un metodo valido (v1.1.0); usamos uno realmente desconocido.
    out = fdr_correction([0.01, 0.02], method="sidak")
    assert "note" in out
    assert out["n_rejected"] == 0
    assert out["reject"] == [False, False]


def test_todos_significativos():
    # Todos los p-valores diminutos -> todos rechazados con ambos metodos.
    pvalues = [1e-6, 1e-5, 1e-4]
    bh = fdr_correction(pvalues, alpha=0.05, method="bh")
    bon = fdr_correction(pvalues, alpha=0.05, method="bonferroni")
    assert bh["n_rejected"] == 3
    assert bon["n_rejected"] == 3
    assert all(bh["reject"])
    assert all(bon["reject"])


def test_holm_golden_rechaza_dos_de_cuatro():
    # Holm-Bonferroni (step-down) sobre [0.01, 0.04, 0.03, 0.005], m=4, alpha=0.05.
    # Ordenado ascendente: 0.005, 0.01, 0.03, 0.04.
    #   padj_(1) = 4*0.005 = 0.02
    #   padj_(2) = max(0.02, 3*0.01=0.03) = 0.03
    #   padj_(3) = max(0.03, 2*0.03=0.06) = 0.06
    #   padj_(4) = max(0.06, 1*0.04=0.04) = 0.06
    # Mapeado al orden de entrada [0.01, 0.04, 0.03, 0.005]:
    #   0.01 -> 0.03, 0.04 -> 0.06, 0.03 -> 0.06, 0.005 -> 0.02
    out = fdr_correction([0.01, 0.04, 0.03, 0.005], alpha=0.05, method="holm")
    assert out["method"] == "holm"
    assert out["n_tests"] == 4
    adj = out["p_values_adjusted"]
    assert abs(adj[0] - 0.03) < 1e-9
    assert abs(adj[1] - 0.06) < 1e-9
    assert abs(adj[2] - 0.06) < 1e-9
    assert abs(adj[3] - 0.02) < 1e-9
    assert out["reject"] == [True, False, False, True]
    assert out["n_rejected"] == 2


def test_holm_entre_bonferroni_y_bh():
    # Holm controla FWER como Bonferroni pero es step-down: rechaza AL MENOS
    # tanto como Bonferroni simple, y a lo sumo tanto como BH (FDR, menos
    # conservador). Cadena de potencia: bonferroni <= holm <= bh.
    pvalues = [0.01, 0.02, 0.04, 0.005]
    bon = fdr_correction(pvalues, alpha=0.05, method="bonferroni")
    holm = fdr_correction(pvalues, alpha=0.05, method="holm")
    bh = fdr_correction(pvalues, alpha=0.05, method="bh")
    assert holm["n_rejected"] >= bon["n_rejected"]
    assert holm["n_rejected"] <= bh["n_rejected"]
    # En este set Holm gana potencia frente a Bonferroni simple (estricto).
    assert holm["n_rejected"] > bon["n_rejected"]

    # Un set donde Holm es estrictamente mas conservador que BH.
    pvals2 = [0.01, 0.02, 0.03, 0.04]
    bon2 = fdr_correction(pvals2, alpha=0.05, method="bonferroni")
    holm2 = fdr_correction(pvals2, alpha=0.05, method="holm")
    bh2 = fdr_correction(pvals2, alpha=0.05, method="bh")
    assert holm2["n_rejected"] >= bon2["n_rejected"]
    assert holm2["n_rejected"] < bh2["n_rejected"]


def test_none_se_propaga_alineado_holm():
    # None se propaga alineado tambien con holm: la posicion central no cuenta
    # como prueba (m=2) y se devuelve como None / False.
    out = fdr_correction([0.001, None, 0.9], method="holm")
    assert out["n_tests"] == 2
    assert out["p_values_adjusted"][1] is None
    assert out["reject"][1] is False
    assert out["reject"][0] is True
    assert len(out["reject"]) == 3


def test_lista_vacia_holm_devuelve_note():
    out = fdr_correction([], method="holm")
    assert out["p_values_adjusted"] == []
    assert out["reject"] == []
    assert out["n_tests"] == 0
    assert out["n_rejected"] == 0
    assert "note" in out
