"""Asynchronous Python client for the easyEnergy API."""
from __future__ import annotations

import asyncio
import socket
from dataclasses import dataclass
from datetime import date, datetime, timedelta, timezone
from importlib import metadata
from typing import Any, cast

import async_timeout
from aiodns import DNSResolver
from aiodns.error import DNSError
from aiohttp.client import ClientError, ClientSession
from aiohttp.hdrs import METH_GET
from yarl import URL

from .const import API_HOST
from .exceptions import (
    EasyEnergyConnectionError,
    EasyEnergyError,
    EasyEnergyNoDataError,
)
from .models import Electricity, Gas


@dataclass
class EasyEnergy:
    """Main class for handling data fetching from easyEnergy."""

    incl_vat: str = "true"
    request_timeout: float = 10.0
    session: ClientSession | None = None

    _close_session: bool = False

    async def _request(
        self,
        uri: str,
        *,
        method: str = METH_GET,
        params: dict[str, Any] | None = None,
    ) -> Any:
        """Handle a request to the API of easyEnergy.

        Args:
        ----
            uri: Request URI, without '/', for example, 'status'
            method: HTTP method to use, for example, 'GET'
            params: Extra options to improve or limit the response.

        Returns:
        -------
            A Python dictionary (json) with the response from easyEnergy.

        Raises:
        ------
            EasyEnergyConnectionError: An error occurred while
                communicating with the API.
            EasyEnergyError: Received an unexpected response from
                the API.
        """
        version = metadata.version(__package__)

        # EasyEnergy is experiencing IPv6 connection issues.
        # DNS returns an AAAA record with an IPv6 address, but
        # there doesn't appear to be something listening at that.
        # Workaround is to resolve the IPv4 address and use that.
        dns = DNSResolver()
        try:
            result = await dns.query(API_HOST, "A")
        except DNSError as err:
            msg = "Error while resolving EasyEnergy API IPv4 address"
            raise EasyEnergyConnectionError(
                msg,
            ) from err

        if not result:
            msg = "Could not resolve EasyEnergy IPv4 address"
            raise EasyEnergyConnectionError(
                msg,
            )

        url = URL.build(
            scheme="https",
            host=result[0].host,
            path="/nl/api/tariff/",
        ).join(URL(uri))

        headers = {
            "Accept": "application/json, text/plain",
            "User-Agent": f"PythonEasyEnergy/{version}",
            "Host": API_HOST,
        }

        if self.session is None:
            self.session = ClientSession()
            self._close_session = True

        try:
            async with async_timeout.timeout(self.request_timeout):
                response = await self.session.request(
                    method,
                    url,
                    params=params,
                    headers=headers,
                    ssl=True,
                )
                response.raise_for_status()
        except asyncio.TimeoutError as exception:
            msg = "Timeout occurred while connecting to the API."
            raise EasyEnergyConnectionError(
                msg,
            ) from exception
        except (ClientError, socket.gaierror) as exception:
            msg = "Error occurred while communicating with the API."
            raise EasyEnergyConnectionError(
                msg,
            ) from exception

        content_type = response.headers.get("Content-Type", "")
        if "application/json" not in content_type:
            text = await response.text()
            msg = "Unexpected content type response from the easyEnergy API"
            raise EasyEnergyError(
                msg,
                {"Content-Type": content_type, "response": text},
            )

        return cast(dict[str, Any], await response.json())

    async def gas_prices(self, start_date: date, end_date: date) -> Gas:
        """Get gas prices for a given period.

        Args:
        ----
            start_date: Start date of the period.
            end_date: End date of the period.

        Returns:
        -------
            A Python dictionary with the response from easyEnergy.

        Raises:
        ------
            EasyEnergyNoDataError: No gas prices found for this period.
        """
        start_date_utc: datetime
        end_date_utc: datetime
        utcnow: datetime = datetime.now(timezone.utc)
        if utcnow.hour >= 5 and utcnow.hour <= 22:
            # Set start_date to 05:00:00 and the end_date to 05:00:00 UTC next day
            start_date_utc = datetime(
                start_date.year,
                start_date.month,
                start_date.day,
                5,
                0,
                0,
                tzinfo=timezone.utc,
            )
            end_date_utc = datetime(
                end_date.year,
                end_date.month,
                end_date.day,
                5,
                0,
                0,
                tzinfo=timezone.utc,
            ) + timedelta(days=1)
        else:
            # Set start_date to 05:00:00 prev day and the end_date to 05:00:00 UTC
            start_date_utc = datetime(
                start_date.year,
                start_date.month,
                start_date.day,
                5,
                0,
                0,
                tzinfo=timezone.utc,
            ) - timedelta(days=1)
            end_date_utc = datetime(
                end_date.year,
                end_date.month,
                end_date.day,
                5,
                0,
                0,
                tzinfo=timezone.utc,
            )

        data = await self._request(
            "getlebatariffs",
            params={
                "startTimestamp": start_date_utc.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
                "endTimestamp": end_date_utc.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
                "includeVat": self.incl_vat.lower(),
            },
        )

        if len(data) == 0:
            msg = "No gas prices found for this period."
            raise EasyEnergyNoDataError(msg)
        return Gas.from_dict(data)

    async def energy_prices(self, start_date: date, end_date: date) -> Electricity:
        """Get energy prices for a given period.

        Args:
        ----
            start_date: Start date of the period.
            end_date: End date of the period.

        Returns:
        -------
            A Python dictionary with the response from easyEnergy.

        Raises:
        ------
            EasyEnergyNoDataError: No energy prices found for this period.
        """
        # Set the start date to 23:00:00 previous day and the end date to 23:00:00 UTC
        start_date_utc: datetime = datetime(
            start_date.year,
            start_date.month,
            start_date.day,
            0,
            0,
            0,
            tzinfo=timezone.utc,
        ) - timedelta(hours=1)
        end_date_utc: datetime = datetime(
            end_date.year,
            end_date.month,
            end_date.day,
            23,
            0,
            0,
            tzinfo=timezone.utc,
        )
        data = await self._request(
            "getapxtariffs",
            params={
                "startTimestamp": start_date_utc.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
                "endTimestamp": end_date_utc.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
                "includeVat": self.incl_vat.lower(),
            },
        )

        if len(data) == 0:
            msg = "No energy prices found for this period."
            raise EasyEnergyNoDataError(msg)
        return Electricity.from_dict(data)

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

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

        Returns
        -------
            The EasyEnergy object.
        """
        return self

    async def __aexit__(self, *_exc_info: Any) -> None:
        """Async exit.

        Args:
        ----
            _exc_info: Exec type.
        """
        await self.close()
