"""BSBLAN platform to control a compatible Climate Device."""
from datetime import timedelta
import logging
from typing import Any, Callable, Dict, List, Optional

from bsblan import BSBLan, BSBLanError, Info, State

from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
    ATTR_HVAC_MODE,
    ATTR_PRESET_MODE,
    HVAC_MODE_AUTO,
    HVAC_MODE_HEAT,
    HVAC_MODE_OFF,
    PRESET_ECO,
    PRESET_NONE,
    SUPPORT_PRESET_MODE,
    SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    ATTR_NAME,
    ATTR_TEMPERATURE,
    TEMP_CELSIUS,
    TEMP_FAHRENHEIT,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import HomeAssistantType

from .const import (
    ATTR_IDENTIFIERS,
    ATTR_MANUFACTURER,
    ATTR_MODEL,
    ATTR_TARGET_TEMPERATURE,
    DATA_BSBLAN_CLIENT,
    DOMAIN,
)

_LOGGER = logging.getLogger(__name__)

PARALLEL_UPDATES = 1
SCAN_INTERVAL = timedelta(seconds=20)

SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE

HVAC_MODES = [
    HVAC_MODE_AUTO,
    HVAC_MODE_HEAT,
    HVAC_MODE_OFF,
]

PRESET_MODES = [
    PRESET_ECO,
    PRESET_NONE,
]

HA_STATE_TO_BSBLAN = {
    HVAC_MODE_AUTO: "1",
    HVAC_MODE_HEAT: "3",
    HVAC_MODE_OFF: "0",
}

BSBLAN_TO_HA_STATE = {value: key for key, value in HA_STATE_TO_BSBLAN.items()}

HA_PRESET_TO_BSBLAN = {
    PRESET_ECO: "2",
}

BSBLAN_TO_HA_PRESET = {
    2: PRESET_ECO,
}


async def async_setup_entry(
    hass: HomeAssistantType,
    entry: ConfigEntry,
    async_add_entities: Callable[[List[Entity], bool], None],
) -> None:
    """Set up BSBLan device based on a config entry."""
    bsblan: BSBLan = hass.data[DOMAIN][entry.entry_id][DATA_BSBLAN_CLIENT]
    info = await bsblan.info()
    async_add_entities([BSBLanClimate(entry.entry_id, bsblan, info)], True)


class BSBLanClimate(ClimateEntity):
    """Defines a BSBLan climate device."""

    def __init__(
        self, entry_id: str, bsblan: BSBLan, info: Info,
    ):
        """Initialize BSBLan climate device."""
        self._current_temperature: Optional[float] = None
        self._available = True
        self._current_hvac_mode: Optional[int] = None
        self._target_temperature: Optional[float] = None
        self._info: Info = info
        self.bsblan = bsblan
        self._temperature_unit = None
        self._hvac_mode = None
        self._preset_mode = None
        self._store_hvac_mode = None

    @property
    def name(self) -> str:
        """Return the name of the entity."""
        return self._info.device_identification

    @property
    def available(self) -> bool:
        """Return True if entity is available."""
        return self._available

    @property
    def unique_id(self) -> str:
        """Return the unique ID for this sensor."""
        return self._info.device_identification

    @property
    def temperature_unit(self) -> str:
        """Return the unit of measurement which this thermostat uses."""
        if self._temperature_unit == "&deg;C":
            return TEMP_CELSIUS
        return TEMP_FAHRENHEIT

    @property
    def supported_features(self) -> int:
        """Flag supported features."""
        return SUPPORT_FLAGS

    @property
    def current_temperature(self):
        """Return the current temperature."""
        return self._current_temperature

    @property
    def hvac_mode(self):
        """Return the current operation mode."""
        return self._current_hvac_mode

    @property
    def hvac_modes(self):
        """Return the list of available operation modes."""
        return HVAC_MODES

    @property
    def target_temperature(self):
        """Return the temperature we try to reach."""
        return self._target_temperature

    @property
    def preset_modes(self):
        """List of available preset modes."""
        return PRESET_MODES

    @property
    def preset_mode(self):
        """Return the preset_mode."""
        return self._preset_mode

    async def async_set_preset_mode(self, preset_mode):
        """Set preset mode."""
        _LOGGER.debug("Setting preset mode to: %s", preset_mode)
        if preset_mode == PRESET_NONE:
            # restore previous hvac mode
            self._current_hvac_mode = self._store_hvac_mode
        else:
            # Store hvac mode.
            self._store_hvac_mode = self._current_hvac_mode
            await self.async_set_data(preset_mode=preset_mode)

    async def async_set_hvac_mode(self, hvac_mode):
        """Set HVAC mode."""
        _LOGGER.debug("Setting HVAC mode to: %s", hvac_mode)
        # preset should be none when hvac mode is set
        self._preset_mode = PRESET_NONE
        await self.async_set_data(hvac_mode=hvac_mode)

    async def async_set_temperature(self, **kwargs):
        """Set new target temperatures."""
        await self.async_set_data(**kwargs)

    async def async_set_data(self, **kwargs: Any) -> None:
        """Set device settings using BSBLan."""
        data = {}

        if ATTR_TEMPERATURE in kwargs:
            data[ATTR_TARGET_TEMPERATURE] = kwargs[ATTR_TEMPERATURE]
            _LOGGER.debug("Set temperature data = %s", data)

        if ATTR_HVAC_MODE in kwargs:
            data[ATTR_HVAC_MODE] = HA_STATE_TO_BSBLAN[kwargs[ATTR_HVAC_MODE]]
            _LOGGER.debug("Set hvac mode data = %s", data)

        if ATTR_PRESET_MODE in kwargs:
            # for now we set the preset as hvac_mode as the api expect this
            data[ATTR_HVAC_MODE] = HA_PRESET_TO_BSBLAN[kwargs[ATTR_PRESET_MODE]]

        try:
            await self.bsblan.thermostat(**data)
        except BSBLanError:
            _LOGGER.error("An error occurred while updating the BSBLan device")
            self._available = False

    async def async_update(self) -> None:
        """Update BSBlan entity."""
        try:
            state: State = await self.bsblan.state()
        except BSBLanError:
            if self._available:
                _LOGGER.error("An error occurred while updating the BSBLan device")
            self._available = False
            return

        self._available = True

        self._current_temperature = float(state.current_temperature)
        self._target_temperature = float(state.target_temperature)

        # check if preset is active else get hvac mode
        _LOGGER.debug("state hvac/preset mode: %s", state.current_hvac_mode)
        if state.current_hvac_mode == "2":
            self._preset_mode = PRESET_ECO
        else:
            self._current_hvac_mode = BSBLAN_TO_HA_STATE[state.current_hvac_mode]
            self._preset_mode = PRESET_NONE

        self._temperature_unit = state.temperature_unit

    @property
    def device_info(self) -> Dict[str, Any]:
        """Return device information about this BSBLan device."""
        return {
            ATTR_IDENTIFIERS: {(DOMAIN, self._info.device_identification)},
            ATTR_NAME: "BSBLan Device",
            ATTR_MANUFACTURER: "BSBLan",
            ATTR_MODEL: self._info.controller_variant,
        }
