"""Define an API client."""
from __future__ import annotations

from typing import Any, cast

from aiohttp import ClientSession, ClientTimeout
from aiohttp.client_exceptions import ClientError
from pydantic import ValidationError

from aiopurpleair.const import LOGGER
from aiopurpleair.endpoints.sensors import SensorsEndpoints
from aiopurpleair.errors import RequestError, raise_error
from aiopurpleair.helpers.model import PurpleAirBaseModel, PurpleAirBaseModelT
from aiopurpleair.models.keys import GetKeysResponse

API_URL_BASE = "https://api.purpleair.com/v1"

DEFAULT_TIMEOUT = 10

MAP_URL_BASE = "https://map.purpleair.com/1/mAQI/a10/p604800/cC0"


class API:
    """Define the API object."""

    def __init__(
        self,
        api_key: str,
        *,
        session: ClientSession | None = None,
    ) -> None:
        """Initialize.

        Args:
            api_key: A PurpleAir API key.
            session: An optional aiohttp ClientSession.
        """
        self._api_key = api_key
        self._session = session

        self.sensors = SensorsEndpoints(self.async_request)

    async def async_check_api_key(self) -> GetKeysResponse:
        """Check the validity of the API key.

        Returns:
            An API response payload.
        """
        return await self.async_request("get", "/keys", GetKeysResponse)

    async def async_request(
        self,
        method: str,
        endpoint: str,
        response_model: type[PurpleAirBaseModel],
        **kwargs: dict[str, Any],
    ) -> PurpleAirBaseModelT:
        """Make an API request.

        Args:
            method: An HTTP method.
            endpoint: A relative API endpoint.
            response_model: A Pydantic model to parse the response data with.
            **kwargs: Additional kwargs to send with the request.

        Returns:
            An API response payload in the form of a Pydantic model.

        Raises:
            RequestError: Raised when response data can't be validated.
        """
        url: str = f"{API_URL_BASE}{endpoint}"

        kwargs.setdefault("headers", {})
        if self._api_key:
            kwargs["headers"]["X-API-Key"] = self._api_key

        if use_running_session := self._session and not self._session.closed:
            session = self._session
        else:
            session = ClientSession(timeout=ClientTimeout(total=DEFAULT_TIMEOUT))

        data: dict[str, Any] = {}

        try:
            async with session.request(method, url, **kwargs) as resp:
                data = await resp.json()
                raising_err = None

                try:
                    resp.raise_for_status()
                except ClientError as err:
                    raising_err = err

                raise_error(resp, data, raising_err)
        finally:
            if not use_running_session:
                await session.close()

        LOGGER.debug("Data received for %s: %s", endpoint, data)

        try:
            return cast(PurpleAirBaseModelT, response_model.model_validate(data))
        except ValidationError as err:
            raise RequestError(
                f"Error while parsing response from {endpoint}: {err}"
            ) from err

    def get_map_url(self, sensor_index: int) -> str:
        """Get the map URL for a sensor index.

        Args:
            sensor_index: The sensor index to get the map URL for.

        Returns:
            A MAP URL.
        """
        return f"{MAP_URL_BASE}?select={sensor_index}"
