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

from collections.abc import Callable
from dataclasses import dataclass
import datetime
import logging
from typing import cast

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfPower
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import UNDEFINED
from homeassistant.helpers.update_coordinator import (
    CoordinatorEntity,
    DataUpdateCoordinator,
)
from homeassistant.util import dt as dt_util

from .const import COORDINATOR, DOMAIN, NAME, SENSORS

ICON = "mdi:flash"
_LOGGER = logging.getLogger(__name__)

INVERTERS_KEY = "inverters"
LAST_REPORTED_KEY = "last_reported"


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

    value_fn: Callable[[tuple[float, str]], datetime.datetime | float | None]


@dataclass
class EnvoySensorEntityDescription(SensorEntityDescription, EnvoyRequiredKeysMixin):
    """Describes an Envoy inverter sensor entity."""


def _inverter_last_report_time(
    watt_report_time: tuple[float, str]
) -> datetime.datetime | None:
    if (report_time := watt_report_time[1]) is None:
        return None
    if (last_reported_dt := dt_util.parse_datetime(report_time)) is None:
        return None
    if last_reported_dt.tzinfo is None:
        return last_reported_dt.replace(tzinfo=dt_util.UTC)
    return last_reported_dt


INVERTER_SENSORS = (
    EnvoySensorEntityDescription(
        key=INVERTERS_KEY,
        native_unit_of_measurement=UnitOfPower.WATT,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.POWER,
        value_fn=lambda watt_report_time: watt_report_time[0],
    ),
    EnvoySensorEntityDescription(
        key=LAST_REPORTED_KEY,
        name="Last Reported",
        device_class=SensorDeviceClass.TIMESTAMP,
        entity_registry_enabled_default=False,
        value_fn=_inverter_last_report_time,
    ),
)


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up envoy sensor platform."""
    data: dict = hass.data[DOMAIN][config_entry.entry_id]
    coordinator: DataUpdateCoordinator = data[COORDINATOR]
    envoy_data: dict = coordinator.data
    envoy_name: str = data[NAME]
    envoy_serial_num = config_entry.unique_id
    assert envoy_serial_num is not None
    _LOGGER.debug("Envoy data: %s", envoy_data)

    entities: list[Envoy | EnvoyInverter] = []
    for description in SENSORS:
        sensor_data = envoy_data.get(description.key)
        if isinstance(sensor_data, str) and "not available" in sensor_data:
            continue
        entities.append(
            Envoy(
                coordinator,
                description,
                envoy_name,
                envoy_serial_num,
            )
        )

    if production := envoy_data.get("inverters_production"):
        entities.extend(
            EnvoyInverter(
                coordinator,
                description,
                envoy_name,
                envoy_serial_num,
                str(inverter),
            )
            for description in INVERTER_SENSORS
            for inverter in production
        )

    async_add_entities(entities)


class Envoy(CoordinatorEntity, SensorEntity):
    """Envoy inverter entity."""

    _attr_icon = ICON

    def __init__(
        self,
        coordinator: DataUpdateCoordinator,
        description: SensorEntityDescription,
        envoy_name: str,
        envoy_serial_num: str,
    ) -> None:
        """Initialize Envoy entity."""
        self.entity_description = description
        self._attr_name = f"{envoy_name} {description.name}"
        self._attr_unique_id = f"{envoy_serial_num}_{description.key}"
        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, envoy_serial_num)},
            manufacturer="Enphase",
            model="Envoy",
            name=envoy_name,
        )
        super().__init__(coordinator)

    @property
    def native_value(self) -> float | None:
        """Return the state of the sensor."""
        if (value := self.coordinator.data.get(self.entity_description.key)) is None:
            return None
        return cast(float, value)


class EnvoyInverter(CoordinatorEntity, SensorEntity):
    """Envoy inverter entity."""

    _attr_icon = ICON
    entity_description: EnvoySensorEntityDescription

    def __init__(
        self,
        coordinator: DataUpdateCoordinator,
        description: EnvoySensorEntityDescription,
        envoy_name: str,
        envoy_serial_num: str,
        serial_number: str,
    ) -> None:
        """Initialize Envoy inverter entity."""
        self.entity_description = description
        self._serial_number = serial_number
        if description.name is not UNDEFINED:
            self._attr_name = (
                f"{envoy_name} Inverter {serial_number} {description.name}"
            )
        else:
            self._attr_name = f"{envoy_name} Inverter {serial_number}"
        if description.key == INVERTERS_KEY:
            self._attr_unique_id = serial_number
        else:
            self._attr_unique_id = f"{serial_number}_{description.key}"
        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, serial_number)},
            name=f"Inverter {serial_number}",
            manufacturer="Enphase",
            model="Inverter",
            via_device=(DOMAIN, envoy_serial_num),
        )
        super().__init__(coordinator)

    @property
    def native_value(self) -> datetime.datetime | float | None:
        """Return the state of the sensor."""
        watt_report_time: tuple[float, str] = self.coordinator.data[
            "inverters_production"
        ][self._serial_number]
        return self.entity_description.value_fn(watt_report_time)