import threading
from abc import ABC, abstractmethod
from enum import Enum

from loguru import logger


class ServiceState(Enum):
    IDLE = "idle"
    STARTING = "starting"
    RUNNING = "running"
    STOPPING = "stopping"
    STOPPED = "stopped"


class Service(ABC):
    """Base class for long-lived background services.

    Subclass and override ``on_start()`` and ``on_stop()``.  The framework
    manages state transitions and threading — ``on_start`` / ``on_stop`` run
    on their own background thread (via ``CentralDispatch.future``), not on
    the main-thread queue.
    """

    def __init__(self):
        self._state = ServiceState.IDLE
        self._application = None
        self._running_event = threading.Event()
        self._stopped_event = threading.Event()

    # -- public read-only properties --

    @property
    def state(self) -> ServiceState:
        return self._state

    @property
    def is_running(self) -> bool:
        return self._state == ServiceState.RUNNING

    @property
    def application(self):
        return self._application

    @property
    def main_thread(self):
        return self._application.main_thread

    # -- lifecycle (override these) --

    @abstractmethod
    def on_start(self):
        """Called on a background thread to start the service."""
        ...

    @abstractmethod
    def on_stop(self):
        """Called on a background thread to stop the service."""
        ...

    # -- framework helpers --

    def wait_until_running(self, timeout=None) -> bool:
        """Block until the service reaches RUNNING state.  Returns True if
        running, False on timeout."""
        return self._running_event.wait(timeout=timeout)

    def wait_until_stopped(self, timeout=None) -> bool:
        """Block until the service reaches STOPPED state.  Returns True if
        stopped, False on timeout."""
        return self._stopped_event.wait(timeout=timeout)

    def dispatch_event(self, event):
        """Shortcut to ``application.dispatch_event``."""
        self._application.dispatch_event(event)
