"""Support for Melissa Climate A/C."""
from __future__ import annotations

import logging
from typing import Any

from homeassistant.components.climate import (
    FAN_AUTO,
    FAN_HIGH,
    FAN_LOW,
    FAN_MEDIUM,
    ClimateEntity,
    ClimateEntityFeature,
    HVACMode,
)
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from . import DATA_MELISSA

_LOGGER = logging.getLogger(__name__)

OP_MODES = [
    HVACMode.HEAT,
    HVACMode.COOL,
    HVACMode.DRY,
    HVACMode.FAN_ONLY,
    HVACMode.OFF,
]

FAN_MODES = [FAN_AUTO, FAN_HIGH, FAN_MEDIUM, FAN_LOW]


async def async_setup_platform(
    hass: HomeAssistant,
    config: ConfigType,
    async_add_entities: AddEntitiesCallback,
    discovery_info: DiscoveryInfoType | None = None,
) -> None:
    """Iterate through and add all Melissa devices."""
    api = hass.data[DATA_MELISSA]
    devices = (await api.async_fetch_devices()).values()

    all_devices = []

    for device in devices:
        if device["type"] == "melissa":
            all_devices.append(MelissaClimate(api, device["serial_number"], device))

    async_add_entities(all_devices)


class MelissaClimate(ClimateEntity):
    """Representation of a Melissa Climate device."""

    _attr_hvac_modes = OP_MODES
    _attr_supported_features = (
        ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
    )
    _attr_temperature_unit = UnitOfTemperature.CELSIUS

    def __init__(self, api, serial_number, init_data):
        """Initialize the climate device."""
        self._name = init_data["name"]
        self._api = api
        self._serial_number = serial_number
        self._data = init_data["controller_log"]
        self._state = None
        self._cur_settings = None

    @property
    def name(self):
        """Return the name of the thermostat, if any."""
        return self._name

    @property
    def fan_mode(self):
        """Return the current fan mode."""
        if self._cur_settings is not None:
            return self.melissa_fan_to_hass(self._cur_settings[self._api.FAN])

    @property
    def current_temperature(self):
        """Return the current temperature."""
        if self._data:
            return self._data[self._api.TEMP]

    @property
    def current_humidity(self):
        """Return the current humidity value."""
        if self._data:
            return self._data[self._api.HUMIDITY]

    @property
    def target_temperature_step(self):
        """Return the supported step of target temperature."""
        return PRECISION_WHOLE

    @property
    def hvac_mode(self) -> HVACMode | None:
        """Return the current operation mode."""
        if self._cur_settings is None:
            return None

        is_on = self._cur_settings[self._api.STATE] in (
            self._api.STATE_ON,
            self._api.STATE_IDLE,
        )

        if not is_on:
            return HVACMode.OFF

        return self.melissa_op_to_hass(self._cur_settings[self._api.MODE])

    @property
    def fan_modes(self):
        """List of available fan modes."""
        return FAN_MODES

    @property
    def target_temperature(self):
        """Return the temperature we try to reach."""
        if self._cur_settings is None:
            return None
        return self._cur_settings[self._api.TEMP]

    @property
    def min_temp(self):
        """Return the minimum supported temperature for the thermostat."""
        return 16

    @property
    def max_temp(self):
        """Return the maximum supported temperature for the thermostat."""
        return 30

    async def async_set_temperature(self, **kwargs: Any) -> None:
        """Set new target temperature."""
        temp = kwargs.get(ATTR_TEMPERATURE)
        await self.async_send({self._api.TEMP: temp})

    async def async_set_fan_mode(self, fan_mode: str) -> None:
        """Set fan mode."""
        melissa_fan_mode = self.hass_fan_to_melissa(fan_mode)
        await self.async_send({self._api.FAN: melissa_fan_mode})

    async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
        """Set operation mode."""
        if hvac_mode == HVACMode.OFF:
            await self.async_send({self._api.STATE: self._api.STATE_OFF})
            return

        mode = self.hass_mode_to_melissa(hvac_mode)
        await self.async_send(
            {self._api.MODE: mode, self._api.STATE: self._api.STATE_ON}
        )

    async def async_send(self, value):
        """Send action to service."""
        try:
            old_value = self._cur_settings.copy()
            self._cur_settings.update(value)
        except AttributeError:
            old_value = None
        if not await self._api.async_send(
            self._serial_number, "melissa", self._cur_settings
        ):
            self._cur_settings = old_value

    async def async_update(self) -> None:
        """Get latest data from Melissa."""
        try:
            self._data = (await self._api.async_status(cached=True))[
                self._serial_number
            ]
            self._cur_settings = (
                await self._api.async_cur_settings(self._serial_number)
            )["controller"]["_relation"]["command_log"]
        except KeyError:
            _LOGGER.warning("Unable to update entity %s", self.entity_id)

    def melissa_op_to_hass(self, mode):
        """Translate Melissa modes to hass states."""
        if mode == self._api.MODE_HEAT:
            return HVACMode.HEAT
        if mode == self._api.MODE_COOL:
            return HVACMode.COOL
        if mode == self._api.MODE_DRY:
            return HVACMode.DRY
        if mode == self._api.MODE_FAN:
            return HVACMode.FAN_ONLY
        _LOGGER.warning("Operation mode %s could not be mapped to hass", mode)
        return None

    def melissa_fan_to_hass(self, fan):
        """Translate Melissa fan modes to hass modes."""
        if fan == self._api.FAN_AUTO:
            return FAN_AUTO
        if fan == self._api.FAN_LOW:
            return FAN_LOW
        if fan == self._api.FAN_MEDIUM:
            return FAN_MEDIUM
        if fan == self._api.FAN_HIGH:
            return FAN_HIGH
        _LOGGER.warning("Fan mode %s could not be mapped to hass", fan)
        return None

    def hass_mode_to_melissa(self, mode):
        """Translate hass states to melissa modes."""
        if mode == HVACMode.HEAT:
            return self._api.MODE_HEAT
        if mode == HVACMode.COOL:
            return self._api.MODE_COOL
        if mode == HVACMode.DRY:
            return self._api.MODE_DRY
        if mode == HVACMode.FAN_ONLY:
            return self._api.MODE_FAN
        _LOGGER.warning("Melissa have no setting for %s mode", mode)

    def hass_fan_to_melissa(self, fan):
        """Translate hass fan modes to melissa modes."""
        if fan == FAN_AUTO:
            return self._api.FAN_AUTO
        if fan == FAN_LOW:
            return self._api.FAN_LOW
        if fan == FAN_MEDIUM:
            return self._api.FAN_MEDIUM
        if fan == FAN_HIGH:
            return self._api.FAN_HIGH
        _LOGGER.warning("Melissa have no setting for %s fan mode", fan)