"""
Observer pattern for execution monitoring.
"""
import logging
from typing import Callable, Optional

from .models import ExecutionRun, TaskRun, StepRun

logger = logging.getLogger('zsynctech_studio_sdk.observers')


class Observer:
    """Base class for observers. All methods are optional."""

    def on_execution_start(self, execution_run: ExecutionRun) -> None:
        """Called when an execution starts."""
        pass

    def on_execution_complete(self, execution_run: ExecutionRun) -> None:
        """Called when an execution completes."""
        pass

    def on_execution_fail(self, execution_run: ExecutionRun) -> None:
        """Called when an execution fails."""
        pass

    def on_execution_update(self, execution_run: ExecutionRun) -> None:
        """Called when an execution is updated."""
        pass

    def on_task_start(self, task_run: TaskRun) -> None:
        """Called when a task starts."""
        pass

    def on_task_complete(self, task_run: TaskRun) -> None:
        """Called when a task completes."""
        pass

    def on_task_fail(self, task_run: TaskRun) -> None:
        """Called when a task fails."""
        pass

    def on_step_start(self, step_run: StepRun) -> None:
        """Called when a step starts."""
        pass

    def on_step_complete(self, step_run: StepRun) -> None:
        """Called when a step completes."""
        pass

    def on_step_fail(self, step_run: StepRun) -> None:
        """Called when a step fails."""
        pass


class CallbackObserver(Observer):
    """Observer that calls registered callback functions."""

    def __init__(
        self,
        on_execution_start: Optional[Callable[[ExecutionRun], None]] = None,
        on_execution_complete: Optional[Callable[[ExecutionRun], None]] = None,
        on_execution_fail: Optional[Callable[[ExecutionRun], None]] = None,
        on_execution_update: Optional[Callable[[ExecutionRun], None]] = None,
        on_task_start: Optional[Callable[[TaskRun], None]] = None,
        on_task_complete: Optional[Callable[[TaskRun], None]] = None,
        on_task_fail: Optional[Callable[[TaskRun], None]] = None,
        on_step_start: Optional[Callable[[StepRun], None]] = None,
        on_step_complete: Optional[Callable[[StepRun], None]] = None,
        on_step_fail: Optional[Callable[[StepRun], None]] = None,
    ) -> None:
        """Initialize callback observer with optional callbacks.

        Args:
            on_execution_start: Callback for execution start.
            on_execution_complete: Callback for execution complete.
            on_execution_fail: Callback for execution fail.
            on_execution_update: Callback for execution update.
            on_task_start: Callback for task start.
            on_task_complete: Callback for task complete.
            on_task_fail: Callback for task fail.
            on_step_start: Callback for step start.
            on_step_complete: Callback for step complete.
            on_step_fail: Callback for step fail.
        """
        self._on_execution_start = on_execution_start
        self._on_execution_complete = on_execution_complete
        self._on_execution_fail = on_execution_fail
        self._on_execution_update = on_execution_update
        self._on_task_start = on_task_start
        self._on_task_complete = on_task_complete
        self._on_task_fail = on_task_fail
        self._on_step_start = on_step_start
        self._on_step_complete = on_step_complete
        self._on_step_fail = on_step_fail

    def on_execution_start(self, execution_run: ExecutionRun) -> None:
        if self._on_execution_start:
            self._on_execution_start(execution_run)

    def on_execution_complete(self, execution_run: ExecutionRun) -> None:
        if self._on_execution_complete:
            self._on_execution_complete(execution_run)

    def on_execution_fail(self, execution_run: ExecutionRun) -> None:
        if self._on_execution_fail:
            self._on_execution_fail(execution_run)

    def on_execution_update(self, execution_run: ExecutionRun) -> None:
        if self._on_execution_update:
            self._on_execution_update(execution_run)

    def on_task_start(self, task_run: TaskRun) -> None:
        if self._on_task_start:
            self._on_task_start(task_run)

    def on_task_complete(self, task_run: TaskRun) -> None:
        if self._on_task_complete:
            self._on_task_complete(task_run)

    def on_task_fail(self, task_run: TaskRun) -> None:
        if self._on_task_fail:
            self._on_task_fail(task_run)

    def on_step_start(self, step_run: StepRun) -> None:
        if self._on_step_start:
            self._on_step_start(step_run)

    def on_step_complete(self, step_run: StepRun) -> None:
        if self._on_step_complete:
            self._on_step_complete(step_run)

    def on_step_fail(self, step_run: StepRun) -> None:
        if self._on_step_fail:
            self._on_step_fail(step_run)


class ConsoleObserver(Observer):
    """Console observer for logging execution events."""

    def __init__(self, verbose: bool = True) -> None:
        """Initialize console observer.

        Args:
            verbose (bool, optional): Enable verbose logging. Defaults to True.
        """
        self.verbose = verbose

    def on_execution_start(self, execution_run: ExecutionRun) -> None:
        logger.info(f"Execution run '{execution_run.name}' - Started execution run")

    def on_execution_complete(self, execution_run: ExecutionRun) -> None:
        logger.info(
            f"Execution run '{execution_run.name}' - Finished in state Completed() "
            f"[{execution_run.completed_tasks}/{execution_run.total_tasks} tasks]"
        )

    def on_execution_fail(self, execution_run: ExecutionRun) -> None:
        logger.error(f"Execution run '{execution_run.name}' - Finished in state Failed(message='{execution_run.error}')")

    def on_task_start(self, task_run: TaskRun) -> None:
        if self.verbose:
            logger.info(f"Task run '{task_run.name}' - Started task run")

    def on_task_complete(self, task_run: TaskRun) -> None:
        if self.verbose:
            logger.info(f"Task run '{task_run.name}' - Finished in state Completed()")

    def on_task_fail(self, task_run: TaskRun) -> None:
        logger.error(f"Task run '{task_run.name}' - Finished in state Failed(message='{task_run.error}')")

    def on_step_start(self, step_run: StepRun) -> None:
        if self.verbose:
            logger.info(f"Step run '{step_run.name}' - Started step run")

    def on_step_fail(self, step_run: StepRun) -> None:
        if self.verbose:
            logger.error(f"Step run '{step_run.name}' - Finished in state Failed(message='{step_run.error}')")


_observers: list[Observer] = []
_default_observer: Observer | None = None


def register_observer(observer: Observer) -> None:
    """Register an observer.

    Args:
        observer (Observer): Observer to register.
    """
    _observers.append(observer)


def on_event(
    on_execution_start: Optional[Callable[[ExecutionRun], None]] = None,
    on_execution_complete: Optional[Callable[[ExecutionRun], None]] = None,
    on_execution_fail: Optional[Callable[[ExecutionRun], None]] = None,
    on_task_start: Optional[Callable[[TaskRun], None]] = None,
    on_task_complete: Optional[Callable[[TaskRun], None]] = None,
    on_task_fail: Optional[Callable[[TaskRun], None]] = None,
    on_step_start: Optional[Callable[[StepRun], None]] = None,
    on_step_complete: Optional[Callable[[StepRun], None]] = None,
    on_step_fail: Optional[Callable[[StepRun], None]] = None,
) -> CallbackObserver:
    """Register callbacks for specific events.

    Args:
        on_execution_start: Callback for execution start.
        on_execution_complete: Callback for execution complete.
        on_execution_fail: Callback for execution fail.
        on_task_start: Callback for task start.
        on_task_complete: Callback for task complete.
        on_task_fail: Callback for task fail.
        on_step_start: Callback for step start.
        on_step_complete: Callback for step complete.
        on_step_fail: Callback for step fail.

    Returns:
        CallbackObserver: The registered observer.

    Example:
        on_event(
            on_task_complete=lambda task: print(f"Task {task.name} completed!"),
            on_execution_fail=lambda exec: send_alert(exec.error)
        )
    """
    observer = CallbackObserver(
        on_execution_start=on_execution_start,
        on_execution_complete=on_execution_complete,
        on_execution_fail=on_execution_fail,
        on_task_start=on_task_start,
        on_task_complete=on_task_complete,
        on_task_fail=on_task_fail,
        on_step_start=on_step_start,
        on_step_complete=on_step_complete,
        on_step_fail=on_step_fail,
    )
    register_observer(observer)
    return observer


def get_observers() -> list[Observer]:
    """Get all registered observers.

    Returns:
        list[Observer]: List of registered observers.
    """
    return _observers


def disable_default_observer() -> None:
    """Disable the default ConsoleObserver that is registered on import."""
    global _default_observer
    if _default_observer and _default_observer in _observers:
        _observers.remove(_default_observer)
        _default_observer = None


def set_default_observer(observer: Observer) -> None:
    """Set a reference to the default observer.

    Args:
        observer (Observer): Observer to set as default.
    """
    global _default_observer
    _default_observer = observer


def notify_execution_start(execution_run: ExecutionRun) -> None:
    """Notify all observers of execution start."""
    for observer in _observers:
        try:
            observer.on_execution_start(execution_run)
        except Exception:
            pass


def notify_execution_complete(execution_run: ExecutionRun) -> None:
    """Notify all observers of execution completion."""
    for observer in _observers:
        try:
            observer.on_execution_complete(execution_run)
        except Exception:
            pass


def notify_execution_fail(execution_run: ExecutionRun) -> None:
    """Notify all observers of execution failure."""
    for observer in _observers:
        try:
            observer.on_execution_fail(execution_run)
        except Exception:
            pass


def notify_execution_update(execution_run: ExecutionRun) -> None:
    """Notify all observers of execution update."""
    for observer in _observers:
        try:
            observer.on_execution_update(execution_run)
        except Exception:
            pass


def notify_task_start(task_run: TaskRun) -> None:
    """Notify all observers of task start."""
    for observer in _observers:
        try:
            observer.on_task_start(task_run)
        except Exception:
            pass


def notify_task_complete(task_run: TaskRun) -> None:
    """Notify all observers of task completion."""
    for observer in _observers:
        try:
            observer.on_task_complete(task_run)
        except Exception:
            pass


def notify_task_fail(task_run: TaskRun) -> None:
    """Notify all observers of task failure."""
    for observer in _observers:
        try:
            observer.on_task_fail(task_run)
        except Exception:
            pass


def notify_step_start(step_run: StepRun) -> None:
    """Notify all observers of step start."""
    for observer in _observers:
        try:
            observer.on_step_start(step_run)
        except Exception:
            pass


def notify_step_complete(step_run: StepRun) -> None:
    """Notify all observers of step completion."""
    for observer in _observers:
        try:
            observer.on_step_complete(step_run)
        except Exception:
            pass


def notify_step_fail(step_run: StepRun) -> None:
    """Notify all observers of step failure."""
    for observer in _observers:
        try:
            observer.on_step_fail(step_run)
        except Exception:
            pass
