"""
Download speed monitoring for CDN failover decisions.

Monitors download speed over a rolling time window to detect slow connections
that should trigger CDN failover.
"""

import time


class SpeedMonitor:
    """
    Monitors download speed over a time window.

    Tracks bytes downloaded and calculates speed to detect connections
    that are too slow (< threshold for full window duration).
    """

    def __init__(
        self,
        threshold_mbps: float = 1.0,
        check_window_seconds: int = 120,
    ) -> None:
        """
        Initialize speed monitor.

        Args:
            threshold_mbps: Minimum acceptable speed in megabits per second
            check_window_seconds: Time window to measure speed (default 2 minutes)
        """
        self.threshold_mbps = threshold_mbps
        # Convert Mbps to bytes per second
        # (1 Mbps = 1,000,000 bits/s ÷ 8 = 125,000 bytes/s)
        self.threshold_bytes_per_sec = threshold_mbps * 1_000_000 / 8
        self.check_window = check_window_seconds

        # Timing
        self.start_time = time.monotonic()
        self.window_start: float | None = None

        # Byte tracking
        self.window_bytes = 0

        # Instantaneous speed tracking (for progress reporting)
        self.last_measurement_time: float | None = None
        self.last_measurement_bytes = 0

    def record_chunk(self, num_bytes: int) -> None:
        """
        Record bytes downloaded.

        Args:
            num_bytes: Number of bytes in this chunk
        """
        now = time.monotonic()

        # Initialize window on first chunk
        if self.window_start is None:
            self.window_start = now

        self.window_bytes += num_bytes

    def is_too_slow(self) -> bool:
        """
        Check if download speed is below threshold for the full window.

        Returns:
            True if speed has been below threshold for full check_window duration
        """
        if self.window_start is None:
            return False  # No data yet

        elapsed = time.monotonic() - self.window_start

        # Don't fail until we have a full window of data
        if elapsed < self.check_window:
            return False

        # Calculate average speed over the window
        speed_bytes_per_sec = self.window_bytes / elapsed

        return speed_bytes_per_sec < self.threshold_bytes_per_sec

    def get_current_speed_mbps(self) -> float:
        """
        Get current download speed in megabits per second.

        Returns:
            Current speed in Mbps, or 0.0 if no data yet
        """
        if self.window_start is None:
            return 0.0

        elapsed = time.monotonic() - self.window_start
        if elapsed == 0:
            return 0.0

        # Calculate speed in bytes/sec, then convert to Mbps
        speed_bytes_per_sec = self.window_bytes / elapsed
        speed_mbps = (speed_bytes_per_sec * 8) / 1_000_000

        return speed_mbps

    def get_instantaneous_speed_mbps(self) -> float:
        """
        Get instantaneous download speed since last measurement.

        This calculates speed based on bytes downloaded since the last call,
        providing current network throughput rather than cumulative average.
        Used for progress reporting to show real-time speed.

        Returns:
            Instantaneous speed in Mbps, or average if first measurement
        """
        now = time.monotonic()

        if self.last_measurement_time is None:
            # First measurement - initialize and return average
            self.last_measurement_time = now
            self.last_measurement_bytes = self.window_bytes
            return self.get_current_speed_mbps()

        time_delta = now - self.last_measurement_time
        if time_delta == 0:
            return 0.0

        bytes_delta = self.window_bytes - self.last_measurement_bytes

        # Update for next measurement
        self.last_measurement_time = now
        self.last_measurement_bytes = self.window_bytes

        # Calculate instantaneous speed
        speed_bytes_per_sec = bytes_delta / time_delta
        speed_mbps = (speed_bytes_per_sec * 8) / 1_000_000

        return speed_mbps

    def get_elapsed_time(self) -> float:
        """
        Get elapsed time since first chunk.

        Returns:
            Elapsed seconds, or 0.0 if no data yet
        """
        if self.window_start is None:
            return 0.0
        return time.monotonic() - self.window_start
