from __future__ import annotations
import threading
from abc import ABC, abstractmethod
import time
from dataclasses import dataclass


class EventError(Exception):
    pass


class TimeLimitedEvent(ABC):
    """
    Интерфейс для временных событий.
    Например, если мы хотим:
        * временно изменить уровень логгирования
        * временно включить логгирование в локальный syslog
        * временно включить профилирование.
    """
    @abstractmethod
    def start(self, *args, **kwargs):
        pass

    @abstractmethod
    def stop(self):
        pass

    @abstractmethod
    def is_in_progress(self):
        """
        Нужно знать, чтобы иметь возможность исключить конкурентные запуски события.
        """
        pass


class SwitchedOffEvent(TimeLimitedEvent):
    """
    Подмена для TimeLimitedEvent в случае, когда событие неприемлемо.
    Например, если включить логгирование в syslog через конфиг, то нет смысла его включать динамически.
    """
    def start(self, *args, **kwargs):
        raise EventError('Call on switched off event.')

    def stop(self):
        raise EventError('Call on switched off event.')

    def is_in_progress(self):
        raise EventError('Call on switched off event.')


class EventWithAutostop:
    """
    Принимает реализацию интерфейса TimeLimitedEvent.
    Реализует автоматическое выключение временного события через заданное время.
    """
    @dataclass
    class State:
        stop_in: float
        is_in_progress: bool
        error: Exception

    class Starter:
        def __init__(self, func):
            self.func = func

        def start(self, *args, **kwargs):
            return self.func(*args, *kwargs)

    def __init__(self, event: TimeLimitedEvent, default_duration: float = 30 * 60, event_check_interval=1):
        self._event = event
        self._default_duration = default_duration
        self._stop_at = time.time()
        self._event_check_interval = event_check_interval

    def _state(self, error: Exception = None) -> EventWithAutostop.State:
        return self.State(
            max(-1., self._stop_at - time.time()),
            self._event.is_in_progress(),
            error,
        )

    def with_stop_after(self, duration) -> Starter:
        def starter(*args, **kwargs):
            if self._event.is_in_progress():
                return self._state(EventError("Event has already been started."))

            self._stop_at = time.time() + duration
            self._event.start(*args, **kwargs)

            def autostop():
                while time.time() <= self._stop_at:
                    time.sleep(self._event_check_interval)
                    if not self._event.is_in_progress():
                        return
                self.stop()

            threading.Thread(target=autostop).start()
            return self._state()

        return self.Starter(starter)

    def state(self):
        return self._state()

    def stop(self):
        if not self._event.is_in_progress():
            return self._state(EventError("Event is not running."))
        self._event.stop()
        self._stop_at = time.time()
        return self._state()
