import asyncio
import inspect
import sys
from collections.abc import Callable, Coroutine, Generator
from signal import SIGHUP, SIGINT, SIGTERM, Signals
from typing import (
    Any,
    Generic,
    ParamSpec,
    Self,
    cast,
)

__P__ = ParamSpec("__P__")

__all__ = [
    "EventClosedError",
    "Event",
    "on_signal",
    "on_shutdown",
]


__SHUTDOWN_SIGNALS__ = (SIGHUP, SIGTERM, SIGINT)

if sys.platform == "win32":  # pragma: py-not-win32
    from signal import SIGBREAK

    __SHUTDOWN_SIGNALS__ += (SIGBREAK,)


class EventClosedError(Exception):
    """
    Exception raised when attempting to perform operations on a closed event.
    This includes adding listeners or emitting events after close() is called.
    """

    pass


class Event(Generic[__P__]):
    """
    Event class that allows registering listeners and emitting events.

    This class implements the observer pattern, allowing functions to subscribe
    to events and be notified when those events occur. It supports both
    synchronous and asynchronous listeners and can be used as a context manager.

    Type Args:
        _P: ParamSpec that defines the argument types that registered listeners
            must accept. This ensures type safety between emitters and listeners.
    """

    __closed__: bool = False  # Flag indicating if event is closed
    __listeners__: set[
        Callable[__P__, None | Coroutine[Any, Any, None]]
    ]  # Set of registered listener functions

    def __init__(self) -> None:
        """
        Initialize a new Event instance.
        """
        self.__listeners__ = set()

    def __call__(
        self, *args: __P__.args, **kwargs: __P__.kwargs
    ) -> Coroutine[Any, Any, None]:
        """
        Emit the event with the given arguments.

        Args:
            *args: Positional arguments to pass to listeners
            **kwargs: Keyword arguments to pass to listeners
        """
        return self.emit(*args, **kwargs)

    def __enter__(self) -> Self:
        """
        Enter context manager.
        Allows event to be used in 'with' statements for automatic cleanup.

        Returns:
            Self: The event instance itself.
        """
        return self

    async def __aenter__(self) -> Self:
        return self.__enter__()

    def __exit__(
        self, et: type[Exception], exc: Exception, tb: inspect.Traceback
    ) -> None:
        """
        Exit context manager and ensure event is closed.

        Args:
            et: Exception type if an error occurred
            exc: Exception instance if an error occurred
            tb: Traceback if an error occurred
        """
        self.close()
        if et:
            raise exc

    async def __aexit__(
        self, et: type[Exception], exc: Exception, tb: inspect.Traceback
    ) -> None:
        """
        Exit context manager and ensure event is closed.

        Args:
            et: Exception type if an error occurred
            exc: Exception instance if an error occurred
            tb: Traceback if an error occurred
        """
        return self.__exit__(et, exc, tb)

    def __len__(self) -> int:
        return len(self.__listeners__)

    @staticmethod
    async def __async_emit_wrapper__(coro: Coroutine[Any, Any, None]) -> None:
        try:
            await coro
        except Exception:
            pass

    async def __emit__(self, *args: __P__.args, **kwargs: __P__.kwargs) -> None:
        """
        Internal method to execute all registered listeners.

        Creates an asyncio TaskGroup to run listeners concurrently.
        Handles both sync and async listeners appropriately.
        Silently catches listener exceptions to prevent cascade failures.

        Args:
            *args: Positional arguments to pass to listeners
            **kwargs: Keyword arguments to pass to listeners
        """
        async with asyncio.TaskGroup() as group:
            for listener in self.__listeners__:
                try:
                    res = listener(*args, **kwargs)
                    if inspect.isawaitable(res):
                        group.create_task(self.__async_emit_wrapper__(res))
                except Exception:
                    pass

    @property
    def is_closed(self) -> bool:
        """
        Property indicating if event is closed.

        Returns:
            bool: True if event is closed, False otherwise
        """
        return self.__closed__

    def add_listener(
        self, listener: Callable[__P__, None | Coroutine[Any, Any, None]]
    ) -> None:
        """
        Register a new listener function or coroutine.

        The listener can be either a regular function or an async function.
        When the event is emitted, the listener will be called with the emit arguments.

        Args:
            listener: Function or coroutine to register as a listener

        Raises:
            EventClosedError: If the event has been closed
        """
        if self.__closed__:
            raise EventClosedError()

        if callable(listener):
            self.__listeners__.add(listener)

    def __iadd__(
        self, listener: Callable[__P__, None | Coroutine[Any, Any, None]]
    ) -> None:
        self.add_listener(listener)

    def remove_listener(
        self, listener: Callable[__P__, None | Coroutine[Any, Any, None]]
    ) -> None:
        """
        Remove a previously registered listener.

        Safely removes listener from the set of registered listeners.
        No error if listener wasn't registered.

        Args:
            listener: The listener function/coroutine to remove
        """
        self.__listeners__.discard(listener)

    def __isub__(
        self, listener: Callable[__P__, None | Coroutine[Any, Any, None]]
    ) -> None:
        self.remove_listener(listener)

    def has_listener(
        self, listener: Callable[__P__, None | Coroutine[Any, Any, None]]
    ) -> bool:
        return listener in self.__listeners__

    def __contains__(
        self, listener: Callable[__P__, None | Coroutine[Any, Any, None]]
    ) -> bool:
        return self.has_listener(listener)

    async def emit(self, *args: __P__.args, **kwargs: __P__.kwargs) -> None:
        """
        Emit an event to all registered listeners.

        Calls all registered listeners with the provided arguments.
        Async listeners are run concurrently in an asyncio TaskGroup.
        Exceptions in listeners are caught and ignored.

        Args:
            *args: Positional arguments to pass to listeners
            **kwargs: Keyword arguments to pass to listeners

        Raises:
            EventClosedError: If the event has been closed
        """
        if self.__closed__:
            raise EventClosedError()
        await asyncio.create_task(self.__emit__(*args, **kwargs))

    def close(self) -> None:
        """
        Close the event and prevent further operations.

        After closing:
        - No new listeners can be added
        - Events cannot be emitted
        - All registered listeners are cleared

        This operation is idempotent - calling multiple times has no effect.
        """
        if self.__closed__:
            return

        self.__closed__ = True
        self.__listeners__.clear()


__ON_SIGNAL__: Event[Signals] | None = None  # Global singleton for signal event


def on_signal() -> Event[Signals]:
    """
    Returns a global singleton Event that emits for all operating system signals.

    This event emits whenever ANY signal is received by the process (SIGINT, SIGTERM,
    SIGHUP, SIGWINCH, SIGURG, etc.). Use this if you need to handle all signals.

    The event automatically registers signal handlers on first call. Handlers are
    registered using asyncio's add_signal_handler (if loop is running) or standard
    signal.signal (if no loop is running yet).

    Returns:
        Event[Signals]: A singleton Event that emits signal.Signals values.

    Example:
        >>> from aioservicekit import on_signal
        >>> @on_signal()
        ... async def handle(sig):
        ...     print(f"Received {sig}")
    """
    global __ON_SIGNAL__

    if __ON_SIGNAL__ is None:
        __ON_SIGNAL__ = Event[Signals]()

        def all_signals() -> Generator[Signals, None, None]:
            """Get all available signals for the current platform."""
            import signal

            for name in dir(signal):
                if name.startswith("SIG") and not name.startswith("SIG_"):
                    try:
                        sig = getattr(signal, name)
                        if isinstance(sig, Signals):
                            yield sig
                    except (ValueError, OSError):
                        pass

        def handle_signal(sig: Signals) -> Callable[..., None]:
            def inner(*args: Any, **kwargs: Any) -> None:
                global __ON_SIGNAL__
                try:
                    loop = asyncio.get_running_loop()
                except RuntimeError:
                    loop = None

                signal_event = cast(Event[Signals], __ON_SIGNAL__)
                if loop is not None and not loop.is_closed():
                    asyncio.create_task(signal_event.emit(sig))
                else:
                    asyncio.run(signal_event.emit(sig))

            return inner

        try:
            loop = asyncio.get_running_loop()
            for sig in all_signals():
                try:
                    loop.add_signal_handler(sig, handle_signal(sig))
                except (OSError, ValueError):
                    pass
        except RuntimeError:
            for sig in all_signals():
                try:
                    from signal import signal

                    signal(sig, handle_signal(sig))
                except (OSError, ValueError):
                    pass

    return __ON_SIGNAL__


__ON_SHUTDOWN__: Event[Signals] | None = None  # Global singleton for shutdown event


def on_shutdown() -> Event[Signals]:
    """
    Returns a global singleton Event for graceful shutdown signals.

    This event emits only for shutdown-related signals: SIGINT, SIGTERM, SIGHUP
    (and SIGBREAK on Windows). It subscribes to on_signal() and filters out
    non-shutdown signals.

    Used internally by Service to trigger graceful shutdown. Use this for
    application cleanup logic.

    Returns:
        Event[Signals]: A singleton Event that emits shutdown signal.Signals values.

    Example:
        >>> from aioservicekit import on_shutdown
        >>> @on_shutdown()
        ... async def cleanup(sig):
        ...     print(f"Shutting down due to {sig}")
    """
    global __ON_SHUTDOWN__

    if __ON_SHUTDOWN__ is None:
        __ON_SHUTDOWN__ = Event[Signals]()

        async def handler(sig: Signals) -> None:
            global __ON_SHUTDOWN__
            if sig in __SHUTDOWN_SIGNALS__:
                await cast(Event[Signals], __ON_SHUTDOWN__).emit(sig)

        on_signal().add_listener(handler)

    return __ON_SHUTDOWN__
