"""Tankerkoenig API client."""

from __future__ import annotations

import asyncio
from dataclasses import dataclass
from importlib import metadata
import logging
import socket
from typing import Any, Self

from aiohttp import ClientError, ClientResponseError, ClientSession
from yarl import URL

from .const import GasType, Sort
from .exceptions import (
    TankerkoenigConnectionError,
    TankerkoenigConnectionTimeoutError,
    TankerkoenigError,
    TankerkoenigRateLimitError,
)
from .models import (
    PriceInfo,
    PriceInfoResponse,
    Station,
    StationDetailResponse,
    StationListResponse,
)

_LOGGER = logging.getLogger(__name__)

VERSION = metadata.version(__package__)


def _raise_timeout_error(exception: BaseException) -> None:
    """Raise library specific timeout error."""
    msg = "Timeout occurred while connecting to tankerkoenig.de API"
    raise TankerkoenigConnectionTimeoutError(
        msg,
    ) from exception


@dataclass
class Tankerkoenig:
    """Tankerkoenig API client."""

    api_key: str
    session: ClientSession | None = None

    _close_session: bool = False

    async def _request(self, path: str, params: dict[str, Any]) -> str:
        """Handle request to tankerkoenig.de API."""
        _LOGGER.debug("Requesting %s", path)
        url = URL.build(
            scheme="https",
            host="creativecommons.tankerkoenig.de",
            path=path,
            query={"apikey": self.api_key, **params},
        )

        headers = {
            "Accept": "application/json",
        }

        if self.session is None:
            self.session = ClientSession()
            self._close_session = True
            headers.update(
                {
                    "User-Agent": f"aiotankerkoenig/{VERSION}",
                },
            )

        try:
            async with self.session.get(url, headers=headers) as response:
                content_type = response.headers.get("Content-Type", "")
                text = await response.text()
                _LOGGER.debug(
                    "Response from %s (status: %s, content-type: %s): %s",
                    path,
                    response.status,
                    content_type,
                    text.replace(self.api_key, "**redacted**"),
                )

                response.raise_for_status()
        except TimeoutError as exception:
            _raise_timeout_error(exception)
        except asyncio.CancelledError as exception:
            if "timeout" not in str(exception).lower():
                raise
            _raise_timeout_error(exception)
        except (
            ClientError,
            socket.gaierror,
        ) as exception:
            if isinstance(exception, ClientResponseError) and exception.status == 503:
                raise TankerkoenigRateLimitError from exception
            msg = "Error occurred while communicating with the tankerkoenig.de API"
            raise TankerkoenigConnectionError(msg) from exception

        if "application/json" not in content_type:
            msg = "Unexpected content type from tankerkoenig.de API"
            raise TankerkoenigError(
                msg,
                {"Content-Type": content_type, "response": text},
            )

        return text

    async def nearby_stations(
        self,
        coordinates: tuple[float, float],
        radius: int,
        gas_type: GasType,
        sort: Sort,
    ) -> list[Station]:
        """Get nearby stations."""
        _LOGGER.debug(
            "Fetching nearby stations near (%0.2f, %0.2f) radius=%s type=%s sort=%s",
            coordinates[0],
            coordinates[1],
            radius,
            gas_type,
            sort,
        )
        result = await self._request(
            path="/json/list.php",
            params={
                "lat": coordinates[0],
                "lng": coordinates[1],
                "rad": radius,
                "type": gas_type,
                "sort": sort,
            },
        )
        return StationListResponse.from_json(result).stations

    async def station_details(
        self,
        station_id: str,
    ) -> Station:
        """Get station details."""
        _LOGGER.debug("Fetching station details for %s", station_id)
        result = await self._request(
            path="/json/detail.php",
            params={
                "id": station_id,
            },
        )
        return StationDetailResponse.from_json(result).station

    async def prices(
        self,
        station_ids: list[str],
    ) -> dict[str, PriceInfo]:
        """Get station details."""
        _LOGGER.debug("Fetching prices for %d station(s)", len(station_ids))
        result = await self._request(
            path="/json/prices.php",
            params={
                "ids": ",".join(station_ids),
            },
        )
        return PriceInfoResponse.from_json(result).prices

    async def close(self) -> None:
        """Close open client session."""
        if self.session and self._close_session:
            await self.session.close()

    async def __aenter__(self) -> Self:
        """Async enter."""
        return self

    async def __aexit__(self, *_exc_info: object) -> None:
        """Async exit.    _exc_info: Exec type."""
        await self.close()
