"""
DEPRECATED: Use sandbox.monitor.SandboxMonitor instead.

This module is kept for backward compatibility but will be removed in a future version.
The new SandboxMonitor provides:
- Atomic state transitions safe for VM snapshot/resume
- Resume detection via clock comparison and marker files
- Idempotent pause callbacks
- No background threads (poll-based)
"""

import threading
import time
import warnings
from typing import Optional, Callable


class CountdownTimer:
    """
    A thread-safe countdown timer that runs in the background.

    Features:
    - Configure a default timeout value
    - Start/reset timer with .set_timeout() (hard timeout - resets only if new timeout < remaining)
    - Start/reset timer with .set_timeout_soft() (soft timeout - always resets, can be overridden)
    - Extend timer with .set_timeout_extend() (extend timeout - resets only if new timeout > remaining)
    - Get remaining time and check expiration status
    - Optional callbacks for tick (every minute), expiry events, and duration increases
    """

    def __init__(
        self,
        on_tick: Optional[Callable[[float], None]] = None,
        on_expire: Optional[Callable[[], None]] = None,
        on_duration_increase: Optional[Callable[[float], None]] = None
    ):
        """
        Initialize the countdown timer.

        Args:
            on_tick: Optional callback called every minute with remaining seconds
            on_expire: Optional callback called when timer reaches 0
            on_duration_increase: Optional callback called when timer duration is increased, with new remaining seconds
        """
        warnings.warn(
            "CountdownTimer is deprecated. Use sandbox.monitor.SandboxMonitor instead.",
            DeprecationWarning,
            stacklevel=2
        )
        self._lock = threading.Lock()
        self._generation = 0  # Track timer generations to invalidate old threads

        self._default_timeout_minutes: Optional[float] = None
        self._end_time: Optional[float] = None
        self._is_running = False
        self._is_soft_timeout = False  # Track if current timeout is soft
        self._has_completed = False  # Track if timer has completed naturally

        self._on_tick = on_tick
        self._on_expire = on_expire
        self._on_duration_increase = on_duration_increase

    def _resolve_timeout(self, timeout_minutes: Optional[float]) -> float:
        """
        Resolve and validate timeout value in seconds.
        Must be called with lock held.

        Args:
            timeout_minutes: Timeout in minutes, or None to use default

        Returns:
            Timeout in seconds

        Raises:
            ValueError: If no timeout is provided and no default is configured
        """
        timeout = timeout_minutes if timeout_minutes is not None else self._default_timeout_minutes
        if timeout is None:
            raise ValueError("No timeout provided and no default timeout configured")
        return timeout * 60

    def _spawn_callback(self, callback: Optional[Callable], *args) -> None:
        """
        Safely spawn a callback in a new daemon thread.

        Args:
            callback: The callback function to invoke
            *args: Arguments to pass to the callback
        """
        if callback:
            threading.Thread(target=lambda: callback(*args), daemon=True).start()

    def _start_timer_thread(self, generation: int) -> None:
        """
        Start the background timer thread for a specific generation.
        Must be called with lock held.

        Args:
            generation: The generation number this thread belongs to
        """
        threading.Thread(target=self._run, args=(generation,), daemon=True).start()

    def _update_timer_state(
        self,
        end_time: Optional[float],
        is_running: bool,
        is_soft: bool,
        has_completed: bool
    ) -> None:
        """
        Atomically update timer state. Must be called with lock held.

        Args:
            end_time: New end time or None
            is_running: Whether timer is running
            is_soft: Whether this is a soft timeout
            has_completed: Whether timer has completed
        """
        self._end_time = end_time
        self._is_running = is_running
        self._is_soft_timeout = is_soft
        self._has_completed = has_completed

    def configure_default_timeout(self, timeout_minutes: float) -> None:
        """
        Configure the default timeout value in minutes.

        Args:
            timeout_minutes: Default timeout duration in minutes
        """
        with self._lock:
            self._default_timeout_minutes = timeout_minutes

    def set_timeout_soft(self, timeout_minutes: Optional[float] = None) -> None:
        """
        Set or reset the countdown timer with a soft timeout.

        A soft timeout can be overridden by subsequent set_timeout() calls without comparison logic.

        Behavior:
        - If timer is not running: start it with the specified timeout (marked as soft)
        - If timer is running: always reset to new timeout (marked as soft)

        Args:
            timeout_minutes: Timeout duration in minutes. If None, uses default timeout.

        Raises:
            ValueError: If no timeout is provided and no default is configured
        """
        with self._lock:
            timeout_seconds = self._resolve_timeout(timeout_minutes)
            current_time = time.time()

            if not self._is_running:
                # Start new timer
                self._generation += 1
                self._update_timer_state(
                    end_time=current_time + timeout_seconds,
                    is_running=True,
                    is_soft=True,
                    has_completed=False
                )
                self._start_timer_thread(self._generation)
                self._spawn_callback(self._on_duration_increase, timeout_seconds)
            else:
                # Reset running timer
                time_remaining = self._end_time - current_time
                self._end_time = current_time + timeout_seconds
                self._is_soft_timeout = True
                self._has_completed = False

                if timeout_seconds > time_remaining:
                    self._spawn_callback(self._on_duration_increase, timeout_seconds)

    def set_timeout(self, timeout_minutes: Optional[float] = None) -> None:
        """
        Set or reset the countdown timer (hard timeout).

        Behavior:
        - If timer is not running: start it with the specified timeout
        - If timer is running with soft timeout: always reset to new timeout
        - If timer is running with hard timeout and new timeout < remaining time: reset to new timeout
        - If timer is running with hard timeout and new timeout >= remaining time: do nothing

        Args:
            timeout_minutes: Timeout duration in minutes. If None, uses default timeout.

        Raises:
            ValueError: If no timeout is provided and no default is configured
        """
        with self._lock:
            timeout_seconds = self._resolve_timeout(timeout_minutes)
            current_time = time.time()

            if not self._is_running:
                # Start new timer
                self._generation += 1
                self._update_timer_state(
                    end_time=current_time + timeout_seconds,
                    is_running=True,
                    is_soft=False,
                    has_completed=False
                )
                self._start_timer_thread(self._generation)
                self._spawn_callback(self._on_duration_increase, timeout_seconds)
            else:
                # Timer is running - check if we should reset
                time_remaining = self._end_time - current_time
                should_reset = self._is_soft_timeout or timeout_seconds < time_remaining

                if should_reset:
                    self._end_time = current_time + timeout_seconds
                    self._is_soft_timeout = False
                    self._has_completed = False
                    self._spawn_callback(self._on_duration_increase, timeout_seconds)
                    
    def set_timeout_extend(self, timeout_minutes: Optional[float] = None, always_extend: bool = False) -> None:
        """
        Extend the countdown timer only if it would give more time.

        Behavior:
        - If timer is not running: do nothing (timer can only be extended if already running)
        - If always_extend=False (default):
          - If timer is running and new timeout > remaining time: reset to new timeout (extend)
          - If timer is running and new timeout <= remaining time: do nothing (keep longer duration)
        - If always_extend=True:
          - If timer is running: add timeout to current remaining time (cumulative extension)

        Args:
            timeout_minutes: Timeout duration in minutes. If None, uses default timeout.
            always_extend: If True, adds timeout to remaining time instead of comparing/replacing.

        Raises:
            ValueError: If no timeout is provided and no default is configured
        """
        with self._lock:
            if not self._is_running:
                # Do nothing - timer can only be extended if already running
                return

            timeout_seconds = self._resolve_timeout(timeout_minutes)
            current_time = time.time()
            time_remaining = self._end_time - current_time

            if always_extend:
                # Add timeout to current remaining time (cumulative extension)
                new_remaining = time_remaining + timeout_seconds
                self._end_time = current_time + new_remaining
                self._is_soft_timeout = False
                self._has_completed = False
                self._spawn_callback(self._on_duration_increase, new_remaining)
            elif timeout_seconds > time_remaining:
                # Extend to the longer timeout
                self._end_time = current_time + timeout_seconds
                self._is_soft_timeout = False
                self._has_completed = False
                self._spawn_callback(self._on_duration_increase, timeout_seconds)

    def stop(self) -> None:
        """
        Stop the countdown timer completely.
        """
        with self._lock:
            if self._is_running:
                self._generation += 1  # Invalidate current thread
                self._update_timer_state(
                    end_time=None,
                    is_running=False,
                    is_soft=False,
                    has_completed=False
                )
            else:
                # Always reset has_completed, even if timer was already stopped
                # This is critical for VM snapshotting scenarios where timer expires
                # during snapshot and needs to be reset after resume
                self._has_completed = False

    def get_time_remaining_seconds(self) -> float:
        """
        Get the remaining time in seconds.

        Returns:
            Remaining time in seconds, or 0 if timer is not running or has expired
        """
        with self._lock:
            if not self._is_running or self._end_time is None:
                return 0.0

            remaining = self._end_time - time.time()
            return max(0.0, remaining)

    def is_expired(self) -> bool:
        """
        Check if the timer has expired.

        Returns:
            True if timer has expired, False otherwise
        """
        return self.get_time_remaining_seconds() == 0.0

    def has_completed(self) -> bool:
        """
        Check if the timer has completed naturally (started and reached expiry).

        Returns:
            True if timer has completed after running, False if stopped/never started/still running
        """
        with self._lock:
            return self._has_completed

    def _run(self, generation: int) -> None:
        """
        Background thread that ticks every minute and handles expiry.

        Args:
            generation: The generation number this thread belongs to
        """
        last_tick_time = time.time()

        while True:
            time.sleep(1)  # Check every second for responsiveness

            with self._lock:
                # Check if this thread is still valid for the current generation
                if generation != self._generation:
                    print(f"TIMER HAS BEEN SUPERSEDED: Generation {generation} != {self._generation}")
                    break  # This timer has been superseded

                if not self._is_running or self._end_time is None:
                    print(f"TIMER HAS STOPPED: is_running={self._is_running}, end_time={self._end_time}")
                    break

                current_time = time.time()
                time_remaining = self._end_time - current_time

                # Check for expiry
                if time_remaining <= 0:
                    self._update_timer_state(
                        end_time=None,
                        is_running=False,
                        is_soft=self._is_soft_timeout,
                        has_completed=True
                    )
                    print
                    print(f"TIMER HAS EXPIRED: is_running={self._is_running}, end_time={self._end_time}")
                    print(f"TIMER HAS EXPIRED: is_soft_timeout={self._is_soft_timeout}")
                    print(f"TIMER HAS EXPIRED: has_completed={self._has_completed}")
                    print(f"TIMER HAS EXPIRED: generation={generation}, self._generation={self._generation}")
                    self._spawn_callback(self._on_expire)
                    break

                # Check for tick (every 60 seconds)
                if current_time - last_tick_time >= 60:
                    last_tick_time = current_time
                    self._spawn_callback(self._on_tick, time_remaining)

    def __del__(self):
        """Cleanup on deletion."""
        self.stop()
