"""Async client for Essent dynamic pricing."""

from __future__ import annotations

from datetime import datetime, timezone
from http import HTTPStatus
from zoneinfo import ZoneInfo

from aiohttp import ClientError, ClientResponse, ClientSession, ClientTimeout
from mashumaro.exceptions import ExtraKeysError, InvalidFieldValue, MissingField

from .exceptions import EssentConnectionError, EssentDataError, EssentResponseError
from .models import (
    EnergyBlock,
    EnergyData,
    EssentPrices,
    PriceResponse,
    PriceDay,
    Tariff,
)

API_ENDPOINT = "https://www.essent.nl/api/public/dynamicpricing/dynamic-prices/v1"
CLIENT_TIMEOUT = ClientTimeout(total=10)
ESSENT_TIME_ZONE = ZoneInfo("Europe/Amsterdam")


def _normalize_tariff_datetime(value: datetime | None) -> datetime | None:
    """Normalize tariff datetimes to the Essent timezone."""
    if value is None:
        return None
    if value.tzinfo is None:
        return value.replace(tzinfo=ESSENT_TIME_ZONE)
    return value.astimezone(ESSENT_TIME_ZONE)


def _tariff_sort_key(tariff: Tariff) -> datetime:
    """Sort key for tariffs based on start time."""
    return tariff.start or datetime.max.replace(tzinfo=timezone.utc)


def _prepare_tariffs(tariffs: list[Tariff], energy_type: str) -> list[Tariff]:
    """Normalize tariffs to timezone-aware datetimes and sort them."""
    prepared: list[Tariff] = []
    for tariff in tariffs:
        if tariff.start is None or tariff.end is None:
            raise EssentDataError(f"Tariff missing start or end for {energy_type}")
        prepared.append(
            Tariff(
                start=_normalize_tariff_datetime(tariff.start),
                end=_normalize_tariff_datetime(tariff.end),
                total_amount=tariff.total_amount,
                total_amount_ex=tariff.total_amount_ex,
                total_amount_vat=tariff.total_amount_vat,
                groups=list(tariff.groups),
            )
        )
    return sorted(prepared, key=_tariff_sort_key)


def _normalize_unit(unit: str) -> str:
    """Normalize unit strings to human-friendly values."""
    unit_normalized = unit.replace("³", "3").lower()
    if unit_normalized == "kwh":
        return "kWh"
    if unit_normalized in {"m3", "m^3"}:
        return "m³"
    return unit


class EssentClient:
    """Client for fetching Essent dynamic pricing data."""

    def __init__(
        self,
        session: ClientSession,
        *,
        endpoint: str = API_ENDPOINT,
        timeout: ClientTimeout = CLIENT_TIMEOUT,
    ) -> None:
        """Initialize the client."""
        self._session = session
        self._endpoint = endpoint
        self._timeout = timeout

    async def async_get_prices(self) -> EssentPrices:
        """Fetch and normalize Essent dynamic pricing data."""
        response = await self._request()
        body = await response.text()

        if response.status != HTTPStatus.OK:
            raise EssentResponseError(
                f"Unexpected status {response.status} from Essent API: {body}"
            )

        try:
            price_response = PriceResponse.from_dict(await response.json())
        except (MissingField, InvalidFieldValue, ExtraKeysError) as err:
            raise EssentDataError("Invalid data structure for current prices") from err
        except ValueError as err:
            raise EssentResponseError("Invalid JSON received from Essent API") from err

        if not price_response.prices:
            raise EssentDataError("No price data available")

        today, tomorrow = self._select_days(price_response.prices)

        if today.electricity is None and today.gas is None:
            raise EssentDataError("Response missing both electricity and gas data")

        electricity_data = None
        if today.electricity is not None:
            electricity_data = self._normalize_energy_block(
                today.electricity,
                "electricity",
                tomorrow.electricity if tomorrow else None,
            )

        gas_data = None
        if today.gas is not None:
            gas_data = self._normalize_energy_block(
                today.gas,
                "gas",
                tomorrow.gas if tomorrow else None,
            )

        if electricity_data is None:
            raise EssentDataError("Response missing electricity data")

        return EssentPrices(
            electricity=electricity_data,
            gas=gas_data,
        )

    async def _request(self) -> ClientResponse:
        """Perform the HTTP request."""
        try:
            return await self._session.get(
                self._endpoint,
                timeout=self._timeout,
                headers={"Accept": "application/json"},
            )
        except ClientError as err:
            raise EssentConnectionError(f"Error communicating with API: {err}") from err

    @staticmethod
    def _select_days(
        prices: list[PriceDay],
    ) -> tuple[PriceDay, PriceDay | None]:
        """Find entries for today and tomorrow from the price list."""
        if not prices:
            raise EssentDataError("No price data available")

        current_date = datetime.now(timezone.utc).astimezone().date().isoformat()
        today_index = 0
        for idx, price in enumerate(prices):
            if price.date == current_date:
                today_index = idx
                break

        today = prices[today_index]
        tomorrow: PriceDay | None = None
        if today_index + 1 < len(prices):
            tomorrow = prices[today_index + 1]

        return today, tomorrow

    def _normalize_energy_block(
        self,
        data: EnergyBlock | None,
        energy_type: str,
        tomorrow: EnergyBlock | None,
    ) -> EnergyData:
        """Normalize the energy block into the client format."""
        if data is None:
            raise EssentDataError(f"No {energy_type} data provided")

        tariffs_today = _prepare_tariffs(data.tariffs, energy_type)
        if not tariffs_today:
            raise EssentDataError(f"No tariffs found for {energy_type}")

        tariffs_tomorrow = (
            _prepare_tariffs(tomorrow.tariffs, energy_type) if tomorrow else []
        )
        unit_raw = (data.unit_of_measurement or data.unit or "").strip()

        amounts = [
            float(total)
            for tariff in tariffs_today
            if (total := tariff.total_amount) is not None
        ]
        if not amounts:
            raise EssentDataError(f"No usable tariff values for {energy_type}")

        if not unit_raw:
            raise EssentDataError(f"No unit provided for {energy_type}")

        return EnergyData(
            tariffs=tariffs_today,
            tariffs_tomorrow=tariffs_tomorrow,
            unit=_normalize_unit(unit_raw),
            min_price=min(amounts),
            avg_price=sum(amounts) / len(amounts),
            max_price=max(amounts),
        )
