import pandas as pd
import polars as pl
import pytest

from chickenstats.chicken_nhl._agg_constants import GroupListBuilder
from chickenstats.chicken_nhl._aggregation import _prep_oi_percent, _prep_p60


# ---------------------------------------------------------------------------
# _prep_p60
# ---------------------------------------------------------------------------


class TestPrepP60:
    def test_polars_adds_p60_column(self):
        df = pl.DataFrame({"toi": [60.0], "goal": [1.0]})
        result = _prep_p60(df, stats=["goal"])
        assert "goal_p60" in result.columns

    def test_polars_p60_value(self):
        df = pl.DataFrame({"toi": [60.0], "goal": [2.0]})
        result = _prep_p60(df, stats=["goal"])
        assert result["goal_p60"][0] == pytest.approx(2.0)  # (2/60)*60 = 2

    def test_pandas_adds_p60_column(self):
        df = pd.DataFrame({"toi": [60.0], "goal": [1.0]})
        result = _prep_p60(df, stats=["goal"])
        assert "goal_p60" in result.columns

    def test_pandas_p60_value(self):
        df = pd.DataFrame({"toi": [30.0], "goal": [1.0]})
        result = _prep_p60(df, stats=["goal"])
        assert result["goal_p60"].iloc[0] == pytest.approx(2.0)  # (1/30)*60 = 2

    def test_missing_stat_column_skipped(self):
        """Stats not present in the DataFrame should be silently skipped."""
        df = pl.DataFrame({"toi": [60.0]})
        result = _prep_p60(df, stats=["goal"])
        assert "goal_p60" not in result.columns

    def test_multiple_stats(self):
        df = pl.DataFrame({"toi": [60.0], "goal": [1.0], "shot": [5.0]})
        result = _prep_p60(df, stats=["goal", "shot"])
        assert "goal_p60" in result.columns
        assert "shot_p60" in result.columns

    def test_partial_stats_present(self):
        """Only stats that exist in the DataFrame get a _p60 column."""
        df = pl.DataFrame({"toi": [60.0], "goal": [1.0]})
        result = _prep_p60(df, stats=["goal", "shot"])
        assert "goal_p60" in result.columns
        assert "shot_p60" not in result.columns


# ---------------------------------------------------------------------------
# _prep_oi_percent
# ---------------------------------------------------------------------------


class TestPrepOiPercent:
    def test_polars_computes_percent(self):
        df = pl.DataFrame({"xgf": [2.0], "xga": [2.0]})
        result = _prep_oi_percent(df, stats_for=["xgf"], stats_against=["xga"])
        assert "xgf_percent" in result.columns
        assert result["xgf_percent"][0] == pytest.approx(0.5)

    def test_pandas_computes_percent(self):
        df = pd.DataFrame({"xgf": [3.0], "xga": [1.0]})
        result = _prep_oi_percent(df, stats_for=["xgf"], stats_against=["xga"])
        assert "xgf_percent" in result.columns
        assert result["xgf_percent"].iloc[0] == pytest.approx(0.75)

    def test_missing_stat_for_fills_zero(self):
        """When stat_for is absent from the DataFrame the percent column is 0.0 (line 533)."""
        df = pl.DataFrame({"xga": [2.0]})
        result = _prep_oi_percent(df, stats_for=["xgf"], stats_against=["xga"])
        assert "xgf_percent" in result.columns
        assert result["xgf_percent"][0] == pytest.approx(0.0)

    def test_missing_stat_for_fills_zero_pandas(self):
        df = pd.DataFrame({"xga": [2.0]})
        result = _prep_oi_percent(df, stats_for=["xgf"], stats_against=["xga"])
        assert "xgf_percent" in result.columns
        assert result["xgf_percent"].iloc[0] == pytest.approx(0.0)

    def test_missing_stat_against_fills_one(self):
        """When stat_against is absent the percent column is 1.0."""
        df = pl.DataFrame({"xgf": [2.0]})
        result = _prep_oi_percent(df, stats_for=["xgf"], stats_against=["xga"])
        assert "xgf_percent" in result.columns
        assert result["xgf_percent"][0] == pytest.approx(1.0)

    def test_missing_stat_against_fills_one_pandas(self):
        df = pd.DataFrame({"xgf": [2.0]})
        result = _prep_oi_percent(df, stats_for=["xgf"], stats_against=["xga"])
        assert "xgf_percent" in result.columns
        assert result["xgf_percent"].iloc[0] == pytest.approx(1.0)

    def test_multiple_stats(self):
        df = pl.DataFrame({"xgf": [1.0], "xga": [1.0], "cf": [3.0], "ca": [1.0]})
        result = _prep_oi_percent(df, stats_for=["xgf", "cf"], stats_against=["xga", "ca"])
        assert "xgf_percent" in result.columns
        assert "cf_percent" in result.columns
        assert result["cf_percent"][0] == pytest.approx(0.75)


# ---------------------------------------------------------------------------
# GroupListBuilder
# ---------------------------------------------------------------------------


class TestGroupListBuilder:
    def test_build_returns_list(self):
        result = GroupListBuilder(["season", "game_id"]).build()
        assert isinstance(result, list)

    def test_build_deduplicates(self):
        """Columns added more than once appear only once in the output."""
        result = GroupListBuilder(["season", "season", "game_id"]).build()
        assert result.count("season") == 1

    def test_build_canonical_order(self):
        """Columns present in _CANONICAL_ORDER are sorted into canonical order."""
        result = GroupListBuilder(["game_id", "season"]).build()
        assert result.index("season") < result.index("game_id")

    def test_with_level_game_adds_columns(self):
        result = GroupListBuilder(["season"]).with_level("game").build()
        assert "game_id" in result
        assert "opp_team" in result

    def test_with_level_period_adds_period(self):
        result = GroupListBuilder(["season"]).with_level("period").build()
        assert "period" in result

    def test_with_strength_state(self):
        result = GroupListBuilder(["season"]).with_strength_state(True).build()
        assert "strength_state" in result

    def test_with_strength_state_opp(self):
        result = GroupListBuilder(["season"]).with_strength_state(True, opp=True).build()
        assert "opp_strength_state" in result

    def test_filter_to_drops_absent_columns(self):
        df = pl.DataFrame({"season": [1], "game_id": [1]})
        result = GroupListBuilder(["season", "game_id", "opp_team"]).filter_to(df)
        assert "season" in result
        assert "game_id" in result
        assert "opp_team" not in result

    def test_filter_to_only_returns_present_columns(self):
        df = pl.DataFrame({"season": [1]})
        result = GroupListBuilder(["season", "game_id"]).filter_to(df)
        assert all(c in df.columns for c in result)

    def test_repr_contains_class_name(self):
        builder = GroupListBuilder(["season"])
        assert "GroupListBuilder" in repr(builder)

    def test_repr_contains_cols(self):
        builder = GroupListBuilder(["season", "game_id"])
        assert "season" in repr(builder)
