"""Tests for the sync and async Football API clients."""

from __future__ import annotations

import httpx
import pytest
import respx

from football_api.client import BASE_URL, AsyncFootballClient, FootballClient
from football_api.exceptions import (
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    ServerError,
)

# ── Fixtures / helpers ────────────────────────────────────────────────────────

AREA_JSON = {"id": 2077, "name": "Europe", "code": "EUR", "childAreas": []}
AREA_LIST_JSON = {"count": 1, "areas": [{"id": 2077, "name": "Europe", "code": "EUR"}]}
COMPETITION_JSON = {
    "id": 2021,
    "name": "Premier League",
    "code": "PL",
    "type": "LEAGUE",
    "area": {"id": 2072, "name": "England"},
    "currentSeason": {"id": 1564, "startDate": "2023-08-11", "endDate": "2024-05-19"},
    "seasons": [],
}
COMPETITION_LIST_JSON = {"count": 1, "competitions": [COMPETITION_JSON]}
TEAM_JSON = {"id": 57, "name": "Arsenal FC", "tla": "ARS", "squad": []}
TEAM_LIST_JSON = {"count": 1, "teams": [TEAM_JSON]}
MATCH_JSON = {
    "id": 12345,
    "status": "FINISHED",
    "homeTeam": {"id": 57, "name": "Arsenal FC"},
    "awayTeam": {"id": 65, "name": "Manchester City FC"},
    "score": {"winner": "HOME_TEAM", "fullTime": {"home": 2, "away": 1}},
}
MATCH_LIST_JSON = {"count": 1, "matches": [MATCH_JSON]}
STANDINGS_JSON = {
    "competition": {"id": 2021, "name": "Premier League"},
    "standings": [
        {
            "stage": "REGULAR_SEASON",
            "type": "TOTAL",
            "table": [
                {"position": 1, "team": {"id": 57, "name": "Arsenal FC"}, "points": 48}
            ],
        }
    ],
}
SCORERS_JSON = {
    "count": 1,
    "scorers": [
        {"player": {"id": 200, "name": "Erling Haaland"}, "goals": 15}
    ],
}
PERSON_JSON = {"id": 200, "name": "Erling Haaland", "position": "Centre-Forward"}
H2H_JSON = {"aggregates": {"numberOfMatches": 3}, "matches": [MATCH_JSON]}


# ── Sync client tests ─────────────────────────────────────────────────────────


class TestFootballClient:
    @respx.mock(base_url=BASE_URL)
    def test_get_areas(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/areas").mock(return_value=httpx.Response(200, json=AREA_LIST_JSON))
        with FootballClient(api_key="test") as c:
            result = c.get_areas()
        assert result.count == 1
        assert result.areas[0].name == "Europe"

    @respx.mock(base_url=BASE_URL)
    def test_get_area(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/areas/2077").mock(return_value=httpx.Response(200, json=AREA_JSON))
        with FootballClient(api_key="test") as c:
            result = c.get_area(2077)
        assert result.id == 2077

    @respx.mock(base_url=BASE_URL)
    def test_get_competitions(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/competitions").mock(
            return_value=httpx.Response(200, json=COMPETITION_LIST_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_competitions()
        assert result.count == 1

    @respx.mock(base_url=BASE_URL)
    def test_get_competition(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/competitions/PL").mock(
            return_value=httpx.Response(200, json=COMPETITION_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_competition("PL")
        assert result.code == "PL"

    @respx.mock(base_url=BASE_URL)
    def test_get_standings(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/competitions/PL/standings").mock(
            return_value=httpx.Response(200, json=STANDINGS_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_standings("PL")
        assert len(result.standings) == 1

    @respx.mock(base_url=BASE_URL)
    def test_get_scorers(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/competitions/PL/scorers").mock(
            return_value=httpx.Response(200, json=SCORERS_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_scorers("PL")
        assert result.scorers[0].goals == 15

    @respx.mock(base_url=BASE_URL)
    def test_get_matches(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/matches").mock(
            return_value=httpx.Response(200, json=MATCH_LIST_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_matches()
        assert result.count == 1

    @respx.mock(base_url=BASE_URL)
    def test_get_match(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/matches/12345").mock(
            return_value=httpx.Response(200, json=MATCH_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_match(12345)
        assert result.id == 12345

    @respx.mock(base_url=BASE_URL)
    def test_get_head2head(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/matches/12345/head2head").mock(
            return_value=httpx.Response(200, json=H2H_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_head2head(12345)
        assert len(result.matches) == 1

    @respx.mock(base_url=BASE_URL)
    def test_get_competition_matches(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/competitions/PL/matches").mock(
            return_value=httpx.Response(200, json=MATCH_LIST_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_competition_matches("PL", matchday=20)
        assert result.count == 1

    @respx.mock(base_url=BASE_URL)
    def test_get_team(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/teams/57").mock(return_value=httpx.Response(200, json=TEAM_JSON))
        with FootballClient(api_key="test") as c:
            result = c.get_team(57)
        assert result.name == "Arsenal FC"

    @respx.mock(base_url=BASE_URL)
    def test_get_competition_teams(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/competitions/PL/teams").mock(
            return_value=httpx.Response(200, json=TEAM_LIST_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_competition_teams("PL")
        assert result.count == 1

    @respx.mock(base_url=BASE_URL)
    def test_get_team_matches(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/teams/57/matches").mock(
            return_value=httpx.Response(200, json=MATCH_LIST_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_team_matches(57, venue="HOME")
        assert result.count == 1

    @respx.mock(base_url=BASE_URL)
    def test_get_person(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/persons/200").mock(
            return_value=httpx.Response(200, json=PERSON_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_person(200)
        assert result.name == "Erling Haaland"

    @respx.mock(base_url=BASE_URL)
    def test_get_person_matches(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/persons/200/matches").mock(
            return_value=httpx.Response(200, json=MATCH_LIST_JSON)
        )
        with FootballClient(api_key="test") as c:
            result = c.get_person_matches(200, limit=10)
        assert result.count == 1


# ── Error handling tests ──────────────────────────────────────────────────────


class TestErrorHandling:
    @respx.mock(base_url=BASE_URL)
    def test_authentication_error(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/competitions").mock(
            return_value=httpx.Response(403, json={"message": "Forbidden"})
        )
        with FootballClient(api_key="bad") as c:
            with pytest.raises(AuthenticationError) as exc_info:
                c.get_competitions()
        assert exc_info.value.status_code == 403

    @respx.mock(base_url=BASE_URL)
    def test_not_found_error(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/teams/99999").mock(
            return_value=httpx.Response(404, json={"message": "Not found"})
        )
        with FootballClient(api_key="test") as c:
            with pytest.raises(NotFoundError) as exc_info:
                c.get_team(99999)
        assert exc_info.value.status_code == 404

    @respx.mock(base_url=BASE_URL)
    def test_rate_limit_error(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/matches").mock(
            return_value=httpx.Response(429, json={"message": "Rate limit exceeded"})
        )
        with FootballClient(api_key="test") as c:
            with pytest.raises(RateLimitError):
                c.get_matches()

    @respx.mock(base_url=BASE_URL)
    def test_server_error(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/matches").mock(
            return_value=httpx.Response(500, json={"message": "Internal server error"})
        )
        with FootballClient(api_key="test") as c:
            with pytest.raises(ServerError):
                c.get_matches()


# ── Async client tests ────────────────────────────────────────────────────────


class TestAsyncFootballClient:
    @respx.mock(base_url=BASE_URL)
    async def test_get_areas(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/areas").mock(return_value=httpx.Response(200, json=AREA_LIST_JSON))
        async with AsyncFootballClient(api_key="test") as c:
            result = await c.get_areas()
        assert result.count == 1

    @respx.mock(base_url=BASE_URL)
    async def test_get_competition(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/competitions/PL").mock(
            return_value=httpx.Response(200, json=COMPETITION_JSON)
        )
        async with AsyncFootballClient(api_key="test") as c:
            result = await c.get_competition("PL")
        assert result.code == "PL"

    @respx.mock(base_url=BASE_URL)
    async def test_get_match(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/matches/12345").mock(
            return_value=httpx.Response(200, json=MATCH_JSON)
        )
        async with AsyncFootballClient(api_key="test") as c:
            result = await c.get_match(12345)
        assert result.id == 12345

    @respx.mock(base_url=BASE_URL)
    async def test_get_team(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/teams/57").mock(return_value=httpx.Response(200, json=TEAM_JSON))
        async with AsyncFootballClient(api_key="test") as c:
            result = await c.get_team(57)
        assert result.name == "Arsenal FC"

    @respx.mock(base_url=BASE_URL)
    async def test_get_standings(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/competitions/PL/standings").mock(
            return_value=httpx.Response(200, json=STANDINGS_JSON)
        )
        async with AsyncFootballClient(api_key="test") as c:
            result = await c.get_standings("PL")
        assert len(result.standings) == 1

    @respx.mock(base_url=BASE_URL)
    async def test_get_person(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/persons/200").mock(
            return_value=httpx.Response(200, json=PERSON_JSON)
        )
        async with AsyncFootballClient(api_key="test") as c:
            result = await c.get_person(200)
        assert result.name == "Erling Haaland"

    @respx.mock(base_url=BASE_URL)
    async def test_error_handling(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.get("/competitions").mock(
            return_value=httpx.Response(403, json={"message": "Forbidden"})
        )
        async with AsyncFootballClient(api_key="bad") as c:
            with pytest.raises(AuthenticationError):
                await c.get_competitions()
