# pylint: disable=line-too-long, abstract-method
""" A climate over switch classe """
import logging
from vtherm_api.log_collector import get_vtherm_logger
from datetime import timedelta, datetime

from homeassistant.helpers.event import (
    async_track_state_change_event,
    async_track_time_interval,
    async_call_later,
    EventStateChangedData,
)
from homeassistant.core import Event, HomeAssistant, callback

from .base_thermostat import BaseThermostat, ConfigData
from .thermostat_prop import ThermostatProp

from .const import *  # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import write_event_log

from .underlyings import UnderlyingValve
from .cycle_scheduler import CycleScheduler
from .vtherm_hvac_mode import VThermHvacMode_OFF

_LOGGER = get_vtherm_logger(__name__)

class ThermostatOverValve(ThermostatProp[UnderlyingValve]):  # pylint: disable=abstract-method
    """Representation of a class for a Versatile Thermostat over a Valve"""

    _entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union(  # pylint: disable=protected-access
        frozenset(
            {
                "is_over_valve",
                "vtherm_over_valve",
            }
        )
    )

    def __init__(
        self, hass: HomeAssistant, unique_id: str, name: str, config_entry: ConfigData
    ):
        """Initialize the thermostat over switch."""
        self._valve_open_percent: int = 0
        self._last_calculation_timestamp: datetime | None = None
        self._auto_regulation_dpercent: float | None = None
        self._auto_regulation_period_min: int | None = None

        # Call to super must be done after initialization because it calls post_init at the end
        super().__init__(hass, unique_id, name, config_entry)

    @property
    def is_over_valve(self) -> bool:
        """True if the Thermostat is over_valve"""
        return True

    @property
    def valve_open_percent(self) -> int:
        """Gives the percentage of valve needed"""
        if self.vtherm_hvac_mode is VThermHvacMode_OFF:
            return 0
        else:
            return self._valve_open_percent

    @overrides
    def post_init(self, config_entry: ConfigData):
        """Initialize the Thermostat"""

        super().post_init(config_entry)

        self._auto_regulation_dpercent = (
            config_entry.get(CONF_AUTO_REGULATION_DTEMP)
            if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
            else 0.0
        )
        self._auto_regulation_period_min = (
            config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
            if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
            else 0
        )

        lst_valves = config_entry.get(CONF_UNDERLYING_LIST)

        for _, valve in enumerate(lst_valves):
            self._underlyings.append(
                UnderlyingValve(hass=self._hass, thermostat=self, valve_entity_id=valve)
            )

        self._bind_scheduler(CycleScheduler(
            hass=self._hass,
            thermostat=self,
            underlyings=self._underlyings,
            cycle_duration_sec=self._cycle_min * 60,
            min_activation_delay=self.minimal_activation_delay,
            min_deactivation_delay=self.minimal_deactivation_delay,
        ))

        self._should_relaunch_control_heating = False

    @overrides
    async def async_added_to_hass(self):
        """Run when entity about to be added."""

        await super().async_added_to_hass()

        # Add listener to all underlying entities
        for valve in self._underlyings:
            self.async_on_remove(
                async_track_state_change_event(
                    self.hass, [valve.entity_id], self._async_valve_changed
                )
            )

        # Start the control_heating
        # starts a cycle
        self.async_on_remove(
            async_track_time_interval(
                self.hass,
                self.async_control_heating,
                interval=timedelta(minutes=self._cycle_min),
            )
        )

    @callback
    async def _async_valve_changed(self, event: Event[EventStateChangedData]):
        """Handle unerdlying valve state changes.
        This method just log the change. It changes nothing to avoid loops.
        """
        new_state = event.data.get("new_state")
        self.calculate_hvac_action()
        self.update_custom_attributes()
        self.async_write_ha_state()
        write_event_log(_LOGGER, self, f"Underlying valve state changed to {new_state}")

    @overrides
    def update_custom_attributes(self):
        """Custom attributes"""
        super().update_custom_attributes()

        self._attr_extra_state_attributes.update(
            {
                "is_over_valve": self.is_over_valve,
                "on_percent": self.safe_on_percent,
                "power_percent": self.power_percent,
                "valve_open_percent": self.valve_open_percent,
                "vtherm_over_valve": {
                    "valve_open_percent": self.valve_open_percent,
                    "underlying_entities": [underlying.entity_id for underlying in self._underlyings],
                    "on_percent": self.safe_on_percent,
                    "function": self._proportional_function,
                    "tpi_coef_int": self._tpi_coef_int,
                    "tpi_coef_ext": self._tpi_coef_ext,
                    "tpi_threshold_low": self._tpi_threshold_low,
                    "tpi_threshold_high": self._tpi_threshold_high,
                    "minimal_activation_delay": self._minimal_activation_delay,
                    "minimal_deactivation_delay": self._minimal_deactivation_delay,
                    "auto_regulation_dpercent": self._auto_regulation_dpercent,
                    "auto_regulation_period_min": self._auto_regulation_period_min,
                    "last_calculation_timestamp": (self._last_calculation_timestamp.astimezone(self._current_tz).isoformat() if self._last_calculation_timestamp else None),
                    "calculated_on_percent": (
                        self._prop_algorithm.calculated_on_percent
                        if self._prop_algorithm
                        else None
                    ),
                },
            }
        )

        # _LOGGER.debug("%s - Calling update_custom_attributes: %s", self, self._attr_extra_state_attributes)

    @overrides
    def recalculate(self, force=False):
        """A utility function to force the calculation of a the algo and
        update the custom attributes and write the state
        """
        _LOGGER.debug("%s - recalculate the open percent", self)

        self.stop_recalculate_later()

        if self._auto_regulation_period_min is None or self._auto_regulation_dpercent is None:
            _LOGGER.warning(
                "%s - auto_regulation_period_min or auto_regulation_dpercent is not set. Stopping TPI calculation.",
                self,
            )
            return

        # For testing purpose. Should call _set_now() before
        now = self.now

        if self._last_calculation_timestamp is not None:
            period = (now - self._last_calculation_timestamp).total_seconds() / 60
            if not force and period < self._auto_regulation_period_min:
                _LOGGER.info(
                    "%s - do not calculate TPI because regulation_period (%d) is not exceeded",
                    self,
                    period,
                )

                self.do_recalculate_later()
                return

        if self._prop_algorithm:
            self._prop_algorithm.calculate(
                self.target_temperature,
                self._cur_temp,
                self._cur_ext_temp,
                self.last_temperature_slope,
                self.vtherm_hvac_mode or VThermHvacMode_OFF,
            )

        current_on_percent = self.safe_on_percent
        if current_on_percent is None:
            # Temperature not yet available; preserve the current valve position.
            return

        new_valve_percent = round(
            max(0, min(current_on_percent, 1)) * 100
        )

        # Issue 533 - don't filter with dtemp if valve should be close. Else it will never close
        if new_valve_percent < self._auto_regulation_dpercent:
            new_valve_percent = 0

        dpercent = new_valve_percent - self.valve_open_percent
        if (
            new_valve_percent > 0
            and -1 * self._auto_regulation_dpercent
            <= dpercent
            < self._auto_regulation_dpercent
        ):
            _LOGGER.debug(
                "%s - do not calculate TPI because regulation_dpercent (%.1f) is not exceeded",
                self,
                dpercent,
            )

            return

        if self._valve_open_percent == new_valve_percent:
            _LOGGER.debug("%s - no change in valve_open_percent.", self)
            return

        self._valve_open_percent = new_valve_percent

        # Valve open percent is sent to underlyings by CycleScheduler
        # in start_cycle (called from control_heating)

        self._last_calculation_timestamp = now

        # self.calculate_hvac_action()
        self.update_custom_attributes()
        self.async_write_ha_state()

    def do_recalculate_later(self):
        """A utility function to set the valve open percent later on all underlyings"""
        _LOGGER.debug("%s - do_recalculate_later call", self)

        async def callback_recalculate(_):
            """Callback to set the valve percent"""
            self.recalculate()
            current_on_percent = self.safe_on_percent
            if self._cycle_scheduler and current_on_percent is not None:
                await self._cycle_scheduler.apply_valve_update(
                    self.vtherm_hvac_mode or VThermHvacMode_OFF,
                    current_on_percent,
                )
            self.update_custom_attributes()
            self.async_write_ha_state()

        self.stop_recalculate_later()

        self._cancel_recalculate_later = async_call_later(self._hass, delay=20, action=callback_recalculate)

    @overrides
    def incremente_energy(self):
        """increment the energy counter if device is active"""
        if self.vtherm_hvac_mode == VThermHvacMode_OFF:
            return

        added_energy = 0
        if not self.is_over_climate and self.power_manager.mean_cycle_power is not None:
            added_energy = (
                self.power_manager.mean_cycle_power * float(self._cycle_min) / 60.0
            )

        if self._total_energy is None:
            self._total_energy = added_energy
            _LOGGER.debug(
                "%s - incremente_energy set energy is %s",
                self,
                self._total_energy,
            )
        else:
            self._total_energy += added_energy
            _LOGGER.debug(
                "%s - get_my_previous_state increment energy is %s",
                self,
                self._total_energy,
            )

        self.update_custom_attributes()
        self.async_write_ha_state()

        _LOGGER.debug(
            "%s - added energy is %.3f . Total energy is now: %.3f",
            self,
            added_energy,
            self._total_energy,
        )

    @property
    def vtherm_type(self) -> str | None:
        """Return the type of thermostat"""
        return "over_valve"
