"""Number platform for Enphase Envoy solar energy monitor."""
from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any

from pyenphase import Envoy, EnvoyDryContactSettings
from pyenphase.const import SupportedFeatures
from pyenphase.models.tariff import EnvoyStorageSettings

from homeassistant.components.number import (
    NumberDeviceClass,
    NumberEntity,
    NumberEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN
from .coordinator import EnphaseUpdateCoordinator
from .entity import EnvoyBaseEntity


@dataclass
class EnvoyRelayRequiredKeysMixin:
    """Mixin for required keys."""

    value_fn: Callable[[EnvoyDryContactSettings], float]


@dataclass
class EnvoyRelayNumberEntityDescription(
    NumberEntityDescription, EnvoyRelayRequiredKeysMixin
):
    """Describes an Envoy Dry Contact Relay number entity."""


@dataclass
class EnvoyStorageSettingsRequiredKeysMixin:
    """Mixin for required keys."""

    value_fn: Callable[[EnvoyStorageSettings], float]
    update_fn: Callable[[Envoy, float], Awaitable[dict[str, Any]]]


@dataclass
class EnvoyStorageSettingsNumberEntityDescription(
    NumberEntityDescription, EnvoyStorageSettingsRequiredKeysMixin
):
    """Describes an Envoy storage mode number entity."""


RELAY_ENTITIES = (
    EnvoyRelayNumberEntityDescription(
        key="soc_low",
        translation_key="cutoff_battery_level",
        device_class=NumberDeviceClass.BATTERY,
        entity_category=EntityCategory.CONFIG,
        value_fn=lambda relay: relay.soc_low,
    ),
    EnvoyRelayNumberEntityDescription(
        key="soc_high",
        translation_key="restore_battery_level",
        device_class=NumberDeviceClass.BATTERY,
        entity_category=EntityCategory.CONFIG,
        value_fn=lambda relay: relay.soc_high,
    ),
)

STORAGE_RESERVE_SOC_ENTITY = EnvoyStorageSettingsNumberEntityDescription(
    key="reserve_soc",
    translation_key="reserve_soc",
    native_unit_of_measurement=PERCENTAGE,
    device_class=NumberDeviceClass.BATTERY,
    value_fn=lambda storage_settings: storage_settings.reserved_soc,
    update_fn=lambda envoy, value: envoy.set_reserve_soc(int(value)),
)


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up Enphase Envoy number platform."""
    coordinator: EnphaseUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
    envoy_data = coordinator.envoy.data
    assert envoy_data is not None
    entities: list[NumberEntity] = []
    if envoy_data.dry_contact_settings:
        entities.extend(
            EnvoyRelayNumberEntity(coordinator, entity, relay)
            for entity in RELAY_ENTITIES
            for relay in envoy_data.dry_contact_settings
        )
    if (
        envoy_data.tariff
        and envoy_data.tariff.storage_settings
        and coordinator.envoy.supported_features & SupportedFeatures.ENCHARGE
    ):
        entities.append(
            EnvoyStorageSettingsNumberEntity(coordinator, STORAGE_RESERVE_SOC_ENTITY)
        )
    async_add_entities(entities)


class EnvoyRelayNumberEntity(EnvoyBaseEntity, NumberEntity):
    """Representation of an Enphase Enpower number entity."""

    entity_description: EnvoyRelayNumberEntityDescription

    def __init__(
        self,
        coordinator: EnphaseUpdateCoordinator,
        description: EnvoyRelayNumberEntityDescription,
        relay_id: str,
    ) -> None:
        """Initialize the Enphase relay number entity."""
        super().__init__(coordinator, description)
        self.envoy = coordinator.envoy
        enpower = self.data.enpower
        assert enpower is not None
        serial_number = enpower.serial_number
        self._relay_id = relay_id
        self._attr_unique_id = f"{serial_number}_relay_{relay_id}_{description.key}"
        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, relay_id)},
            manufacturer="Enphase",
            model="Dry contact relay",
            name=self.data.dry_contact_settings[relay_id].load_name,
            sw_version=str(enpower.firmware_version),
            via_device=(DOMAIN, serial_number),
        )

    @property
    def native_value(self) -> float:
        """Return the state of the relay entity."""
        return self.entity_description.value_fn(
            self.data.dry_contact_settings[self._relay_id]
        )

    async def async_set_native_value(self, value: float) -> None:
        """Update the relay."""
        await self.envoy.update_dry_contact(
            {"id": self._relay_id, self.entity_description.key: int(value)}
        )
        await self.coordinator.async_request_refresh()


class EnvoyStorageSettingsNumberEntity(EnvoyBaseEntity, NumberEntity):
    """Representation of an Enphase storage settings number entity."""

    entity_description: EnvoyStorageSettingsNumberEntityDescription

    def __init__(
        self,
        coordinator: EnphaseUpdateCoordinator,
        description: EnvoyStorageSettingsNumberEntityDescription,
    ) -> None:
        """Initialize the Enphase relay number entity."""
        super().__init__(coordinator, description)
        self.envoy = coordinator.envoy
        assert self.data.enpower is not None
        enpower = self.data.enpower
        self._serial_number = enpower.serial_number
        self._attr_unique_id = f"{self._serial_number}_{description.key}"
        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, self._serial_number)},
            manufacturer="Enphase",
            model="Enpower",
            name=f"Enpower {self._serial_number}",
            sw_version=str(enpower.firmware_version),
            via_device=(DOMAIN, self.envoy_serial_num),
        )

    @property
    def native_value(self) -> float:
        """Return the state of the storage setting entity."""
        assert self.data.tariff is not None
        assert self.data.tariff.storage_settings is not None
        return self.entity_description.value_fn(self.data.tariff.storage_settings)

    async def async_set_native_value(self, value: float) -> None:
        """Update the storage setting."""
        await self.entity_description.update_fn(self.envoy, value)
        await self.coordinator.async_request_refresh()