"""Support for Tado hot water zones."""

import logging
from typing import Any

import voluptuous as vol

from homeassistant.components.water_heater import (
    WaterHeaterEntity,
    WaterHeaterEntityFeature,
)
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import VolDictType

from .const import (
    CONST_HVAC_HEAT,
    CONST_MODE_AUTO,
    CONST_MODE_HEAT,
    CONST_MODE_OFF,
    CONST_MODE_SMART_SCHEDULE,
    CONST_OVERLAY_MANUAL,
    CONST_OVERLAY_TADO_MODE,
    CONST_OVERLAY_TIMER,
    TYPE_HOT_WATER,
)
from .coordinator import TadoConfigEntry, TadoDataUpdateCoordinator
from .entity import TadoZoneEntity
from .helper import decide_duration, decide_overlay_mode
from .repairs import manage_water_heater_fallback_issue

_LOGGER = logging.getLogger(__name__)

MODE_AUTO = "auto"
MODE_HEAT = "heat"
MODE_OFF = "off"

OPERATION_MODES = [MODE_AUTO, MODE_HEAT, MODE_OFF]

WATER_HEATER_MAP_TADO = {
    CONST_OVERLAY_MANUAL: MODE_HEAT,
    CONST_OVERLAY_TIMER: MODE_HEAT,
    CONST_OVERLAY_TADO_MODE: MODE_HEAT,
    CONST_HVAC_HEAT: MODE_HEAT,
    CONST_MODE_SMART_SCHEDULE: MODE_AUTO,
    CONST_MODE_OFF: MODE_OFF,
}

SERVICE_WATER_HEATER_TIMER = "set_water_heater_timer"
ATTR_TIME_PERIOD = "time_period"

WATER_HEATER_TIMER_SCHEMA: VolDictType = {
    vol.Required(ATTR_TIME_PERIOD, default="01:00:00"): vol.All(
        cv.time_period, cv.positive_timedelta, lambda td: td.total_seconds()
    ),
    vol.Optional(ATTR_TEMPERATURE): vol.Coerce(float),
}


async def async_setup_entry(
    hass: HomeAssistant,
    entry: TadoConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the Tado water heater platform."""

    coordinator = entry.runtime_data
    entities = await _generate_entities(coordinator)

    platform = entity_platform.async_get_current_platform()

    platform.async_register_entity_service(
        SERVICE_WATER_HEATER_TIMER,
        WATER_HEATER_TIMER_SCHEMA,
        "set_timer",
    )

    async_add_entities(entities, True)

    manage_water_heater_fallback_issue(
        hass=hass,
        water_heater_names=[e.zone_name for e in entities],
        integration_overlay_fallback=coordinator.fallback,
    )


async def _generate_entities(coordinator: TadoDataUpdateCoordinator) -> list:
    """Create all water heater entities."""
    entities = []

    for zone in coordinator.zones:
        if zone["type"] == TYPE_HOT_WATER:
            entity = await create_water_heater_entity(
                coordinator, zone["name"], zone["id"], str(zone["name"])
            )
            entities.append(entity)

    return entities


async def create_water_heater_entity(
    coordinator: TadoDataUpdateCoordinator, name: str, zone_id: int, zone: str
):
    """Create a Tado water heater device."""
    capabilities = await coordinator.get_capabilities(zone_id)

    supports_temperature_control = capabilities["canSetTemperature"]

    if supports_temperature_control and "temperatures" in capabilities:
        temperatures = capabilities["temperatures"]
        min_temp = float(temperatures["celsius"]["min"])
        max_temp = float(temperatures["celsius"]["max"])
    else:
        min_temp = None
        max_temp = None

    return TadoWaterHeater(
        coordinator,
        name,
        zone_id,
        supports_temperature_control,
        min_temp,
        max_temp,
    )


class TadoWaterHeater(TadoZoneEntity, WaterHeaterEntity):
    """Representation of a Tado water heater."""

    _attr_name = None
    _attr_operation_list = OPERATION_MODES
    _attr_temperature_unit = UnitOfTemperature.CELSIUS

    def __init__(
        self,
        coordinator: TadoDataUpdateCoordinator,
        zone_name: str,
        zone_id: int,
        supports_temperature_control: bool,
        min_temp,
        max_temp,
    ) -> None:
        """Initialize of Tado water heater entity."""
        super().__init__(zone_name, coordinator.home_id, zone_id, coordinator)

        self.zone_id = zone_id
        self._attr_unique_id = f"{zone_id} {coordinator.home_id}"

        self._device_is_active = False

        self._supports_temperature_control = supports_temperature_control
        self._min_temperature = min_temp
        self._max_temperature = max_temp

        self._target_temp: float | None = None

        self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE
        if self._supports_temperature_control:
            self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE

        self._current_tado_hvac_mode = CONST_MODE_SMART_SCHEDULE
        self._overlay_mode = CONST_MODE_SMART_SCHEDULE
        self._tado_zone_data: Any = None

        self._async_update_data()

    @callback
    def _handle_coordinator_update(self) -> None:
        """Handle updated data from the coordinator."""
        self._async_update_data()
        super()._handle_coordinator_update()

    @property
    def current_operation(self) -> str | None:
        """Return current readable operation mode."""
        return WATER_HEATER_MAP_TADO.get(self._current_tado_hvac_mode)

    @property
    def target_temperature(self) -> float | None:
        """Return the temperature we try to reach."""
        return self._tado_zone_data.target_temp

    @property
    def is_away_mode_on(self) -> bool:
        """Return true if away mode is on."""
        return self._tado_zone_data.is_away

    @property
    def min_temp(self) -> float:
        """Return the minimum temperature."""
        return self._min_temperature

    @property
    def max_temp(self) -> float:
        """Return the maximum temperature."""
        return self._max_temperature

    async def async_set_operation_mode(self, operation_mode: str) -> None:
        """Set new operation mode."""
        mode = None

        if operation_mode == MODE_OFF:
            mode = CONST_MODE_OFF
        elif operation_mode == MODE_AUTO:
            mode = CONST_MODE_SMART_SCHEDULE
        elif operation_mode == MODE_HEAT:
            mode = CONST_MODE_HEAT

        await self._control_heater(hvac_mode=mode)
        await self.coordinator.async_request_refresh()

    async def set_timer(self, time_period: int, temperature: float | None = None):
        """Set the timer on the entity, and temperature if supported."""
        if not self._supports_temperature_control and temperature is not None:
            temperature = None

        await self._control_heater(
            hvac_mode=CONST_MODE_HEAT, target_temp=temperature, duration=time_period
        )
        await self.coordinator.async_request_refresh()

    async def async_set_temperature(self, **kwargs: Any) -> None:
        """Set new target temperature."""
        temperature = kwargs.get(ATTR_TEMPERATURE)
        if not self._supports_temperature_control or temperature is None:
            return

        if self._current_tado_hvac_mode not in (
            CONST_MODE_OFF,
            CONST_MODE_AUTO,
            CONST_MODE_SMART_SCHEDULE,
        ):
            await self._control_heater(target_temp=temperature)
            return

        await self._control_heater(target_temp=temperature, hvac_mode=CONST_MODE_HEAT)
        await self.coordinator.async_request_refresh()

    @callback
    def _async_update_callback(self) -> None:
        """Load tado data and update state."""
        self._async_update_data()
        self.async_write_ha_state()

    @callback
    def _async_update_data(self) -> None:
        """Load tado data."""
        _LOGGER.debug("Updating water_heater platform for zone %d", self.zone_id)
        self._tado_zone_data = self.coordinator.data["zone"][self.zone_id]
        self._current_tado_hvac_mode = self._tado_zone_data.current_hvac_mode

    async def _control_heater(
        self,
        hvac_mode: str | None = None,
        target_temp: float | None = None,
        duration: int | None = None,
    ):
        """Send new target temperature."""
        if hvac_mode:
            self._current_tado_hvac_mode = hvac_mode

        if target_temp:
            self._target_temp = target_temp

        # Set a target temperature if we don't have any
        if self._target_temp is None:
            self._target_temp = self.min_temp

        if self._current_tado_hvac_mode == CONST_MODE_SMART_SCHEDULE:
            _LOGGER.debug(
                "Switching to SMART_SCHEDULE for zone %s (%d)",
                self.zone_name,
                self.zone_id,
            )
            await self.coordinator.reset_zone_overlay(self.zone_id)
            await self.coordinator.async_request_refresh()
            return

        if self._current_tado_hvac_mode == CONST_MODE_OFF:
            _LOGGER.debug(
                "Switching to OFF for zone %s (%d)", self.zone_name, self.zone_id
            )
            await self.coordinator.set_zone_off(
                self.zone_id, CONST_OVERLAY_MANUAL, TYPE_HOT_WATER
            )
            return

        overlay_mode = decide_overlay_mode(
            coordinator=self.coordinator,
            duration=duration,
            zone_id=self.zone_id,
        )
        duration = decide_duration(
            coordinator=self.coordinator,
            duration=duration,
            zone_id=self.zone_id,
            overlay_mode=overlay_mode,
        )
        _LOGGER.debug(
            "Switching to %s for zone %s (%d) with temperature %s",
            self._current_tado_hvac_mode,
            self.zone_name,
            self.zone_id,
            self._target_temp,
        )
        await self.coordinator.set_zone_overlay(
            zone_id=self.zone_id,
            overlay_mode=overlay_mode,
            temperature=self._target_temp,
            duration=duration,
            device_type=TYPE_HOT_WATER,
        )
        self._overlay_mode = self._current_tado_hvac_mode
