"""Integration tests for active_pathways and enrichment_analysis."""
import warnings

import numpy as np
import pandas as pd
import pytest

from activepathways import GMT, Pathway, active_pathways, enrichment_analysis, make_background


# ---------------------------------------------------------------------------
# Minimal fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def small_gmt():
    return GMT([
        Pathway(id="P1", name="Cell Cycle", genes=["TP53", "BRCA1", "ATM", "CHEK2", "CDK4", "CDK6", "RB1"]),
        Pathway(id="P2", name="Apoptosis", genes=["BCL2", "BAX", "CASP3", "CASP8", "FADD", "FAS", "BID"]),
        Pathway(id="P3", name="DNA Repair", genes=["BRCA1", "BRCA2", "ATM", "CHEK1", "CHEK2", "RAD51"]),
        Pathway(id="P4", name="PI3K Signaling", genes=["AKT1", "PIK3CA", "PTEN", "KRAS", "EGFR", "MYC"]),
    ])


@pytest.fixture
def small_scores(small_gmt):
    # Low p-values for genes in P1 / P3 to drive significant enrichment.
    genes = ["TP53", "BRCA1", "ATM", "CHEK2", "CDK4", "CDK6", "RB1",
             "BRCA2", "CHEK1", "RAD51", "BCL2", "BAX", "CASP3", "AKT1", "PIK3CA"]
    pvals = [0.001, 0.001, 0.002, 0.002, 0.003, 0.003, 0.004,
             0.001, 0.002, 0.003, 0.5, 0.6, 0.7, 0.4, 0.35]
    return pd.DataFrame({"RNA": pvals}, index=genes)


@pytest.fixture
def small_scores_multi(small_scores):
    rna = small_scores["RNA"].values
    protein = np.clip(rna * 1.5, 0, 1)
    df = small_scores.copy()
    df["Protein"] = protein
    return df


# ---------------------------------------------------------------------------
# active_pathways — basic functionality
# ---------------------------------------------------------------------------

def test_active_pathways_returns_dataframe_or_none(small_scores, small_gmt):
    result = active_pathways(small_scores, small_gmt)
    assert result is None or isinstance(result, pd.DataFrame)


def test_active_pathways_significant_terms_found(small_scores, small_gmt):
    result = active_pathways(small_scores, small_gmt, significant=0.2)
    assert result is not None
    assert len(result) >= 1


def test_active_pathways_output_columns(small_scores, small_gmt):
    result = active_pathways(small_scores, small_gmt, significant=0.2)
    if result is not None:
        required = {"term_id", "term_name", "adjusted_p_val", "overlap"}
        assert required.issubset(set(result.columns))


def test_active_pathways_pvals_in_range(small_scores, small_gmt):
    result = active_pathways(small_scores, small_gmt, significant=0.2)
    if result is not None:
        assert (result["adjusted_p_val"] >= 0).all()
        assert (result["adjusted_p_val"] <= 1).all()


def test_active_pathways_multi_column(small_scores_multi, small_gmt):
    result = active_pathways(small_scores_multi, small_gmt, significant=0.2)
    if result is not None:
        assert "Genes_RNA" in result.columns or "Genes_Protein" in result.columns


def test_active_pathways_accepts_gmt_path(small_scores, tmp_path):
    from activepathways import write_gmt, GMT, Pathway
    g = GMT([
        Pathway(id="P1", name="Cell Cycle", genes=list(small_scores.index[:5])),
    ])
    path = tmp_path / "test.gmt"
    write_gmt(g, str(path))
    result = active_pathways(small_scores, str(path), significant=0.5)
    # Should run without error whether or not terms are significant.
    assert result is None or isinstance(result, pd.DataFrame)


def test_active_pathways_no_significant_warns(small_gmt):
    # All p-values = 1 → no genes pass cutoff.
    genes = list(small_gmt[0].genes)
    scores = pd.DataFrame({"RNA": [1.0] * len(genes)}, index=genes)
    with pytest.raises(ValueError, match="cutoff"):
        active_pathways(scores, small_gmt, cutoff=0.05)


def test_active_pathways_result_sorted_by_pval(small_scores, small_gmt):
    result = active_pathways(small_scores, small_gmt, significant=0.5)
    if result is not None and len(result) > 1:
        pvals = result["adjusted_p_val"].tolist()
        assert pvals == sorted(pvals)


# ---------------------------------------------------------------------------
# active_pathways — input validation
# ---------------------------------------------------------------------------

def test_active_pathways_rejects_nan_scores(small_gmt):
    scores = pd.DataFrame({"RNA": [0.01, np.nan, 0.05]}, index=["TP53", "BRCA1", "ATM"])
    with pytest.raises(ValueError, match="missing values"):
        active_pathways(scores, small_gmt)


def test_active_pathways_rejects_out_of_range_scores(small_gmt):
    scores = pd.DataFrame({"RNA": [0.01, -0.1, 0.05]}, index=["TP53", "BRCA1", "ATM"])
    with pytest.raises(ValueError, match=r"\[0,1\]"):
        active_pathways(scores, small_gmt)


def test_active_pathways_rejects_non_gmt(small_scores):
    with pytest.raises(TypeError, match="GMT"):
        active_pathways(small_scores, {"not": "a gmt"})


def test_active_pathways_rejects_duplicate_genes(small_gmt):
    scores = pd.DataFrame({"RNA": [0.01, 0.02]}, index=["TP53", "TP53"])
    with pytest.raises(ValueError, match="duplicated"):
        active_pathways(scores, small_gmt)


def test_active_pathways_invalid_cutoff_type(small_scores, small_gmt):
    with pytest.raises(ValueError):
        active_pathways(small_scores, small_gmt, cutoff="high")


def test_active_pathways_cutoff_out_of_range(small_scores, small_gmt):
    with pytest.raises(ValueError, match="cutoff"):
        active_pathways(small_scores, small_gmt, cutoff=1.5)


# ---------------------------------------------------------------------------
# active_pathways — geneset_filter
# ---------------------------------------------------------------------------

def test_active_pathways_geneset_filter_removes_small_pathways(small_scores, small_gmt):
    # With min_size=100, no pathway has enough genes → error
    with pytest.raises(ValueError, match="geneset_filter"):
        active_pathways(small_scores, small_gmt, geneset_filter=(100, 1000))


def test_active_pathways_geneset_filter_none_disables(small_scores, small_gmt):
    result = active_pathways(small_scores, small_gmt, geneset_filter=None, significant=0.5)
    assert result is None or isinstance(result, pd.DataFrame)


# ---------------------------------------------------------------------------
# active_pathways — correction methods
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("method", ["holm", "BH", "bonferroni", "none"])
def test_active_pathways_correction_methods(small_scores, small_gmt, method):
    result = active_pathways(small_scores, small_gmt, correction_method=method, significant=0.5)
    assert result is None or isinstance(result, pd.DataFrame)


# ---------------------------------------------------------------------------
# enrichment_analysis
# ---------------------------------------------------------------------------

def test_enrichment_analysis_returns_dataframe(small_gmt):
    background = make_background(small_gmt)
    gene_list = ["TP53", "BRCA1", "ATM", "CHEK2"]
    result = enrichment_analysis(gene_list, small_gmt, background)
    assert isinstance(result, pd.DataFrame)


def test_enrichment_analysis_columns(small_gmt):
    background = make_background(small_gmt)
    gene_list = ["TP53", "BRCA1", "ATM"]
    result = enrichment_analysis(gene_list, small_gmt, background)
    required = {"term_id", "term_name", "adjusted_p_val", "overlap"}
    assert required.issubset(set(result.columns))


def test_enrichment_analysis_overlap_is_subset(small_gmt):
    background = make_background(small_gmt)
    gene_list = ["TP53", "BRCA1", "ATM", "CHEK2"]
    result = enrichment_analysis(gene_list, small_gmt, background)
    for _, row in result.iterrows():
        assert set(row["overlap"]).issubset(set(gene_list))


# ---------------------------------------------------------------------------
# Integration: full adenocarcinoma dataset
# ---------------------------------------------------------------------------

def test_active_pathways_adenocarcinoma_runs(adenocarcinoma_scores, gmt):
    result = active_pathways(adenocarcinoma_scores, gmt, significant=0.2)
    assert result is None or isinstance(result, pd.DataFrame)
