"""This module manages the central boiler feature of the Versatile Thermostat integration."""

from datetime import timedelta
from typing import Any

from homeassistant.core import (
    HomeAssistant,
    HomeAssistantError,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (
    async_call_later,
    async_track_state_change_event,
    async_track_time_interval,
)

from .base_manager import BaseFeatureManager
from .commons import check_and_extract_service_configuration, write_event_log
from .const import *  # pylint: disable=wildcard-import, unused-wildcard-import
from vtherm_api.log_collector import get_vtherm_logger


_LOGGER = get_vtherm_logger(__name__)


class FeatureCentralBoilerManager(BaseFeatureManager):
    """The implementation of the Central Boiler Feature Manager for Versatile Thermostat"""

    def __init__(self, hass: HomeAssistant, vtherm_api: Any):
        """Initialize the FeatureCentralBoilerManager."""
        super().__init__(vtherm_api, hass)
        self._vtherm_api = vtherm_api
        self._is_configured: bool = False
        self._is_ready: bool = False
        self._is_on: bool | None = None
        self._service_activate: dict | None = None
        self._service_deactivate: dict | None = None

        self._central_boiler_entity = None
        self._nb_active_device_threshold_number_entity = None
        self._total_power_active_threshold_number_entity = None
        self._nb_active_device_number_entity = None
        self._total_power_active_entity = None
        self._all_boiler_entities: list[Entity] = []

        self._activation_delay_sec: int = 0
        self._call_later_handle = None

        # Keep-alive tracking
        self._keep_alive_boiler_state_enabled: bool = False
        self._keep_alive_boiler_delay_sec: int = 0
        self._time_interval_listener_cancel = None
        self._last_activated_service: dict | None = None

    @overrides
    def post_init(self, entry_infos: dict):
        """Reinit of the manager"""
        self._service_activate = check_and_extract_service_configuration(
            entry_infos.get(CONF_CENTRAL_BOILER_ACTIVATION_SRV)
        )
        self._service_deactivate = check_and_extract_service_configuration(
            entry_infos.get(CONF_CENTRAL_BOILER_DEACTIVATION_SRV)
        )
        self._activation_delay_sec = entry_infos.get(CONF_CENTRAL_BOILER_ACTIVATION_DELAY_SEC, 0)
        self._keep_alive_boiler_delay_sec = entry_infos.get(CONF_KEEP_ALIVE_BOILER_DELAY_SEC, DEFAULT_KEEP_ALIVE_BOILER_DELAY_SEC)
        self._last_activated_service = None
        self._is_configured = bool(self._service_activate or self._service_deactivate)
        self._keep_alive_boiler_state_enabled = self._keep_alive_boiler_delay_sec > 0 and self._is_configured

    @overrides
    async def start_listening(self, force: bool = False):
        """Initialize the listening of state change of VTherms"""

        # Listen to all VTherm state change
        boiler_entity_ids = self._get_all_boiler_entity_ids(force=force)
        if self.is_ready:
            self.stop_listening()
            listener_cancel = async_track_state_change_event(
                self._hass,
                boiler_entity_ids,
                self.calculate_central_boiler_state,
            )
            _LOGGER.debug(
                "%s - entities to get the nb of active VTherm are %s",
                self,
                boiler_entity_ids,
            )
            self.add_listener(listener_cancel)

            # Add periodic keep-alive if delay is configured
            if self._keep_alive_boiler_state_enabled:
                interval_listener_cancel = async_track_time_interval(
                    self._hass,
                    self._keep_alive_boiler,
                    timedelta(seconds=self._keep_alive_boiler_delay_sec),
                )
                self._time_interval_listener_cancel = interval_listener_cancel
                self.add_listener(interval_listener_cancel)
                _LOGGER.debug(
                    "%s - Keep-alive boiler enabled (every %d seconds)",
                    self,
                    self._keep_alive_boiler_delay_sec,
                )

            await self.calculate_central_boiler_state(None)
        else:
            _LOGGER.debug("%s - no VTherm could controls the central boiler", self)

    @property
    def is_configured(self) -> bool:
        """True if the FeatureManager is fully configured"""
        return self._is_configured

    @property
    def is_ready(self) -> bool:
        """True if the FeatureManager is fully configured and has all registered entities"""
        return self._is_ready

    @property
    def is_on(self) -> bool | None:
        """Return True if the central boiler is on. Return None if the state is unknown (at startup for example)"""
        return self._is_on

    @property
    def is_detected(self) -> bool:
        """Return True if the central boiler is detected"""
        return self._is_on if self._is_on is not None else False

    async def calculate_central_boiler_state(self, _):
        """Calculate the central boiler state depending on all VTherm that
        controls this central boiler"""

        _LOGGER.debug("%s - calculating the new central boiler state", self)

        def _send_vtherm_event(data: dict):
            send_vtherm_event(
                hass=self._hass,
                event_type=EventType.CENTRAL_BOILER_EVENT,
                entity=self.central_boiler_entity,
                data=data,
            )

        async def _activate_later(_):
            """Activate the central boiler after a delay"""
            if self._call_later_handle:
                self._call_later_handle()
                self._call_later_handle = None

            # the condition may have changed during the delay, so we check it again before activating the boiler
            active = self.is_nb_active_active_for_boiler_exceeded or self.is_total_power_active_for_boiler_exceeded
            if not active:
                _LOGGER.info("%s - central boiler delayed activation cancelled because condition is not valid anymore", self)
                return

            _LOGGER.info("%s - central boiler will be turned on", self)
            try:
                await self.call_service(self._service_activate)
                _LOGGER.info("%s - central boiler have been turned on after delay", self)
                self._is_on = True
                if self._keep_alive_boiler_state_enabled:
                    self._last_activated_service = self._service_activate
                _send_vtherm_event(
                    data={"central_boiler": True},
                )
            except HomeAssistantError as err:
                _LOGGER.error(
                    "%s - Impossible to activate boiler due to error %s. "
                    "Central boiler will not being controlled by VTherm. "
                    "Please check your service configuration. Cf. README.",
                    self,
                    err,
                )
                return

        if not self.is_ready:
            _LOGGER.warning(
                "%s - the central boiler manager is not ready. Central boiler state cannot be calculated",
                self,
            )
            return False

        active = self.is_nb_active_active_for_boiler_exceeded or self.is_total_power_active_for_boiler_exceeded

        if self._is_on != active:
            try:
                if active:
                    write_event_log(
                        _LOGGER,
                        self,
                        f"Central boiler is being turned on (nb_active= {self.nb_active_device_for_boiler}/{self.nb_active_device_for_boiler_threshold},"
                        "total_power= {self.total_power_active_for_boiler}/{self.total_power_active_for_boiler_threshold})",
                    )
                    if self._call_later_handle:
                        _LOGGER.debug("%s - central boiler activation is already scheduled", self)
                        return

                    if self._activation_delay_sec > 0:
                        self._call_later_handle = async_call_later(self._hass, timedelta(seconds=self._activation_delay_sec), _activate_later)
                        _send_vtherm_event(
                            data={"delayed_activation_sec": self._activation_delay_sec},
                        )
                    else:
                        await _activate_later(None)
                else:
                    write_event_log(
                        _LOGGER,
                        self,
                        f"Central boiler is being turned off (nb_active= {self.nb_active_device_for_boiler}/{self.nb_active_device_for_boiler_threshold},"
                        "total_power= {self.total_power_active_for_boiler}/{self.total_power_active_for_boiler_threshold})",
                    )

                    # Cancel any pending activation
                    if self._call_later_handle:
                        self._call_later_handle()
                        self._call_later_handle = None

                    # call deactivation service
                    await self.call_service(self._service_deactivate)
                    _LOGGER.info("%s - central boiler have been turned off", self)
                    self._is_on = active
                    if self._keep_alive_boiler_state_enabled:
                        self._last_activated_service = self._service_deactivate
                    _send_vtherm_event(
                        data={"central_boiler": active},
                    )
            except HomeAssistantError as err:
                _LOGGER.error(
                    "%s - Impossible to activate/deactivate boiler due to error %s. "
                    "Central boiler will not being controlled by VTherm. "
                    "Please check your service configuration. Cf. README.",
                    self,
                    err,
                )

    async def _keep_alive_boiler(self, _=None):
        """Send keep-alive command to maintain boiler state (called periodically if delay is configured)"""
        if self._keep_alive_boiler_delay_sec <= 0:
            return

        # If we have a pending activation (after delay), don't send keep-alive yet
        if self._call_later_handle is not None:
            return

        # Send keep-alive command for the last activated service
        if self._last_activated_service:
            try:
                await self.call_service(self._last_activated_service)
                entity_id = self._last_activated_service.get("entity_id", "unknown")
                _LOGGER.debug(
                    "%s - Keep-alive boiler command sent (entity %s)",
                    self,
                    entity_id,
                )
            except HomeAssistantError as err:
                _LOGGER.warning(
                    "%s - Failed to send keep-alive command: %s",
                    self,
                    err,
                )

    async def call_service(self, service_config: dict):
        """Make a call to a service if correctly configured"""
        if not service_config:
            return

        _LOGGER.debug(
            "%s - Calling central boiler service %s with data %s",
            self,
            f"{service_config['service_domain']}.{service_config['service_name']}",
            service_config.get("data", {}),
        )
        await self._hass.services.async_call(
            service_config["service_domain"],
            service_config["service_name"],
            service_data=service_config["data"],
            target={
                "entity_id": service_config["entity_id"],
            },
        )

    async def reload_central_boiler_binary_listener(self):
        """Reloads the BinarySensor entity which listen to the number of
        active devices and the thresholds entities"""
        if self._nb_active_device_number_entity:
            await self._nb_active_device_number_entity.listen_vtherms_entities()
        if self._total_power_active_entity:
            await self._total_power_active_entity.listen_vtherms_entities()

    async def reload_central_boiler_entities_list(self):
        """Reload the central boiler list of entities if a central boiler is used"""
        if self._nb_active_device_number_entity is not None:
            await self._nb_active_device_number_entity.listen_vtherms_entities()

    def add_custom_attributes(self, extra_state_attributes: dict[str, Any]) -> None:
        """Add custom attributes to the attributes dict."""
        extra_state_attributes.update(
            {
                "is_central_boiler_configured": self._is_configured,
                "is_central_boiler_ready": self._is_ready,
            }
        )
        if self._is_ready:
            extra_state_attributes.update(
                {
                    "central_boiler_manager": {
                        "is_on": self._is_on,
                        "activation_scheduled": self._call_later_handle is not None,
                        "delayed_activation_sec": self._activation_delay_sec,
                        "nb_active_device_for_boiler": self.nb_active_device_for_boiler,
                        "nb_active_device_for_boiler_threshold": self.nb_active_device_for_boiler_threshold,
                        "total_power_active_for_boiler": self.total_power_active_for_boiler,
                        "total_power_active_for_boiler_threshold": self.total_power_active_for_boiler_threshold,
                        "service_activate": self._service_activate,
                        "service_deactivate": self._service_deactivate,
                        "keep_alive_boiler_delay_sec": self._keep_alive_boiler_delay_sec,
                    }
                }
            )

    def register_central_boiler(self, central_boiler_entity):
        """Register the central boiler entity. This is used by the CentralBoilerBinarySensor
        class to register itself at creation"""
        self._central_boiler_entity = central_boiler_entity
        self.hass.create_task(self.start_listening(force=True))

    def register_central_boiler_activation_number_threshold(self, threshold_number_entity):
        """register the number entities needed for boiler activation"""
        self._nb_active_device_threshold_number_entity = threshold_number_entity
        self.hass.create_task(self.start_listening(force=True))

    def register_central_boiler_power_activation_threshold(self, power_threshold_number_entity):
        """register the power entities needed for boiler activation"""
        self._total_power_active_threshold_number_entity = power_threshold_number_entity
        self.hass.create_task(self.start_listening(force=True))

    def register_nb_device_active_boiler(self, nb_active_number_entity):
        """register the two number entities needed for boiler activation"""
        self._nb_active_device_number_entity = nb_active_number_entity
        self.hass.create_task(self.start_listening(force=True))

    def register_total_power_active_boiler(self, total_power_active_entity):
        """register the two number entities needed for boiler activation"""
        self._total_power_active_entity = total_power_active_entity
        self.hass.create_task(self.start_listening(force=True))

    @property
    def central_boiler_entity(self):
        """Get the central boiler binary_sensor entity"""
        return self._central_boiler_entity

    @property
    def nb_active_device_for_boiler(self):
        """Returns the number of active VTherm which have an
        influence on boiler"""
        if self._nb_active_device_number_entity is None:
            return None
        else:
            return self._nb_active_device_number_entity.native_value

    @property
    def nb_active_device_for_boiler_threshold(self):
        """Returns the number of active VTherm entity which have an
        influence on boiler"""
        if self._nb_active_device_threshold_number_entity is None:
            return None
        return int(self._nb_active_device_threshold_number_entity.native_value)

    @property
    def total_power_active_for_boiler(self):
        """Returns the total power of active VTherm which have an
        influence on boiler"""
        if self._total_power_active_entity is None:
            return None
        else:
            return self._total_power_active_entity.native_value

    @property
    def total_power_active_for_boiler_threshold(self):
        """Returns the number of active VTherm entity which have an
        influence on boiler"""
        if self._total_power_active_threshold_number_entity is None:
            return None
        return int(self._total_power_active_threshold_number_entity.native_value)

    @property
    def is_nb_active_active_for_boiler_exceeded(self) -> bool:
        """Returns True if the number of active VTherm for boiler
        have exceeded the threshold"""
        if self.nb_active_device_for_boiler is None or self.nb_active_device_for_boiler_threshold is None or self.nb_active_device_for_boiler_threshold == 0:
            return False

        return self.nb_active_device_for_boiler >= self.nb_active_device_for_boiler_threshold

    @property
    def is_total_power_active_for_boiler_exceeded(self) -> bool:
        """Returns True if the total power of active VTherm for boiler
        have exceeded the threshold"""
        if self.total_power_active_for_boiler is None or self.total_power_active_for_boiler_threshold is None or self.total_power_active_for_boiler_threshold == 0:
            return False

        total_power = self.total_power_active_for_boiler
        power_threshold = self.total_power_active_for_boiler_threshold

        return total_power >= power_threshold

    def _get_all_boiler_entity_ids(self, force=False) -> list[str]:
        """Returns the list of all VTherm entity ids which have an influence
        on the central boiler"""
        if self._is_configured and not force:
            return self._all_boiler_entities

        self._all_boiler_entities = []
        if self._nb_active_device_threshold_number_entity:
            self._all_boiler_entities.append(self._nb_active_device_threshold_number_entity.entity_id)
        if self._total_power_active_threshold_number_entity:
            self._all_boiler_entities.append(self._total_power_active_threshold_number_entity.entity_id)
        if self._nb_active_device_number_entity:
            self._all_boiler_entities.append(self._nb_active_device_number_entity.entity_id)
        if self._total_power_active_entity:
            self._all_boiler_entities.append(self._total_power_active_entity.entity_id)

        old_ready = self._is_ready
        self._is_ready = self.is_configured and len(self._all_boiler_entities) == 4 and self._central_boiler_entity is not None
        if not self._is_ready and self.is_configured:
            _LOGGER.warning(
                "%s - central boiler manager is not fully configured. Found only %d/4 entities and central boiler entity=%s. Central boiler control will not work properly. This could a temporary message at startup.",
                self,
                len(self._all_boiler_entities),
                self._central_boiler_entity,
            )
            return []
        if not self._is_ready and not self.is_configured:
            # Silence warning if the feature is not configured (user doesn't want it)
            _LOGGER.debug(
                "%s - central boiler manager is not configured. Ignoring initialization.",
                self,
            )
            return []
        if self._is_ready != old_ready and self._central_boiler_entity:
            # Notify the central boiler entity that the manager is now ready
            self.hass.create_task(self.refresh_central_boiler_custom_attributes())

        return self._all_boiler_entities

    async def refresh_central_boiler_custom_attributes(self):
        """Refresh the custom attributes of the central boiler entity"""
        if self._central_boiler_entity:
            self._central_boiler_entity.refresh_custom_attributes()

    @property
    def nb_device_active_for_boiler_entity(self):
        """Returns the entity if the sensor which gives the number of active VTherm which have an
        influence on boiler"""
        return self._nb_active_device_number_entity

    # For testing purpose
    def _set_nb_active_device_threshold(self, value: int) -> None:
        """Set the number of active device threshold"""
        if self._nb_active_device_threshold_number_entity:
            self._nb_active_device_threshold_number_entity.set_native_value(value)

    def _set_total_power_active_threshold(self, value: int) -> None:
        """Set the total power of active device threshold"""
        if self._total_power_active_threshold_number_entity:
            self._total_power_active_threshold_number_entity.set_native_value(value)

    def __str__(self):
        return "FeatureCentralBoilerManager"
