"""Shared helpers for parsing numeric state values and converting between units."""

from decimal import Decimal, DecimalException
import logging

from homeassistant.const import UnitOfEnergy
from homeassistant.core import State
from homeassistant.exceptions import HomeAssistantError, TemplateError
from homeassistant.helpers.template import Template
from homeassistant.util.unit_conversion import (
    BaseUnitConverter,
    EnergyConverter,
    PowerConverter,
)

from custom_components.powercalc.const import UNAVAILABLE_STATES, UnitPrefix

_LOGGER = logging.getLogger(__name__)

# Maps a configured energy unit prefix to the resulting energy unit of measurement.
ENERGY_UNIT_PREFIX_MAPPING: dict[str, str] = {
    UnitPrefix.NONE: UnitOfEnergy.WATT_HOUR,
    UnitPrefix.KILO: UnitOfEnergy.KILO_WATT_HOUR,
    UnitPrefix.MEGA: UnitOfEnergy.MEGA_WATT_HOUR,
    UnitPrefix.GIGA: UnitOfEnergy.GIGA_WATT_HOUR,
    UnitPrefix.TERA: UnitOfEnergy.TERA_WATT_HOUR,
}

# Maps any energy or power unit of measurement to the converter that handles it.
UNIT_CONVERTERS: dict[str | None, type[BaseUnitConverter]] = {
    **dict.fromkeys(EnergyConverter.VALID_UNITS, EnergyConverter),
    **dict.fromkeys(PowerConverter.VALID_UNITS, PowerConverter),
}


def parse_decimal(value: object) -> Decimal | None:
    """Parse a numeric value into a Decimal, returning None when it is not a usable number.

    Accepts a Home Assistant State (its ``state`` string is used) or a raw value of any type.
    Unknown or unavailable states and values that are not numeric yield None instead of raising.
    """
    if isinstance(value, State):
        if value.state in UNAVAILABLE_STATES:
            return None
        value = value.state
    if not isinstance(value, (str, int, float, Decimal)):
        return None
    try:
        return Decimal(value)
    except DecimalException, ValueError:
        return None


def evaluate_to_decimal(value: object) -> Decimal | None:
    """Evaluate a value into a Decimal, rendering it first when it is a template.

    Non-template values (strings, numbers) are parsed directly. Returns None when a template
    fails to render, or when the (rendered) value is not a usable number. Unknown/unavailable
    renders yield None silently; any other failure is logged.
    """
    if isinstance(value, Template):
        try:
            value = value.async_render()
        except TemplateError as ex:
            _LOGGER.error("Could not render template %s: %s", value, ex)
            return None
        if value in UNAVAILABLE_STATES:
            return None

    result = parse_decimal(value)
    if result is None:
        _LOGGER.error("Could not convert value %s to a decimal", value)
    return result


def convert_to_decimal(
    value: State | str | float | Decimal,
    from_unit: str | None,
    to_unit: str | None,
) -> Decimal | None:
    """Convert a value between energy/power units and return it as a Decimal.

    Returns None when the value is not numeric or the units cannot be converted.
    """
    parsed = parse_decimal(value)
    if parsed is None:
        return None
    if from_unit == to_unit:
        return parsed
    converter = UNIT_CONVERTERS.get(from_unit)
    if converter is None:
        return None
    try:
        return Decimal(str(converter.convert(float(parsed), from_unit, to_unit)))
    except HomeAssistantError:
        return None
