# pylint: disable=line-too-long
""" This file implements the Open Window by temperature algorithm
    This algo works the following way:
    - each time a new temperature is measured
    -    calculate the slope of the temperature curve. For this we calculate the slope(t) = 1/2 slope(t-1) + 1/2 * dTemp / dt
    -    if the slope is lower than a threshold the window opens alert is notified
    -    if the slope regain positive the end of the window open alert is notified
"""

import logging
from vtherm_api.log_collector import get_vtherm_logger
from datetime import datetime

_LOGGER = get_vtherm_logger(__name__)

# To filter bad values
MIN_DELTA_T_SEC = 0  # two temp mesure should be > 0 sec
MAX_SLOPE_VALUE = (
    120  # slope cannot be > 2°/min or < -2°/min -> else this is an aberrant point
)

MAX_DURATION_MIN = 30  # a fake data point is added in the cycle if last measurement was older than 30 min

MIN_NB_POINT = 4  # do not calculate slope until we have enough point


class WindowOpenDetectionAlgorithm:
    """The class that implements the algorithm listed above"""

    def __init__(self, alert_threshold, end_alert_threshold, vtherm=None) -> None:
        """Initalize a new algorithm with the both threshold"""
        self._alert_threshold: float = alert_threshold
        self._end_alert_threshold: float = end_alert_threshold
        self._last_slope: float | None = None
        self._last_datetime: datetime = None
        self._last_temperature: float | None = None
        self._nb_point: int = 0
        self._vtherm = vtherm

    def check_age_last_measurement(self, temperature, datetime_now) -> float:
        """ " Check if last measurement is old and add
        a fake measurement point if this is the case
        """
        if self._last_datetime is None:
            return self.add_temp_measurement(temperature, datetime_now)

        delta_t_sec = float((datetime_now - self._last_datetime).total_seconds()) / 60.0
        if delta_t_sec >= MAX_DURATION_MIN:
            return self.add_temp_measurement(temperature, datetime_now, False)
        else:
            # do nothing
            return self._last_slope

    def add_temp_measurement(
        self, temperature: float, datetime_measure: datetime, store_date: bool = True
    ) -> float:
        """Add a new temperature measurement
        returns the last slope
        """
        if self._last_datetime is None or self._last_temperature is None:
            _LOGGER.debug("%s - First initialisation", self)
            self._last_datetime = datetime_measure
            self._last_temperature = temperature
            self._nb_point = self._nb_point + 1
            return None

        _LOGGER.debug(
            "%s - We are already initialized slope=%s last_temp=%0.2f",
            self,
            self._last_slope,
            self._last_temperature,
        )
        lspe = self._last_slope

        delta_t_sec = float((datetime_measure - self._last_datetime).total_seconds())
        delta_t = delta_t_sec / 60.0
        if delta_t_sec <= MIN_DELTA_T_SEC:
            _LOGGER.debug(
                "%s - Delta t is %d < %d which should be not possible. We don't consider this value",
                self,
                delta_t_sec,
                MIN_DELTA_T_SEC,
            )
            return lspe

        delta_t_hour = delta_t / 60.0

        delta_temp = float(temperature - self._last_temperature)
        new_slope = delta_temp / delta_t_hour
        if new_slope > MAX_SLOPE_VALUE or new_slope < -MAX_SLOPE_VALUE:
            _LOGGER.debug(
                "%s - New_slope is abs(%.2f) > %.2f which should be not possible. We don't consider this value",
                self,
                new_slope,
                MAX_SLOPE_VALUE,
            )
            return lspe

        if self._last_slope is None:
            self._last_slope = round(new_slope, 2)
        else:
            self._last_slope = round((0.2 * self._last_slope) + (0.8 * new_slope), 2)

        # if we are in cycle check and so adding a fake datapoint, we don't store the event datetime
        # so that, when we will receive a real temperature point we will not calculate a wrong slope
        if store_date:
            self._last_datetime = datetime_measure

        self._last_temperature = temperature

        self._nb_point = self._nb_point + 1
        _LOGGER.debug(
            "%s - delta_t=%.3f delta_temp=%.3f new_slope=%.3f last_slope=%s slope=%.3f nb_point=%s",
            self,
            delta_t,
            delta_temp,
            new_slope,
            lspe,
            self._last_slope,
            self._nb_point,
        )

        return self._last_slope

    def is_window_open_detected(self) -> bool:
        """True if the last calculated slope is under (because negative value) the _alert_threshold"""
        if self._alert_threshold is None:
            return False

        if self._nb_point < MIN_NB_POINT or self._last_slope is None:
            return False

        return self._last_slope < -self._alert_threshold

    def is_window_close_detected(self) -> bool:
        """True if the last calculated slope is above (cause negative) the _end_alert_threshold"""
        if self._end_alert_threshold is None:
            return False

        if self._nb_point < MIN_NB_POINT or self._last_slope is None:
            return False

        return self._last_slope >= self._end_alert_threshold

    @property
    def last_slope(self) -> float:
        """Return the last calculated slope"""
        return self._last_slope

    def __str__(self) -> str:
        if self._vtherm and hasattr(self._vtherm, "name"):
            return f"{self._vtherm.name}-WindowOpenDetectionAlgorithm"
        else:
            return f"UnknownVTherm-WindowOpenDetectionAlgorithm"
