import dataclasses
import datetime
import warnings

from .cycler import AsyncCycler

__all__ = ['TimeWindow', 'HotCycler', 'HotAsyncCycler']



@dataclasses.dataclass(slots=True)
class TimeWindow:
    start: datetime.time
    end: datetime.time
    
    
    def contains(self, t: datetime.time) -> bool:
        if self.start <= self.end:  # normal window
            return self.start <= t <= self.end
        return t >= self.start or t <= self.end  # crosses midnight
    
    
    @staticmethod
    def start_duration(start: datetime.time, duration: datetime.timedelta) -> 'TimeWindow':
        dt = datetime.datetime.combine(datetime.date.today(), start)
        return TimeWindow(start, (dt + duration).time())
    
    
    @staticmethod
    def around_time(target: datetime.time, pre: datetime.timedelta, post: datetime.timedelta = None) -> 'TimeWindow':
        post = post or pre
        dt = datetime.datetime.combine(datetime.date.today(), target)
        return TimeWindow((dt - pre).time(), (dt + post).time())


class HotCycler(AsyncCycler):
    
    def __init__(self, name, func=None,
                 timetable: list[TimeWindow] = None,
                 HOT_SLEEP=0.2,
                 DEFAULT_SLEEP=1,
                 WAS_WORK_SLEEP=1,
                 ERROR_SLEEP=1 * 60):
        super().__init__(name, func,
                         DEFAULT_SLEEP=DEFAULT_SLEEP,
                         WAS_WORK_SLEEP=WAS_WORK_SLEEP,
                         ERROR_SLEEP=ERROR_SLEEP)
        self.timetable: list[TimeWindow] = sorted(timetable or [], key=lambda w: w.start)
        self.HOT_SLEEP = HOT_SLEEP
        self._window: TimeWindow | None = None  # cached ongoing window
    
    
    @property
    def current_window(self) -> TimeWindow | None:
        """Returns the ongoing hot window (cached) or the nearest upcoming one."""
        now_t = datetime.datetime.now().time()
        if self._window is not None and self._window.contains(now_t):
            return self._window
        self._window = None
        for w in self.timetable:
            if w.contains(now_t):
                self._window = w  # cache ongoing window
                return w
        for w in self.timetable:  # upcoming — not cached
            if w.start > now_t:
                return w
        return self.timetable[0] if self.timetable else None  # wrap around midnight
    
    
    def next_hot_start(self) -> datetime.datetime | None:
        w = self.current_window
        if w is None:
            return None
        now = datetime.datetime.now()
        if w.contains(now.time()):
            return None  # already hot
        return datetime.datetime.combine(now.date(), w.start)
    
    
    def get_sleep_time(self, was_work, was_error):
        if was_error:
            return self.ERROR_SLEEP
        w = self.current_window
        now = datetime.datetime.now()
        if w is None:
            return self.WAS_WORK_SLEEP if was_work else self.DEFAULT_SLEEP
        if w.contains(now.time()):
            return self.HOT_SLEEP
        wait = (datetime.datetime.combine(now.date(), w.start) - now).total_seconds()
        if wait < 0:
            wait += 86400  # window starts tomorrow
        return min(self.DEFAULT_SLEEP, max(0, wait))


class HotAsyncCycler(HotCycler):
    def __init__(self, *args, **kwargs):
        warnings.warn("HotAsyncCycler is deprecated, use HotCycler instead", DeprecationWarning, stacklevel=2)
        super().__init__(*args, **kwargs)
