"""Tests for AltSportsData SDK v2"""

import pytest
from unittest.mock import MagicMock, patch

from altsportsdata import (
    AltSportsData, SportType, OddType, EventStatus, FutureType,
    ConfigurationError, AltSportsDataError,
)
from altsportsdata.exceptions import (
    AuthenticationError, NotFoundError, NetworkError, RateLimitError,
)


def _ok(data):
    r = MagicMock(); r.ok = True; r.json.return_value = data; return r

def _err(code, msg="error"):
    r = MagicMock(); r.ok = False; r.status_code = code
    r.json.return_value = {"message": msg}; return r

P = "altsportsdata.client.requests.Session.request"


def _params(mock):
    """Extract params from mock call as a dict (handles list-of-tuples format)."""
    raw = mock.call_args.kwargs.get("params", {})
    if isinstance(raw, list):
        d = {}
        for k, v in raw:
            clean_k = k.rstrip("[]")
            if clean_k in d:
                if not isinstance(d[clean_k], list):
                    d[clean_k] = [d[clean_k]]
                d[clean_k].append(v)
            else:
                d[clean_k] = v
        return d
    return raw


class TestInit:
    def test_requires_key(self):
        with pytest.raises(ConfigurationError):
            AltSportsData(api_key=None)

    def test_empty_key(self):
        with pytest.raises(ConfigurationError):
            AltSportsData(api_key="")

    def test_league_stored(self):
        c = AltSportsData(api_key="k", league="wsl")
        assert c.league == "wsl"

    def test_league_enum(self):
        c = AltSportsData(api_key="k", league=SportType.F1)
        assert c.league == "f1"

    def test_header(self):
        c = AltSportsData(api_key="my-key")
        assert c._session.headers["X-API-KEY"] == "my-key"

    def test_repr(self):
        c = AltSportsData(api_key="k", league="wsl")
        assert "wsl" in repr(c)


class TestLeagues:
    @patch(P)
    def test_list_leagues(self, m):
        m.return_value = _ok([{"id": "1", "name": "WSL", "sportType": "wsl"}])
        assert len(AltSportsData(api_key="k").list_leagues()) == 1

    @patch(P)
    def test_list_sports_alias(self, m):
        m.return_value = _ok([])
        AltSportsData(api_key="k").list_sports()  # should not raise


class TestEvents:
    @patch(P)
    def test_list_events(self, m):
        m.return_value = _ok([{"id": "e1", "sportType": "f1"}])
        events = AltSportsData(api_key="k").list_events(league="f1")
        assert events[0].sportType == "f1"

    @patch(P)
    def test_status_string(self, m):
        m.return_value = _ok([])
        AltSportsData(api_key="k", league="wsl").list_events(status="upcoming")
        p = _params(m)
        assert p["eventStatus"] == "UPCOMING"

    @patch(P)
    def test_status_list(self, m):
        m.return_value = _ok([])
        AltSportsData(api_key="k").list_events(status=["live", "upcoming"])
        p = _params(m)
        assert p["eventStatus"] == ["LIVE", "UPCOMING"]

    @patch(P)
    def test_league_auto_filter(self, m):
        m.return_value = _ok([])
        AltSportsData(api_key="k", league="wsl").list_events()
        assert _params(m)["sportType"] == "wsl"

    @patch(P)
    def test_get_event(self, m):
        m.return_value = _ok({"id": "e1", "name": "Event"})
        e = AltSportsData(api_key="k").get_event("e1")
        assert e.id == "e1"

    @patch(P)
    def test_participants(self, m):
        m.return_value = _ok([{"athlete": {"id": "a1", "firstName": "J", "lastName": "D"}}])
        p = AltSportsData(api_key="k").get_participants("e1")
        assert p[0].athlete.firstName == "J"

    @patch(P)
    def test_heat_scores(self, m):
        m.return_value = _ok([{"id": "s1", "athleteHeatScore": 8.5}])
        s = AltSportsData(api_key="k").get_heat_scores("e1", "h1")
        assert s[0].athleteHeatScore == 8.5


class TestOdds:
    @patch(P)
    def test_get_odds_string(self, m):
        m.return_value = _ok({})
        AltSportsData(api_key="k").get_odds("e1", "eventWinner")
        assert "/eventWinner" in m.call_args.kwargs["url"]

    @patch(P)
    def test_get_odds_alias(self, m):
        m.return_value = _ok({})
        AltSportsData(api_key="k").get_odds("e1", "moneyline")
        assert "/eventWinner" in m.call_args.kwargs["url"]

    @patch(P)
    def test_get_moneylines(self, m):
        m.return_value = _ok({})
        AltSportsData(api_key="k").get_moneylines("e1")
        assert "/eventWinner" in m.call_args.kwargs["url"]

    @patch(P)
    def test_get_matchups(self, m):
        m.return_value = _ok({})
        AltSportsData(api_key="k").get_matchups("e1")
        assert "/headToHead" in m.call_args.kwargs["url"]

    @patch(P)
    def test_get_player_props(self, m):
        m.return_value = _ok({})
        AltSportsData(api_key="k").get_player_props("e1")
        assert "/propBets" in m.call_args.kwargs["url"]

    @patch(P)
    def test_get_totals(self, m):
        m.return_value = _ok({})
        AltSportsData(api_key="k").get_totals("e1", sub_market="points")
        p = _params(m)
        assert "/overUnder" in m.call_args.kwargs["url"]
        assert p["overUnderSubMarketType"] == "points"

    @patch(P)
    def test_get_heat_winners(self, m):
        m.return_value = _ok({})
        AltSportsData(api_key="k").get_heat_winners("e1")
        assert "/heatWinner" in m.call_args.kwargs["url"]

    @patch(P)
    def test_get_exactas(self, m):
        m.return_value = _ok({})
        AltSportsData(api_key="k").get_exactas("e1", n=3)
        p = _params(m)
        assert p["exactasType"] == 3

    @patch(P)
    def test_enum_market(self, m):
        m.return_value = _ok({})
        AltSportsData(api_key="k").get_odds("e1", OddType.DREAM_TEAM)
        assert "/dreamTeam" in m.call_args.kwargs["url"]


class TestParlays:
    @patch(P)
    def test_calculate_parlay(self, m):
        m.return_value = _ok({"combinedOdds": 5.2})
        r = AltSportsData(api_key="k").calculate_parlay("e1", picks=["o1", "o2"])
        assert r["combinedOdds"] == 5.2

    @patch(P)
    def test_sgp_alias(self, m):
        m.return_value = _ok({})
        AltSportsData(api_key="k").calculate_sgp("e1", picks=["o1"])


class TestJaiAlai:
    @patch(P)
    def test_matches(self, m):
        m.return_value = _ok([{"id": "j1", "name": "M1"}])
        assert len(AltSportsData(api_key="k").get_jaialai_matches("j1")) == 1

    @patch(P)
    def test_odds(self, m):
        m.return_value = _ok({"id": "j1", "matchName": "M1"})
        assert AltSportsData(api_key="k").get_jaialai_odds("j1").matchName == "M1"


class TestFutures:
    @patch(P)
    def test_list(self, m):
        m.return_value = _ok([{"id": "f1"}])
        assert len(AltSportsData(api_key="k").list_futures()) == 1

    @patch(P)
    def test_get_futures(self, m):
        m.return_value = _ok([{"id": "fo1"}])
        AltSportsData(api_key="k", league="f1").get_futures(tour="t1", type="winner")
        assert "/futures/f1/tour/t1/odds/winner" in m.call_args.kwargs["url"]

    def test_futures_requires_league(self):
        with pytest.raises(ConfigurationError):
            AltSportsData(api_key="k").get_futures(tour="t1")


class TestErrors:
    @patch(P)
    def test_401(self, m):
        m.return_value = _err(401)
        with pytest.raises(AuthenticationError):
            AltSportsData(api_key="k").list_leagues()

    @patch(P)
    def test_404(self, m):
        m.return_value = _err(404)
        with pytest.raises(NotFoundError):
            AltSportsData(api_key="k").get_event("x")

    @patch(P)
    def test_429(self, m):
        m.return_value = _err(429)
        with pytest.raises(RateLimitError):
            AltSportsData(api_key="k").list_leagues()

    @patch(P)
    def test_timeout(self, m):
        import requests as req
        m.side_effect = req.exceptions.Timeout()
        with pytest.raises(NetworkError):
            AltSportsData(api_key="k").list_leagues()

    @patch(P)
    def test_connection(self, m):
        import requests as req
        m.side_effect = req.exceptions.ConnectionError()
        with pytest.raises(NetworkError):
            AltSportsData(api_key="k").list_leagues()
