"""Graceful shutdown coordination.

Provides a central ShutdownHandler that components can use to:
- Check if shutdown has been triggered
- Register cleanup hooks
- Sleep interruptibly
- Handle SIGTERM / SIGINT signals
"""

import logging
import signal
import time
import threading
from typing import Callable, List, Optional

log = logging.getLogger(__name__)


class ShutdownHandler:
    """Central shutdown coordinator for the application.

    Uses ``threading.Event`` for single-process, multi-threaded usage.
    """

    _instance: Optional["ShutdownHandler"] = None

    def __init__(self, install_signal_handlers: bool = True) -> None:
        self._event = threading.Event()
        self._trigger_at: float = 0.0
        self._last_seen_shutdown = False
        self._hooks: List[Callable[[], None]] = []
        self._hooks_lock = threading.Lock()
        self._hooks_executed = False

        if install_signal_handlers:
            self._install_signals()

    # ------------------------------------------------------------------
    # Singleton
    # ------------------------------------------------------------------
    @staticmethod
    def get_instance() -> "ShutdownHandler":
        if ShutdownHandler._instance is None:
            ShutdownHandler._instance = ShutdownHandler()
        return ShutdownHandler._instance

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------
    def is_shutdown(self) -> bool:
        """Return *True* once ``trigger()`` has been called (or timer expired)."""
        result = self._check_flag()
        if not result and self._last_seen_shutdown:
            log.info("Inconsistent shutdown flag state")
            result = True
        if result and not self._last_seen_shutdown:
            log.info("Recognizing shutdown")
        self._last_seen_shutdown = result
        return result

    def trigger(self, delay_sec: float = 0) -> None:
        """Initiate shutdown, optionally after *delay_sec* seconds."""
        if delay_sec <= 0:
            self._execute_hooks()
            self._event.set()
        else:
            new_time = time.monotonic() + delay_sec
            if self._trigger_at == 0 or self._trigger_at > new_time:
                self._trigger_at = new_time

    def register_hook(self, callback: Callable[[], None]) -> None:
        """Register a cleanup callback that runs exactly once on shutdown."""
        with self._hooks_lock:
            self._hooks.append(callback)

    def sleep(self, seconds: float) -> bool:
        """Sleep for *seconds*, returning early if shutdown is triggered.

        Returns ``True`` if the sleep was interrupted by shutdown.
        """
        target = time.monotonic() + seconds
        remaining = seconds
        while remaining > 0 and not self.is_shutdown():
            time.sleep(min(remaining, 0.1))
            remaining = target - time.monotonic()
        return self.is_shutdown()

    def wait(self, timeout: float | None = None) -> bool:
        """Block until shutdown is triggered.  Returns the event state."""
        return self._event.wait(timeout)

    @property
    def event(self) -> threading.Event:
        """Expose the underlying event for external integration."""
        return self._event

    # ------------------------------------------------------------------
    # Signal handlers
    # ------------------------------------------------------------------
    def handle_sigterm(self, signum, frame) -> None:
        log.info("Got SIGTERM")
        self.trigger()

    def handle_sigint(self, signum, frame) -> None:
        log.info("Got SIGINT")
        self.trigger()

    # ------------------------------------------------------------------
    # Testing
    # ------------------------------------------------------------------
    def reset_for_testing(self) -> None:
        """Reset all state.  **Only use in tests.**"""
        self._event.clear()
        self._trigger_at = 0.0
        self._last_seen_shutdown = False
        self._hooks_executed = False

    # ------------------------------------------------------------------
    # Internals
    # ------------------------------------------------------------------
    def _check_flag(self) -> bool:
        if self._event.is_set():
            return True
        if self._trigger_at > 0 and time.monotonic() > self._trigger_at:
            self._execute_hooks()
            self._event.set()
            return True
        return False

    def _execute_hooks(self) -> None:
        with self._hooks_lock:
            if self._hooks_executed:
                return
            self._hooks_executed = True
            for hook in self._hooks:
                try:
                    hook()
                except Exception as exc:
                    log.error("Error executing shutdown hook: %s", exc)

    def _install_signals(self) -> None:
        try:
            signal.signal(signal.SIGTERM, self.handle_sigterm)
            signal.signal(signal.SIGINT, self.handle_sigint)
        except (OSError, ValueError):
            # Not on main thread or signal not available
            pass
