from typing import List, Dict, Optional, Any
from pathlib import Path

from atmospyre.loggers import SensorLogger


class SchedulerDispatchTag:
    """Base class for scheduler backend dispatch tags.

    All backend-specific tags should inherit from this class.
    """
    pass


# Global namespace for all scheduler backends
scheduler_dispatch_namespace = {}


class LoggerScheduler:
    """Scheduler interface with pluggable backends via namespace dispatch.

    This scheduler provides a unified interface for managing multiple
    SensorLogger instances while allowing different scheduling libraries
    to be used as backends. The backend is selected via a tag parameter,
    following the exact dispatch pattern used in the Sensor class.

    All execution is blocking and serial, ensuring safe Modbus communication
    when multiple sensors share a bus.

    Parameters
    ----------
    scheduler_dispatch_tag : SchedulerDispatchTag subclass
        Tag class for backend selection. Use:
        - ScheduleTag: For 'schedule' library (simple, lightweight)
        - APSchedulerTag: For APScheduler (feature-rich)
    log_path : str or Path, optional
        Path where scheduler logs should be written (default: ".").
    verbose : bool, optional
        Whether to print scheduler events to stdout (default: True).
    check_interval : float, optional
        Time in seconds between schedule checks (default: 1.0).
        Only applies to schedule backend.

    Examples
    --------
    Using the schedule library backend:

    >>> from atmospyre.scheduler.logger_scheduler import LoggerScheduler
    >>> from atmospyre.scheduler.schedule_backend import ScheduleTag
    >>>
    >>> scheduler = LoggerScheduler(
    ...     scheduler_dispatch_tag=ScheduleTag,
    ...     log_path='./logs/scheduler.log'
    ... )
    >>> scheduler.add_logger(logger1)
    >>> scheduler.add_logger(logger2)
    >>> scheduler.run()  # Blocking

    Using the APScheduler backend:

    >>> from atmospyre.scheduler.apscheduler_backend import APSchedulerTag
    >>>
    >>> scheduler = LoggerScheduler(scheduler_dispatch_tag=APSchedulerTag)
    >>> scheduler.add_logger(logger1)
    >>> scheduler.run()  # Blocking

    Context manager usage:

    >>> with LoggerScheduler(scheduler_dispatch_tag=ScheduleTag) as scheduler:
    ...     scheduler.add_logger(logger1)
    ...     scheduler.add_logger(logger2)
    ...     scheduler.run()

    Notes
    -----
    Backend Selection:

    The choice of backend depends on your needs:

    - **ScheduleTag** (schedule library):
      - Simple, minimal dependencies
      - Good for straightforward periodic tasks
      - Lightweight and easy to understand
      - Install: pip install schedule

    - **APSchedulerTag** (APScheduler):
      - More features and options
      - Better job management capabilities
      - Persistent job storage options
      - Install: pip install apscheduler

    Both backends ensure serial execution for Modbus safety.

    Serial Execution:

    Regardless of backend, all loggers execute serially in the main thread.
    This prevents Modbus communication conflicts when sensors share a bus.
    Even if multiple loggers are due simultaneously, they run one at a time.

    Dispatch Pattern:

    This class uses the same multipledispatch pattern as the Sensor class.
    All backends register their implementations in the shared
    scheduler_dispatch_namespace. The tag type determines which implementation
    gets called:

    >>> _add_logger = scheduler_dispatch_namespace['_add_logger']
    >>> _add_logger(tag_instance, scheduler, logger, interval)

    This allows different backends to provide their own implementations of
    each operation without coupling the main class to specific backends.
    """

    def __init__(
        self,
        scheduler_dispatch_tag: SchedulerDispatchTag,
        log_path: Path = Path("."),
        verbose: bool = True,
        check_interval: float = 1.0
    ):
        """Initialize the scheduler with a specific backend.

        See class docstring for parameter details.
        """
        # Store the tag class and create an instance
        self.scheduler_dispatch_tag = scheduler_dispatch_tag

        # Store configuration
        self.log_path = Path(log_path)
        self.check_interval = check_interval
        self.verbose = verbose
        self.loggers: List[SensorLogger] = []

        # Create log directory if needed
        if self.log_path:
            self.log_path.parent.mkdir(parents=True, exist_ok=True)

        # Create the scheduler using the tag for dispatch
        _create_scheduler = scheduler_dispatch_namespace['_create_scheduler']
        self.schedule = _create_scheduler(self.scheduler_dispatch_tag)

    def add_logger(
        self,
        logger: SensorLogger,
    ):
        """Add a logger to the scheduler.

        Parameters
        ----------
        logger : SensorLogger
            The sensor logger to schedule.

        Raises
        ------
        ValueError
            If the logger is already registered.
        """
        _add_logger = scheduler_dispatch_namespace['_add_logger']
        _add_logger(self.scheduler_dispatch_tag, self, logger, logger.interval_seconds)

    def remove_logger(self, logger: SensorLogger) -> bool:
        """Remove a logger from the scheduler.

        Parameters
        ----------
        logger : SensorLogger
            The logger instance to remove.

        Returns
        -------
        bool
            True if logger was found and removed, False otherwise.
        """
        _remove_logger = scheduler_dispatch_namespace['_remove_logger']
        return _remove_logger(self.scheduler_dispatch_tag, self, logger)

    def clear(self):
        """Remove all loggers and clear the schedule."""
        _clear = scheduler_dispatch_namespace['_clear']
        _clear(self.scheduler_dispatch_tag, self)

    def get_status(self) -> Dict:
        """Get current scheduler status.

        Returns
        -------
        dict
            Dictionary containing backend name, logger counts, and details.
        """
        _get_status = scheduler_dispatch_namespace['_get_status']
        return _get_status(self.scheduler_dispatch_tag, self)

    def run_pending(self):
        """Run all pending jobs manually.

        Note: Not all backends support this operation.
        """
        _run_pending = scheduler_dispatch_namespace['_run_pending']
        _run_pending(self.scheduler_dispatch_tag, self)

    def run(self):
        """Start the scheduler in a blocking loop.

        This method blocks indefinitely, running scheduled jobs.
        Press Ctrl+C to stop.
        """
        _run = scheduler_dispatch_namespace['_run']
        _run(self.scheduler_dispatch_tag, self)

    def __repr__(self) -> str:
        """Return string representation of the scheduler."""
        status = self.get_status()
        return (
            f"LoggerScheduler("
            f"backend={status.get('backend', 'unknown')}, "
            f"loggers={status['total_loggers']})"
        )

    def __enter__(self):
        """Context manager entry."""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit - clears all loggers."""
        self.clear()