"""VERONICA Exit Handler - Graceful shutdown with state preservation."""

from __future__ import annotations

__all__ = [
    "ExitTier",
    "VeronicaExit",
]
import signal
import atexit
import logging
import threading
from enum import IntEnum
from typing import Optional

from veronica_core.state import VeronicaStateMachine, VeronicaState
from veronica_core.backends import PersistenceBackend

logger = logging.getLogger(__name__)


class ExitTier(IntEnum):
    """Exit priority levels."""

    GRACEFUL = 1  # Save state, cleanup, log
    EMERGENCY = 2  # Save state, minimal cleanup
    FORCE = 3  # Immediate exit (no save)


class VeronicaExit:
    """Exit handler for VERONICA state persistence."""

    def __init__(
        self,
        state_machine: VeronicaStateMachine,
        persistence: Optional[PersistenceBackend] = None,
    ):
        self.state_machine = state_machine
        if persistence is None:
            from veronica_core.backends import JSONBackend

            self.persistence: PersistenceBackend = JSONBackend(
                "data/state/veronica_state.json"
            )
        else:
            self.persistence = persistence
        self.exit_requested = False
        self.exit_tier: Optional[ExitTier] = None
        self.exit_reason: str = ""
        self._exit_lock = threading.Lock()

        # Register handlers
        self._register_handlers()

    def _register_handlers(self) -> None:
        """Register signal handlers and atexit."""
        # signal.signal() raises ValueError when called from a non-main thread.
        # Only register signal handlers from the main thread; atexit is always
        # registered as the fallback path for all threads.
        if threading.current_thread() is threading.main_thread():
            signal.signal(signal.SIGTERM, self._signal_handler)
            signal.signal(signal.SIGINT, self._signal_handler)

        # Atexit as fallback
        atexit.register(self._atexit_handler)

        logger.info(
            "[VERONICA_EXIT] Exit handlers registered (SIGTERM, SIGINT, atexit)"
        )

    def _signal_handler(self, signum: int, frame) -> None:
        """Handle SIGTERM/SIGINT.

        M3 NOTE: This handler does NOT call sys.exit(). request_exit() saves
        state and sets exit_requested=True, but does not terminate the process.
        The main loop must poll is_exit_requested() and exit cleanly.
        This design allows the main loop to finish in-progress operations before
        terminating, rather than being interrupted at an arbitrary point.
        If immediate termination is needed, use ExitTier.FORCE and call
        sys.exit() in the main loop upon detecting is_exit_requested().
        """
        signal_name = signal.Signals(signum).name
        logger.warning(f"[VERONICA_EXIT] Signal received: {signal_name}")

        if signum == signal.SIGTERM:
            self.request_exit(ExitTier.GRACEFUL, "SIGTERM received")
        elif signum == signal.SIGINT:
            self.request_exit(ExitTier.EMERGENCY, "SIGINT (Ctrl+C) received")

    def _atexit_handler(self) -> None:
        """Atexit fallback (process termination)."""
        if not self.exit_requested:
            logger.warning(
                "[VERONICA_EXIT] atexit triggered without explicit exit request"
            )
            self.request_exit(ExitTier.EMERGENCY, "atexit fallback")

    def request_exit(self, tier: ExitTier, reason: str) -> None:
        """Request exit at specified tier. Thread-safe against duplicate signals.

        M3 NOTE: This method does NOT call sys.exit(). After saving state and
        setting exit_requested=True, it returns control to the caller. The main
        application loop is responsible for checking is_exit_requested() and
        performing a clean shutdown. This is intentional: it allows the main loop
        to complete in-flight operations before exiting. If the process must
        terminate immediately (e.g. for FORCE tier), the caller should call
        sys.exit() after this method returns.
        """
        with self._exit_lock:
            if self.exit_requested:
                logger.warning(
                    f"[VERONICA_EXIT] Exit already requested (tier={self.exit_tier}), ignoring"
                )
                return

            self.exit_requested = True
            self.exit_tier = tier
            self.exit_reason = reason

        logger.warning(
            f"[VERONICA_EXIT] Exit requested: tier={tier.name} reason='{reason}'"
        )

        # Execute tier-specific logic
        if tier == ExitTier.GRACEFUL:
            self._graceful_exit()
        elif tier == ExitTier.EMERGENCY:
            self._emergency_exit()
        elif tier == ExitTier.FORCE:
            self._force_exit()

    def _graceful_exit(self) -> None:
        """Tier 1: Full cleanup with state save."""
        logger.info("[VERONICA_EXIT] GRACEFUL exit initiated")

        # 1. Transition to SAFE_MODE
        try:
            self.state_machine.transition(VeronicaState.SAFE_MODE, self.exit_reason)
        except Exception:
            logger.exception("[VERONICA_EXIT] State transition to SAFE_MODE failed")

        # 2. Cleanup expired cooldowns
        try:
            expired = self.state_machine.cleanup_expired()
            if expired:
                logger.info(
                    f"[VERONICA_EXIT] Cleaned up {len(expired)} expired cooldowns"
                )
        except Exception:
            logger.exception("[VERONICA_EXIT] Cooldown cleanup failed")

        # 3. Save state
        try:
            success = self.persistence.save(self.state_machine.to_dict())
            if success:
                logger.info("[VERONICA_EXIT] State saved successfully")
            else:
                logger.error("[VERONICA_EXIT] State save FAILED")
        except Exception:
            logger.exception("[VERONICA_EXIT] State save raised an exception")

        # 4. Log final stats
        try:
            stats = self.state_machine.get_stats()
            logger.info(f"[VERONICA_EXIT] Final stats: {stats}")
        except Exception:
            logger.exception("[VERONICA_EXIT] Failed to retrieve final stats")

        logger.info("[VERONICA_EXIT] GRACEFUL exit complete")

    def _emergency_exit(self) -> None:
        """Tier 2: Minimal cleanup, save state."""
        logger.warning("[VERONICA_EXIT] EMERGENCY exit initiated")

        # 1. Transition to SAFE_MODE
        try:
            self.state_machine.transition(VeronicaState.SAFE_MODE, self.exit_reason)
        except Exception:
            logger.exception("[VERONICA_EXIT] Emergency state transition failed")

        # 2. Save state (critical)
        try:
            success = self.persistence.save(self.state_machine.to_dict())
            if success:
                logger.info("[VERONICA_EXIT] Emergency state save OK")
            else:
                logger.error("[VERONICA_EXIT] Emergency state save FAILED")
        except Exception:
            logger.exception("[VERONICA_EXIT] Emergency state save raised an exception")

        logger.warning("[VERONICA_EXIT] EMERGENCY exit complete")

    def _force_exit(self) -> None:
        """Tier 3: Immediate exit, no save."""
        logger.error("[VERONICA_EXIT] FORCE exit initiated (NO STATE SAVE)")
        logger.error(f"[VERONICA_EXIT] Reason: {self.exit_reason}")
        # No save, no cleanup

    def is_exit_requested(self) -> bool:
        """Check if exit has been requested."""
        return self.exit_requested
