"""YoLink Unit convert helper."""

from __future__ import annotations
from collections.abc import Callable
from functools import lru_cache
from enum import IntEnum

from .exception import YoLinkError
from .const import UNIT_NOT_RECOGNIZED_TEMPLATE


class UnitOfVolume(IntEnum):
    """Unit of meter."""

    GALLONS = 0
    CENTUM_CUBIC_FEET = 1
    CUBIC_METERS = 2
    LITERS = 3


_IN_TO_M = 0.0254  # 1 inch = 0.0254 m
_FOOT_TO_M = _IN_TO_M * 12  # 12 inches = 1 foot (0.3048 m)
_L_TO_CUBIC_METER = 0.001  # 1 L = 0.001 m³
_GALLON_TO_CUBIC_METER = 231 * pow(_IN_TO_M, 3)  # US gallon is 231 cubic inches
_CUBIC_FOOT_TO_CUBIC_METER = pow(_FOOT_TO_M, 3)


# source code from homeassistant.util.unit_conversion.py
class BaseUnitConverter:
    """Define the format of a conversion utility."""

    UNIT_CLASS: str
    NORMALIZED_UNIT: str | None
    VALID_UNITS: set[str | None]

    _UNIT_CONVERSION: dict[str | None, float]

    @classmethod
    def convert(cls, value: float, from_unit: str | None, to_unit: str | None) -> float:
        """Convert one unit of measurement to another."""
        return cls.converter_factory(from_unit, to_unit)(value)

    @classmethod
    @lru_cache
    def converter_factory(
        cls, from_unit: str | None, to_unit: str | None
    ) -> Callable[[float], float]:
        """Return a function to convert one unit of measurement to another."""
        if from_unit == to_unit:
            return lambda value: value
        from_ratio, to_ratio = cls._get_from_to_ratio(from_unit, to_unit)
        return lambda val: (val / from_ratio) * to_ratio

    @classmethod
    def _get_from_to_ratio(
        cls, from_unit: str | None, to_unit: str | None
    ) -> tuple[float, float]:
        """Get unit ratio between units of measurement."""
        unit_conversion = cls._UNIT_CONVERSION
        try:
            return unit_conversion[from_unit], unit_conversion[to_unit]
        except KeyError as err:
            raise YoLinkError(
                UNIT_NOT_RECOGNIZED_TEMPLATE.format(err.args[0], cls.UNIT_CLASS)
            ) from err

    @classmethod
    @lru_cache
    def converter_factory_allow_none(
        cls, from_unit: str | None, to_unit: str | None
    ) -> Callable[[float | None], float | None]:
        """Return a function to convert one unit of measurement to another which allows None."""
        if from_unit == to_unit:
            return lambda value: value
        from_ratio, to_ratio = cls._get_from_to_ratio(from_unit, to_unit)
        return lambda val: None if val is None else (val / from_ratio) * to_ratio

    @classmethod
    @lru_cache
    def get_unit_ratio(cls, from_unit: str | None, to_unit: str | None) -> float:
        """Get unit ratio between units of measurement."""
        from_ratio, to_ratio = cls._get_from_to_ratio(from_unit, to_unit)
        return from_ratio / to_ratio


class VolumeConverter(BaseUnitConverter):
    """Utility to convert volume values."""

    UNIT_CLASS = "volume"
    NORMALIZED_UNIT = UnitOfVolume.CUBIC_METERS
    # Units in terms of m³
    _UNIT_CONVERSION: dict[str | None, float] = {
        UnitOfVolume.LITERS: 1 / _L_TO_CUBIC_METER,
        UnitOfVolume.GALLONS: 1 / _GALLON_TO_CUBIC_METER,
        UnitOfVolume.CUBIC_METERS: 1,
        UnitOfVolume.CENTUM_CUBIC_FEET: 1 / (100 * _CUBIC_FOOT_TO_CUBIC_METER),
    }
    VALID_UNITS = {
        UnitOfVolume.LITERS,
        UnitOfVolume.GALLONS,
        UnitOfVolume.CUBIC_METERS,
        UnitOfVolume.CENTUM_CUBIC_FEET,
    }
