"""Sync and async HTTP clients for the football-data.org API v4."""

from __future__ import annotations

import os
from typing import Any

import httpx

from football_api.exceptions import (
    AuthenticationError,
    FootballAPIError,
    NotFoundError,
    RateLimitError,
    ServerError,
)
from football_api.models import (
    AreaListResponse,
    AreaResponse,
    CompetitionListResponse,
    CompetitionResponse,
    Head2HeadResponse,
    Match,
    MatchListResponse,
    PersonResponse,
    ScorersResponse,
    StandingsResponse,
    TeamListResponse,
    TeamResponse,
)

BASE_URL = "https://api.football-data.org/v4"

_STATUS_MAP: dict[int, type[FootballAPIError]] = {
    403: AuthenticationError,
    404: NotFoundError,
    429: RateLimitError,
}


def _build_params(**kwargs: Any) -> dict[str, str]:
    """Convert keyword arguments to query parameters, dropping ``None`` values."""
    params: dict[str, str] = {}
    key_map = {
        "date_from": "dateFrom",
        "date_to": "dateTo",
    }
    for key, value in kwargs.items():
        if value is None:
            continue
        api_key = key_map.get(key, key)
        params[api_key] = str(value)
    return params


def _raise_for_status(response: httpx.Response) -> None:
    if response.is_success:
        return
    code = response.status_code
    try:
        detail = response.json().get("message", response.text)
    except Exception:
        detail = response.text
    exc_cls = _STATUS_MAP.get(code, ServerError if code >= 500 else FootballAPIError)
    raise exc_cls(detail, status_code=code)


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


class FootballClient:
    """Synchronous client for the football-data.org API v4.

    Args:
        api_key: Your API token. Falls back to the ``FOOTBALL_DATA_API_KEY``
            environment variable when not provided.
        base_url: Override the default API base URL.
        timeout: Request timeout in seconds.
    """

    def __init__(
        self,
        api_key: str | None = None,
        *,
        base_url: str = BASE_URL,
        timeout: float = 30.0,
    ) -> None:
        token = api_key or os.environ.get("FOOTBALL_DATA_API_KEY", "")
        self._client = httpx.Client(
            base_url=base_url,
            headers={"X-Auth-Token": token},
            timeout=timeout,
        )

    def close(self) -> None:
        self._client.close()

    def __enter__(self) -> FootballClient:
        return self

    def __exit__(self, *exc: object) -> None:
        self.close()

    def _get(self, path: str, **params: Any) -> dict[str, Any]:
        response = self._client.get(path, params=_build_params(**params))
        _raise_for_status(response)
        result: dict[str, Any] = response.json()
        return result

    # ── Areas ─────────────────────────────────────────────────────────────

    def get_areas(self) -> AreaListResponse:
        """List all available areas."""
        return AreaListResponse.model_validate(self._get("/areas"))

    def get_area(self, area_id: int) -> AreaResponse:
        """Get a single area by ID."""
        return AreaResponse.model_validate(self._get(f"/areas/{area_id}"))

    # ── Competitions ──────────────────────────────────────────────────────

    def get_competitions(self, *, areas: str | None = None) -> CompetitionListResponse:
        """List competitions, optionally filtered by area IDs (comma-separated)."""
        return CompetitionListResponse.model_validate(
            self._get("/competitions", areas=areas)
        )

    def get_competition(self, competition_id: int | str) -> CompetitionResponse:
        """Get a single competition by ID or code (e.g. ``'PL'``)."""
        return CompetitionResponse.model_validate(
            self._get(f"/competitions/{competition_id}")
        )

    # ── Standings ─────────────────────────────────────────────────────────

    def get_standings(
        self,
        competition_id: int | str,
        *,
        season: int | None = None,
        matchday: int | None = None,
        date: str | None = None,
    ) -> StandingsResponse:
        """Get league table for a competition."""
        return StandingsResponse.model_validate(
            self._get(
                f"/competitions/{competition_id}/standings",
                season=season,
                matchday=matchday,
                date=date,
            )
        )

    # ── Scorers ───────────────────────────────────────────────────────────

    def get_scorers(
        self,
        competition_id: int | str,
        *,
        season: int | None = None,
        matchday: int | None = None,
        limit: int | None = None,
    ) -> ScorersResponse:
        """Get top scorers for a competition."""
        return ScorersResponse.model_validate(
            self._get(
                f"/competitions/{competition_id}/scorers",
                season=season,
                matchday=matchday,
                limit=limit,
            )
        )

    # ── Matches ───────────────────────────────────────────────────────────

    def get_matches(
        self,
        *,
        competitions: str | None = None,
        date: str | None = None,
        date_from: str | None = None,
        date_to: str | None = None,
        status: str | None = None,
    ) -> MatchListResponse:
        """List matches across competitions (defaults to today)."""
        return MatchListResponse.model_validate(
            self._get(
                "/matches",
                competitions=competitions,
                date=date,
                date_from=date_from,
                date_to=date_to,
                status=status,
            )
        )

    def get_match(self, match_id: int) -> Match:
        """Get a single match by ID."""
        return Match.model_validate(self._get(f"/matches/{match_id}"))

    def get_head2head(
        self,
        match_id: int,
        *,
        limit: int | None = None,
        date_from: str | None = None,
        date_to: str | None = None,
    ) -> Head2HeadResponse:
        """Get head-to-head history for a match."""
        return Head2HeadResponse.model_validate(
            self._get(
                f"/matches/{match_id}/head2head",
                limit=limit,
                date_from=date_from,
                date_to=date_to,
            )
        )

    def get_competition_matches(
        self,
        competition_id: int | str,
        *,
        season: int | None = None,
        matchday: int | None = None,
        status: str | None = None,
        date_from: str | None = None,
        date_to: str | None = None,
        stage: str | None = None,
        group: str | None = None,
    ) -> MatchListResponse:
        """List matches for a specific competition."""
        return MatchListResponse.model_validate(
            self._get(
                f"/competitions/{competition_id}/matches",
                season=season,
                matchday=matchday,
                status=status,
                date_from=date_from,
                date_to=date_to,
                stage=stage,
                group=group,
            )
        )

    # ── Teams ─────────────────────────────────────────────────────────────

    def get_team(self, team_id: int) -> TeamResponse:
        """Get a single team by ID."""
        return TeamResponse.model_validate(self._get(f"/teams/{team_id}"))

    def get_competition_teams(
        self,
        competition_id: int | str,
        *,
        season: int | None = None,
    ) -> TeamListResponse:
        """List teams in a competition."""
        return TeamListResponse.model_validate(
            self._get(f"/competitions/{competition_id}/teams", season=season)
        )

    def get_team_matches(
        self,
        team_id: int,
        *,
        season: int | None = None,
        status: str | None = None,
        venue: str | None = None,
        date_from: str | None = None,
        date_to: str | None = None,
        limit: int | None = None,
    ) -> MatchListResponse:
        """List matches for a specific team."""
        return MatchListResponse.model_validate(
            self._get(
                f"/teams/{team_id}/matches",
                season=season,
                status=status,
                venue=venue,
                date_from=date_from,
                date_to=date_to,
                limit=limit,
            )
        )

    # ── Persons ───────────────────────────────────────────────────────────

    def get_person(self, person_id: int) -> PersonResponse:
        """Get a person (player, coach, referee) by ID."""
        return PersonResponse.model_validate(self._get(f"/persons/{person_id}"))

    def get_person_matches(
        self,
        person_id: int,
        *,
        date_from: str | None = None,
        date_to: str | None = None,
        status: str | None = None,
        competitions: str | None = None,
        limit: int | None = None,
        offset: int | None = None,
    ) -> MatchListResponse:
        """List matches for a specific person."""
        return MatchListResponse.model_validate(
            self._get(
                f"/persons/{person_id}/matches",
                date_from=date_from,
                date_to=date_to,
                status=status,
                competitions=competitions,
                limit=limit,
                offset=offset,
            )
        )


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


class AsyncFootballClient:
    """Asynchronous client for the football-data.org API v4.

    Args:
        api_key: Your API token. Falls back to the ``FOOTBALL_DATA_API_KEY``
            environment variable when not provided.
        base_url: Override the default API base URL.
        timeout: Request timeout in seconds.
    """

    def __init__(
        self,
        api_key: str | None = None,
        *,
        base_url: str = BASE_URL,
        timeout: float = 30.0,
    ) -> None:
        token = api_key or os.environ.get("FOOTBALL_DATA_API_KEY", "")
        self._client = httpx.AsyncClient(
            base_url=base_url,
            headers={"X-Auth-Token": token},
            timeout=timeout,
        )

    async def close(self) -> None:
        await self._client.aclose()

    async def __aenter__(self) -> AsyncFootballClient:
        return self

    async def __aexit__(self, *exc: object) -> None:
        await self.close()

    async def _get(self, path: str, **params: Any) -> dict[str, Any]:
        response = await self._client.get(path, params=_build_params(**params))
        _raise_for_status(response)
        result: dict[str, Any] = response.json()
        return result

    # ── Areas ─────────────────────────────────────────────────────────────

    async def get_areas(self) -> AreaListResponse:
        """List all available areas."""
        return AreaListResponse.model_validate(await self._get("/areas"))

    async def get_area(self, area_id: int) -> AreaResponse:
        """Get a single area by ID."""
        return AreaResponse.model_validate(await self._get(f"/areas/{area_id}"))

    # ── Competitions ──────────────────────────────────────────────────────

    async def get_competitions(
        self, *, areas: str | None = None
    ) -> CompetitionListResponse:
        """List competitions, optionally filtered by area IDs (comma-separated)."""
        return CompetitionListResponse.model_validate(
            await self._get("/competitions", areas=areas)
        )

    async def get_competition(self, competition_id: int | str) -> CompetitionResponse:
        """Get a single competition by ID or code (e.g. ``'PL'``)."""
        return CompetitionResponse.model_validate(
            await self._get(f"/competitions/{competition_id}")
        )

    # ── Standings ─────────────────────────────────────────────────────────

    async def get_standings(
        self,
        competition_id: int | str,
        *,
        season: int | None = None,
        matchday: int | None = None,
        date: str | None = None,
    ) -> StandingsResponse:
        """Get league table for a competition."""
        return StandingsResponse.model_validate(
            await self._get(
                f"/competitions/{competition_id}/standings",
                season=season,
                matchday=matchday,
                date=date,
            )
        )

    # ── Scorers ───────────────────────────────────────────────────────────

    async def get_scorers(
        self,
        competition_id: int | str,
        *,
        season: int | None = None,
        matchday: int | None = None,
        limit: int | None = None,
    ) -> ScorersResponse:
        """Get top scorers for a competition."""
        return ScorersResponse.model_validate(
            await self._get(
                f"/competitions/{competition_id}/scorers",
                season=season,
                matchday=matchday,
                limit=limit,
            )
        )

    # ── Matches ───────────────────────────────────────────────────────────

    async def get_matches(
        self,
        *,
        competitions: str | None = None,
        date: str | None = None,
        date_from: str | None = None,
        date_to: str | None = None,
        status: str | None = None,
    ) -> MatchListResponse:
        """List matches across competitions (defaults to today)."""
        return MatchListResponse.model_validate(
            await self._get(
                "/matches",
                competitions=competitions,
                date=date,
                date_from=date_from,
                date_to=date_to,
                status=status,
            )
        )

    async def get_match(self, match_id: int) -> Match:
        """Get a single match by ID."""
        return Match.model_validate(await self._get(f"/matches/{match_id}"))

    async def get_head2head(
        self,
        match_id: int,
        *,
        limit: int | None = None,
        date_from: str | None = None,
        date_to: str | None = None,
    ) -> Head2HeadResponse:
        """Get head-to-head history for a match."""
        return Head2HeadResponse.model_validate(
            await self._get(
                f"/matches/{match_id}/head2head",
                limit=limit,
                date_from=date_from,
                date_to=date_to,
            )
        )

    async def get_competition_matches(
        self,
        competition_id: int | str,
        *,
        season: int | None = None,
        matchday: int | None = None,
        status: str | None = None,
        date_from: str | None = None,
        date_to: str | None = None,
        stage: str | None = None,
        group: str | None = None,
    ) -> MatchListResponse:
        """List matches for a specific competition."""
        return MatchListResponse.model_validate(
            await self._get(
                f"/competitions/{competition_id}/matches",
                season=season,
                matchday=matchday,
                status=status,
                date_from=date_from,
                date_to=date_to,
                stage=stage,
                group=group,
            )
        )

    # ── Teams ─────────────────────────────────────────────────────────────

    async def get_team(self, team_id: int) -> TeamResponse:
        """Get a single team by ID."""
        return TeamResponse.model_validate(await self._get(f"/teams/{team_id}"))

    async def get_competition_teams(
        self,
        competition_id: int | str,
        *,
        season: int | None = None,
    ) -> TeamListResponse:
        """List teams in a competition."""
        return TeamListResponse.model_validate(
            await self._get(f"/competitions/{competition_id}/teams", season=season)
        )

    async def get_team_matches(
        self,
        team_id: int,
        *,
        season: int | None = None,
        status: str | None = None,
        venue: str | None = None,
        date_from: str | None = None,
        date_to: str | None = None,
        limit: int | None = None,
    ) -> MatchListResponse:
        """List matches for a specific team."""
        return MatchListResponse.model_validate(
            await self._get(
                f"/teams/{team_id}/matches",
                season=season,
                status=status,
                venue=venue,
                date_from=date_from,
                date_to=date_to,
                limit=limit,
            )
        )

    # ── Persons ───────────────────────────────────────────────────────────

    async def get_person(self, person_id: int) -> PersonResponse:
        """Get a person (player, coach, referee) by ID."""
        return PersonResponse.model_validate(await self._get(f"/persons/{person_id}"))

    async def get_person_matches(
        self,
        person_id: int,
        *,
        date_from: str | None = None,
        date_to: str | None = None,
        status: str | None = None,
        competitions: str | None = None,
        limit: int | None = None,
        offset: int | None = None,
    ) -> MatchListResponse:
        """List matches for a specific person."""
        return MatchListResponse.model_validate(
            await self._get(
                f"/persons/{person_id}/matches",
                date_from=date_from,
                date_to=date_to,
                status=status,
                competitions=competitions,
                limit=limit,
                offset=offset,
            )
        )
