"""Support for a ScreenLogic heating device."""

from dataclasses import dataclass
import logging
from typing import Any

from screenlogicpy.const.common import UNIT, ScreenLogicCommunicationError
from screenlogicpy.const.data import ATTR, DEVICE, VALUE
from screenlogicpy.const.msg import CODE
from screenlogicpy.device_const.heat import HEAT_MODE
from screenlogicpy.device_const.system import EQUIPMENT_FLAG

from homeassistant.components.climate import (
    ATTR_PRESET_MODE,
    ClimateEntity,
    ClimateEntityDescription,
    ClimateEntityFeature,
    HVACAction,
    HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity

from .const import DOMAIN as SL_DOMAIN
from .coordinator import ScreenlogicDataUpdateCoordinator
from .entity import ScreenLogicPushEntity, ScreenLogicPushEntityDescription

_LOGGER = logging.getLogger(__name__)


SUPPORTED_MODES = [HVACMode.OFF, HVACMode.HEAT]

SUPPORTED_PRESETS = [
    HEAT_MODE.SOLAR,
    HEAT_MODE.SOLAR_PREFERRED,
    HEAT_MODE.HEATER,
]


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up entry."""
    coordinator: ScreenlogicDataUpdateCoordinator = hass.data[SL_DOMAIN][
        config_entry.entry_id
    ]

    gateway = coordinator.gateway

    async_add_entities(
        ScreenLogicClimate(
            coordinator,
            ScreenLogicClimateDescription(
                subscription_code=CODE.STATUS_CHANGED,
                data_root=(DEVICE.BODY,),
                key=body_index,
            ),
        )
        for body_index in gateway.get_data(DEVICE.BODY)
    )


@dataclass(frozen=True, kw_only=True)
class ScreenLogicClimateDescription(
    ClimateEntityDescription, ScreenLogicPushEntityDescription
):
    """Describes a ScreenLogic climate entity."""


class ScreenLogicClimate(ScreenLogicPushEntity, ClimateEntity, RestoreEntity):
    """Represents a ScreenLogic climate entity."""

    entity_description: ScreenLogicClimateDescription
    _attr_hvac_modes = SUPPORTED_MODES
    _attr_supported_features = (
        ClimateEntityFeature.TARGET_TEMPERATURE
        | ClimateEntityFeature.PRESET_MODE
        | ClimateEntityFeature.TURN_OFF
        | ClimateEntityFeature.TURN_ON
    )
    _enable_turn_on_off_backwards_compatibility = False

    def __init__(self, coordinator, entity_description) -> None:
        """Initialize a ScreenLogic climate entity."""
        super().__init__(coordinator, entity_description)
        self._configured_heat_modes = []
        # Is solar listed as available equipment?
        if EQUIPMENT_FLAG.SOLAR in self.gateway.equipment_flags:
            self._configured_heat_modes.extend(
                [HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERRED]
            )
        self._configured_heat_modes.append(HEAT_MODE.HEATER)
        self._attr_preset_modes = [
            HEAT_MODE(mode_num).title for mode_num in self._configured_heat_modes
        ]

        self._attr_min_temp = self.entity_data[ATTR.MIN_SETPOINT]
        self._attr_max_temp = self.entity_data[ATTR.MAX_SETPOINT]
        self._attr_name = self.entity_data[VALUE.HEAT_STATE][ATTR.NAME]
        self._last_preset = None

    @property
    def current_temperature(self) -> float:
        """Return water temperature."""
        return self.entity_data[VALUE.LAST_TEMPERATURE][ATTR.VALUE]

    @property
    def target_temperature(self) -> float:
        """Target temperature."""
        return self.entity_data[VALUE.HEAT_SETPOINT][ATTR.VALUE]

    @property
    def temperature_unit(self) -> str:
        """Return the unit of measurement."""
        if self.gateway.temperature_unit == UNIT.CELSIUS:
            return UnitOfTemperature.CELSIUS
        return UnitOfTemperature.FAHRENHEIT

    @property
    def hvac_mode(self) -> HVACMode:
        """Return the current hvac mode."""
        if self.entity_data[VALUE.HEAT_MODE][ATTR.VALUE] > 0:
            return HVACMode.HEAT
        return HVACMode.OFF

    @property
    def hvac_action(self) -> HVACAction:
        """Return the current action of the heater."""
        if self.entity_data[VALUE.HEAT_STATE][ATTR.VALUE] > 0:
            return HVACAction.HEATING
        if self.hvac_mode == HVACMode.HEAT:
            return HVACAction.IDLE
        return HVACAction.OFF

    @property
    def preset_mode(self) -> str:
        """Return current/last preset mode."""
        if self.hvac_mode == HVACMode.OFF:
            return HEAT_MODE(self._last_preset).title
        return HEAT_MODE(self.entity_data[VALUE.HEAT_MODE][ATTR.VALUE]).title

    async def async_set_temperature(self, **kwargs: Any) -> None:
        """Change the setpoint of the heater."""
        if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
            raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}")

        try:
            await self.gateway.async_set_heat_temp(
                int(self._data_key), int(temperature)
            )
        except ScreenLogicCommunicationError as sle:
            raise HomeAssistantError(
                f"Failed to set_temperature {temperature} on body"
                f" {self.entity_data[ATTR.BODY_TYPE][ATTR.VALUE]}:"
                f" {sle.msg}"
            ) from sle
        _LOGGER.debug("Set temperature for body %s to %s", self._data_key, temperature)

    async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
        """Set the operation mode."""
        if hvac_mode == HVACMode.OFF:
            mode = HEAT_MODE.OFF
        else:
            mode = HEAT_MODE.parse(self.preset_mode)

        try:
            await self.gateway.async_set_heat_mode(int(self._data_key), int(mode.value))
        except ScreenLogicCommunicationError as sle:
            raise HomeAssistantError(
                f"Failed to set_hvac_mode {mode.name} on body"
                f" {self.entity_data[ATTR.BODY_TYPE][ATTR.VALUE]}:"
                f" {sle.msg}"
            ) from sle
        _LOGGER.debug("Set hvac_mode on body %s to %s", self._data_key, mode.name)

    async def async_set_preset_mode(self, preset_mode: str) -> None:
        """Set the preset mode."""
        mode = HEAT_MODE.parse(preset_mode)
        _LOGGER.debug("Setting last_preset to %s", mode.name)
        self._last_preset = mode.value
        if self.hvac_mode == HVACMode.OFF:
            return

        try:
            await self.gateway.async_set_heat_mode(int(self._data_key), int(mode.value))
        except ScreenLogicCommunicationError as sle:
            raise HomeAssistantError(
                f"Failed to set_preset_mode {mode.name} on body"
                f" {self.entity_data[ATTR.BODY_TYPE][ATTR.VALUE]}:"
                f" {sle.msg}"
            ) from sle
        _LOGGER.debug("Set preset_mode on body %s to %s", self._data_key, mode.name)

    async def async_added_to_hass(self) -> None:
        """Run when entity is about to be added."""
        await super().async_added_to_hass()

        _LOGGER.debug("Startup last preset is %s", self._last_preset)
        if self._last_preset is not None:
            return
        prev_state = await self.async_get_last_state()
        if (
            prev_state is not None
            and prev_state.attributes.get(ATTR_PRESET_MODE) is not None
        ):
            mode = HEAT_MODE.parse(prev_state.attributes.get(ATTR_PRESET_MODE))
            _LOGGER.debug(
                "Startup setting last_preset to %s from prev_state",
                mode.name,
            )
            self._last_preset = mode.value
        else:
            mode = HEAT_MODE.parse(self._configured_heat_modes[0])
            _LOGGER.debug(
                "Startup setting last_preset to default (%s)",
                mode.name,
            )
            self._last_preset = mode.value