//! LEDBAT++ congestion controller.
//!
//! This module contains the main controller implementation with slow start
//! and periodic slowdown support.

use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};
use std::time::Duration;

use crate::config::GlobalRng;
use crate::simulation::{RealTime, TimeSource};

use super::atomic::{AtomicBaseDelayHistory, AtomicDelayFilter};
use super::config::{
    LedbatConfig, MAX_GAIN_DIVISOR, MSS, PATH_CHANGE_RTT_THRESHOLD, RTT_SCALING_BYTES_PER_MS,
    SLOWDOWN_DELAY_RTTS, SLOWDOWN_FREEZE_RTTS, SLOWDOWN_INTERVAL_MULTIPLIER,
    SLOWDOWN_REDUCTION_FACTOR, TARGET,
};
use super::state::{AtomicCongestionState, CongestionState};
use super::stats::LedbatStats;

/// LEDBAT++ congestion controller with slow start and periodic slowdowns.
///
/// Implements draft-irtf-iccrg-ledbat-plus-plus with improvements over RFC 6817:
/// - Dynamic GAIN based on base_delay
/// - Multiplicative decrease capped at -W/2
/// - Periodic slowdowns for inter-flow fairness
/// - 60ms target delay (vs 100ms in RFC 6817)
///
/// ## Lock-Free Design
///
/// This controller is fully lock-free, using atomic operations for all state.
/// No mutexes are held on the hot path (on_ack, on_send).
///
/// ## Thread Safety
///
/// Individual atomic operations are lock-free and thread-safe. However,
/// compound state transitions (e.g., state change + associated variable updates)
/// are NOT atomic. In practice, this controller is designed to be accessed from
/// a single tokio task per connection. For strictly concurrent access from
/// multiple threads, external synchronization may be required to prevent
/// interleaved state machine transitions (e.g., `on_timeout` racing with `on_ack`).
///
/// ## Slow Start Phase
///
/// Before LEDBAT's congestion avoidance, uses TCP-style exponential growth
/// for fast ramp-up. Exits when:
/// - cwnd >= ssthresh (reached threshold), OR
/// - queuing_delay > TARGET * delay_exit_threshold (congestion detected)
///
/// ## Periodic Slowdowns
///
/// After initial slow start exit, periodically reduces cwnd to minimum to
/// re-measure base delay and ensure fair sharing with competing flows.
///
/// ## Type Parameter
///
/// `T` is the time source used for timing operations. Defaults to `RealTime`
/// for production use. In tests, use `VirtualTime` for deterministic
/// virtual time testing via `LedbatController::new_with_time_source()`.
pub struct LedbatController<T: TimeSource = RealTime> {
    /// Congestion window (bytes in flight)
    pub(crate) cwnd: AtomicUsize,

    /// Bytes currently in flight (sent but not ACKed)
    pub(crate) flightsize: AtomicUsize,

    /// Lock-free delay filter (MIN over recent samples)
    pub(crate) delay_filter: AtomicDelayFilter,

    /// Lock-free base delay history (10-minute buckets)
    pub(crate) base_delay_history: AtomicBaseDelayHistory<T>,

    /// Current queuing delay estimate (stored as nanoseconds)
    pub(crate) queuing_delay_nanos: AtomicU64,

    /// Last update time (stored as nanos since controller creation)
    pub(crate) last_update_nanos: AtomicU64,

    /// Time source for getting current time.
    ///
    /// In production, this is `RealTime` which wraps `Instant::now()`.
    /// In tests, `VirtualTime` allows deterministic virtual time control.
    pub(crate) time_source: T,

    /// Reference epoch in nanoseconds for converting to duration-since-start.
    ///
    /// All timing calculations use `time_source.now_nanos() - epoch_nanos`
    /// to ensure tests with virtual time sources observe the mocked time
    /// rather than wall-clock time.
    ///
    /// This field is set once at construction time from `time_source.now_nanos()`.
    pub(crate) epoch_nanos: u64,

    /// Bytes acknowledged since last update
    pub(crate) bytes_acked_since_update: AtomicUsize,

    /// Slow start threshold (bytes)
    pub(crate) ssthresh: AtomicUsize,

    // ===== Unified Congestion State Machine =====
    /// Current congestion control state. This single field replaces the previous
    /// `in_slow_start` boolean and `SlowdownState` enum, ensuring unambiguous
    /// state transitions. See [`CongestionState`] for the state machine diagram.
    pub(crate) congestion_state: AtomicCongestionState,

    /// Time when current slowdown phase started (nanos since epoch)
    pub(crate) slowdown_phase_start_nanos: AtomicU64,

    /// RTT count within current slowdown phase
    pub(crate) slowdown_rtt_count: AtomicU32,

    /// Duration of last complete slowdown cycle (nanos) for scheduling next
    pub(crate) last_slowdown_duration_nanos: AtomicU64,

    /// Time when next scheduled slowdown should start (nanos since epoch)
    pub(crate) next_slowdown_time_nanos: AtomicU64,

    /// cwnd value saved before slowdown (for ramping back up)
    pub(crate) pre_slowdown_cwnd: AtomicUsize,

    /// Whether initial slow start has completed (triggers first slowdown)
    pub(crate) initial_slow_start_completed: AtomicBool,

    /// Configuration
    pub(crate) target_delay: Duration,
    pub(crate) allowed_increase_packets: usize,
    pub(crate) min_cwnd: usize,
    pub(crate) max_cwnd: usize,
    pub(crate) delay_exit_threshold: f64,
    pub(crate) enable_periodic_slowdown: bool,
    /// Minimum ssthresh floor for timeout recovery.
    /// If None, uses the spec-compliant 2*min_cwnd floor.
    pub(crate) min_ssthresh: Option<usize>,

    // ===== Adaptive min_ssthresh tracking (Phase 2) =====
    /// Initial ssthresh from config (saved for adaptive floor calculation).
    /// This represents the configured upper bound for the adaptive floor.
    pub(crate) initial_ssthresh: usize,

    /// cwnd captured at slow start exit (proxy for path BDP).
    /// This represents the point where we first detected congestion, which is
    /// a good proxy for the actual bandwidth-delay product of the path.
    pub(crate) slow_start_exit_cwnd: AtomicUsize,

    /// Base delay (min RTT) at slow start exit (for path change detection).
    /// Stored as nanoseconds. If the current base_delay differs significantly
    /// from this value, we may have changed paths and should not rely on
    /// the old slow_start_exit_cwnd.
    pub(crate) slow_start_exit_base_delay_nanos: AtomicU64,

    /// Statistics
    pub(crate) total_increases: AtomicUsize,
    pub(crate) total_decreases: AtomicUsize,
    pub(crate) total_losses: AtomicUsize,
    pub(crate) min_cwnd_events: AtomicUsize,
    pub(crate) slow_start_exits: AtomicUsize,
    pub(crate) periodic_slowdowns: AtomicUsize,
    /// Peak congestion window reached during this controller's lifetime
    pub(crate) peak_cwnd: AtomicUsize,
    /// Total retransmission timeouts (RTO events)
    pub(crate) total_timeouts: AtomicUsize,
}

// ============================================================================
// Production constructors (backward-compatible, use real time)
// ============================================================================

#[cfg(test)]
impl LedbatController<RealTime> {
    /// Create new LEDBAT controller with default config (backward compatible).
    ///
    /// # Arguments
    /// * `initial_cwnd` - Initial congestion window (bytes)
    /// * `min_cwnd` - Minimum window (typically 2 * MSS)
    /// * `max_cwnd` - Maximum window (protocol limit or config)
    #[cfg(test)]
    pub fn new(initial_cwnd: usize, min_cwnd: usize, max_cwnd: usize) -> Self {
        let config = LedbatConfig {
            initial_cwnd,
            min_cwnd,
            max_cwnd,
            ..LedbatConfig::default()
        };
        Self::new_with_config(config)
    }

    /// Create new LEDBAT controller with custom configuration.
    ///
    /// This constructor allows full control over slow start parameters
    /// and other LEDBAT settings. Uses real system time (`RealTime`).
    pub fn new_with_config(config: LedbatConfig) -> Self {
        Self::new_with_time_source(config, RealTime::new())
    }
}

// ============================================================================
// Generic implementation (works with any TimeSource)
// ============================================================================

impl<T: TimeSource> LedbatController<T> {
    /// Create new LEDBAT controller with custom configuration and time source.
    ///
    /// This constructor is the primary entry point for creating a controller
    /// with a mock time source for deterministic testing.
    pub fn new_with_time_source(config: LedbatConfig, time_source: T) -> Self {
        // Validate configuration parameters
        assert!(
            config.min_cwnd <= config.initial_cwnd,
            "min_cwnd ({}) must be <= initial_cwnd ({})",
            config.min_cwnd,
            config.initial_cwnd
        );
        assert!(
            config.initial_cwnd <= config.max_cwnd,
            "initial_cwnd ({}) must be <= max_cwnd ({})",
            config.initial_cwnd,
            config.max_cwnd
        );
        assert!(
            config.delay_exit_threshold >= 0.0 && config.delay_exit_threshold <= 1.0,
            "delay_exit_threshold ({}) must be in range [0.0, 1.0]",
            config.delay_exit_threshold
        );

        // Apply ±20% jitter to ssthresh to prevent synchronization
        let ssthresh = if config.randomize_ssthresh {
            // Use GlobalRng for deterministic simulation
            let random_byte = GlobalRng::random_range(0u8..40);
            let jitter_pct = 0.8 + (random_byte as f64) / 100.0; // 0.8 to 1.2 (±20%)
            ((config.ssthresh as f64) * jitter_pct) as usize
        } else {
            config.ssthresh
        };

        let epoch_nanos = time_source.now_nanos();

        Self {
            cwnd: AtomicUsize::new(config.initial_cwnd),
            flightsize: AtomicUsize::new(0),
            delay_filter: AtomicDelayFilter::new(),
            base_delay_history: AtomicBaseDelayHistory::new(time_source.clone()),
            queuing_delay_nanos: AtomicU64::new(0),
            last_update_nanos: AtomicU64::new(0),
            time_source,
            epoch_nanos,
            bytes_acked_since_update: AtomicUsize::new(0),
            ssthresh: AtomicUsize::new(ssthresh),
            // Unified congestion state: start in SlowStart or CongestionAvoidance
            congestion_state: AtomicCongestionState::new(if config.enable_slow_start {
                CongestionState::SlowStart
            } else {
                CongestionState::CongestionAvoidance
            }),
            slowdown_phase_start_nanos: AtomicU64::new(0),
            slowdown_rtt_count: AtomicU32::new(0),
            last_slowdown_duration_nanos: AtomicU64::new(0),
            next_slowdown_time_nanos: AtomicU64::new(u64::MAX), // No scheduled slowdown yet
            pre_slowdown_cwnd: AtomicUsize::new(0),
            initial_slow_start_completed: AtomicBool::new(false),
            // Configuration
            target_delay: TARGET,
            allowed_increase_packets: 2, // RFC 6817 default
            min_cwnd: config.min_cwnd,
            max_cwnd: config.max_cwnd,
            delay_exit_threshold: config.delay_exit_threshold,
            enable_periodic_slowdown: config.enable_periodic_slowdown,
            min_ssthresh: config.min_ssthresh,
            // Adaptive min_ssthresh tracking
            initial_ssthresh: ssthresh, // Save for adaptive floor calculation
            slow_start_exit_cwnd: AtomicUsize::new(0),
            slow_start_exit_base_delay_nanos: AtomicU64::new(0),
            // Statistics
            total_increases: AtomicUsize::new(0),
            total_decreases: AtomicUsize::new(0),
            total_losses: AtomicUsize::new(0),
            min_cwnd_events: AtomicUsize::new(0),
            slow_start_exits: AtomicUsize::new(0),
            periodic_slowdowns: AtomicUsize::new(0),
            peak_cwnd: AtomicUsize::new(config.initial_cwnd),
            total_timeouts: AtomicUsize::new(0),
        }
    }

    /// Store new cwnd value and update peak_cwnd if this is a new maximum.
    fn store_cwnd(&self, new_cwnd: usize) {
        self.cwnd.store(new_cwnd, Ordering::Release);
        // Update peak if this is a new maximum (lock-free max update)
        self.peak_cwnd
            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current_peak| {
                if new_cwnd > current_peak {
                    Some(new_cwnd)
                } else {
                    None
                }
            })
            .ok(); // Ignore result - we just want the side effect
    }

    /// Get the peak congestion window reached during this controller's lifetime.
    pub fn peak_cwnd(&self) -> usize {
        self.peak_cwnd.load(Ordering::Relaxed)
    }

    /// Get elapsed time in nanoseconds since controller creation (for testing).
    #[cfg(test)]
    pub fn elapsed_nanos(&self) -> u64 {
        self.time_source.now_nanos() - self.epoch_nanos
    }

    /// Calculate dynamic GAIN based on base delay (LEDBAT++ Section 4.2)
    ///
    /// GAIN = 1 / min(16, ceil(2 * TARGET / base_delay))
    ///
    /// This adapts the responsiveness based on the ratio of target to base delay.
    /// For low-latency links (low base_delay), GAIN is smaller for stability.
    /// For high-latency links (high base_delay), GAIN approaches 1/16 minimum.
    pub(crate) fn calculate_dynamic_gain(&self, base_delay: Duration) -> f64 {
        let base_ms = base_delay.as_millis() as f64;
        let target_ms = self.target_delay.as_millis() as f64;

        if base_ms <= 0.0 {
            // Edge case: base_delay is zero or unset. This occurs:
            // 1. At connection start before any RTT measurements
            // 2. After base_delay history reset (rare)
            // 3. As a defensive guard against measurement errors
            // Use most conservative GAIN (1/16) for stability.
            return 1.0 / MAX_GAIN_DIVISOR as f64;
        }

        let divisor = (2.0 * target_ms / base_ms).ceil() as u32;
        let clamped_divisor = divisor.clamp(1, MAX_GAIN_DIVISOR);
        1.0 / clamped_divisor as f64
    }

    /// Called when packet sent.
    ///
    /// Tracks bytes in flight for application-limited handling.
    pub fn on_send(&self, bytes: usize) {
        self.flightsize.fetch_add(bytes, Ordering::Relaxed);
    }

    /// Called when ACK received with RTT sample.
    ///
    /// Updates congestion window based on queuing delay.
    /// This method is fully lock-free.
    ///
    /// # Arguments
    /// * `rtt_sample` - Round-trip time measurement
    /// * `bytes_acked_now` - Bytes acknowledged by this ACK
    pub fn on_ack(&self, rtt_sample: Duration, bytes_acked_now: usize) {
        // Capture flightsize BEFORE decreasing - this represents the actual utilization
        // level when the ACK was sent, which is needed for the app-limited cap.
        let pre_ack_flightsize = self.flightsize.load(Ordering::Relaxed);

        // Decrease flightsize with saturating subtraction to prevent underflow
        self.flightsize
            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
                Some(current.saturating_sub(bytes_acked_now))
            })
            .ok();

        // Accumulate acknowledged bytes
        self.bytes_acked_since_update
            .fetch_add(bytes_acked_now, Ordering::Relaxed);

        // Lock-free delay tracking
        // Update base delay history
        self.base_delay_history.update(rtt_sample);

        // Add to delay filter
        self.delay_filter.add_sample(rtt_sample);

        // Get filtered delay (minimum of recent samples)
        if !self.delay_filter.is_ready() {
            return; // Need more samples
        }
        let filtered_rtt = self.delay_filter.filtered_delay().unwrap_or(rtt_sample);

        // Calculate base and queuing delays
        let base_delay = self.base_delay_history.base_delay();
        let queuing_delay = filtered_rtt.saturating_sub(base_delay);
        self.queuing_delay_nanos
            .store(queuing_delay.as_nanos() as u64, Ordering::Release);

        // Rate-limit updates to approximately once per RTT
        let now_nanos = self.time_source.now_nanos() - self.epoch_nanos;
        let last_update = self.last_update_nanos.load(Ordering::Acquire);
        let elapsed_nanos = now_nanos.saturating_sub(last_update);

        // Use base delay as RTT estimate for rate-limiting
        if elapsed_nanos < base_delay.as_nanos() as u64 {
            return;
        }

        // Try to claim this update slot (only one thread should proceed)
        if self
            .last_update_nanos
            .compare_exchange(last_update, now_nanos, Ordering::AcqRel, Ordering::Relaxed)
            .is_err()
        {
            return; // Another thread won the race
        }

        // Calculate off-target amount
        let target = self.target_delay;
        let off_target_ms = if queuing_delay < target {
            (target - queuing_delay).as_millis() as f64
        } else {
            -((queuing_delay - target).as_millis() as f64)
        };
        let target_ms = target.as_millis() as f64;

        // Get total bytes acked since last update
        let bytes_acked_total = self.bytes_acked_since_update.swap(0, Ordering::AcqRel);
        if bytes_acked_total == 0 {
            return; // No progress
        }

        // Unified state machine dispatch - single check, no overlapping flags
        let state = self.congestion_state.load();
        match state {
            CongestionState::SlowStart => {
                // Initial slow start: exponential growth
                self.handle_slow_start(bytes_acked_total, queuing_delay, base_delay);
                return;
            }
            CongestionState::WaitingForSlowdown
            | CongestionState::InSlowdown
            | CongestionState::Frozen
            | CongestionState::RampingUp => {
                // Slowdown state machine handles these states
                if self.handle_congestion_state(bytes_acked_total, queuing_delay, base_delay) {
                    return;
                }
                // Fall through to congestion avoidance if state completed
            }
            CongestionState::CongestionAvoidance => {
                // Check for scheduled slowdown trigger
                if self.enable_periodic_slowdown
                    && self.handle_congestion_state(bytes_acked_total, queuing_delay, base_delay)
                {
                    return;
                }
                // Fall through to congestion avoidance
            }
        }

        // LEDBAT++ congestion avoidance (draft-irtf-iccrg-ledbat-plus-plus Section 4.2)
        let current_cwnd = self.cwnd.load(Ordering::Acquire);

        // Calculate dynamic GAIN based on base delay
        let gain = self.calculate_dynamic_gain(base_delay);

        let cwnd_change =
            gain * (off_target_ms / target_ms) * (bytes_acked_total as f64) * (MSS as f64)
                / (current_cwnd as f64);

        // LEDBAT++ key improvement: cap decrease at -W/2 (multiplicative decrease limit)
        let max_decrease = -(current_cwnd as f64 / 2.0);
        let capped_change = if cwnd_change < max_decrease {
            max_decrease
        } else {
            cwnd_change
        };

        let mut new_cwnd = current_cwnd as f64 + capped_change;

        // Track statistics
        if capped_change > 0.0 {
            self.total_increases.fetch_add(1, Ordering::Relaxed);
        } else if capped_change < 0.0 {
            self.total_decreases.fetch_add(1, Ordering::Relaxed);
        }

        // Application-limited cap (RFC 6817 Section 2.4.1)
        let basic_cap = pre_ack_flightsize + (self.allowed_increase_packets * MSS);
        let max_allowed_cwnd = if capped_change >= 0.0 {
            // Stable or growing: allow up to ssthresh to escape the flightsize trap
            let ssthresh = self.ssthresh.load(Ordering::Acquire);
            basic_cap.max(ssthresh)
        } else {
            // Decreasing (congestion response): use strict app-limited cap
            basic_cap
        };
        new_cwnd = new_cwnd.min(max_allowed_cwnd as f64);

        // Enforce bounds
        new_cwnd = new_cwnd.max(self.min_cwnd as f64).min(self.max_cwnd as f64);

        let new_cwnd_usize = new_cwnd as usize;

        if new_cwnd_usize == self.min_cwnd && current_cwnd > self.min_cwnd {
            self.min_cwnd_events.fetch_add(1, Ordering::Relaxed);
        }

        self.store_cwnd(new_cwnd_usize);

        // Log significant changes
        let change_abs = (new_cwnd_usize as i64 - current_cwnd as i64).unsigned_abs() as usize;
        if change_abs > 10_000 {
            tracing::debug!(
                old_cwnd_kb = current_cwnd / 1024,
                new_cwnd_kb = new_cwnd_usize / 1024,
                change_kb = cwnd_change as i64 / 1024,
                queuing_delay_ms = queuing_delay.as_millis(),
                base_delay_ms = base_delay.as_millis(),
                off_target_ms,
                bytes_acked = bytes_acked_total,
                flightsize_kb = pre_ack_flightsize / 1024,
                "LEDBAT cwnd update"
            );
        }
    }

    /// Handle slow start phase (exponential growth).
    fn handle_slow_start(&self, bytes_acked: usize, queuing_delay: Duration, base_delay: Duration) {
        // Early exit if no longer in SlowStart state (race: timeout occurred)
        if !self.congestion_state.is_slow_start() {
            return;
        }

        let current_cwnd = self.cwnd.load(Ordering::Acquire);
        let ssthresh = self.ssthresh.load(Ordering::Acquire);

        // Check exit conditions (LEDBAT++ uses 3/4 of target, configured via delay_exit_threshold)
        let delay_threshold =
            Duration::from_secs_f64(self.target_delay.as_secs_f64() * self.delay_exit_threshold);
        let should_exit = current_cwnd >= ssthresh || queuing_delay > delay_threshold;

        if should_exit {
            self.slow_start_exits.fetch_add(1, Ordering::Relaxed);

            // Capture BDP proxy for adaptive min_ssthresh calculation.
            self.slow_start_exit_cwnd
                .store(current_cwnd, Ordering::Release);
            self.slow_start_exit_base_delay_nanos
                .store(base_delay.as_nanos() as u64, Ordering::Release);

            // Conservative reduction on exit
            let new_cwnd = ((current_cwnd as f64) * 0.9) as usize;
            let new_cwnd = new_cwnd.max(self.min_cwnd).min(self.max_cwnd);
            self.cwnd.store(new_cwnd, Ordering::Release);

            // LEDBAT++: Schedule initial slowdown after first slow start exit
            if self.enable_periodic_slowdown
                && !self
                    .initial_slow_start_completed
                    .swap(true, Ordering::AcqRel)
            {
                let now_nanos = self.time_source.now_nanos() - self.epoch_nanos;
                let delay_nanos = base_delay.as_nanos() as u64 * SLOWDOWN_DELAY_RTTS as u64;
                self.next_slowdown_time_nanos
                    .store(now_nanos + delay_nanos, Ordering::Release);
                self.congestion_state.enter_waiting_for_slowdown();

                tracing::debug!(
                    delay_ms = delay_nanos / 1_000_000,
                    "LEDBAT++ scheduling initial slowdown after slow start exit"
                );
            } else {
                self.congestion_state.enter_congestion_avoidance();
            }

            let exit_reason = if current_cwnd >= ssthresh {
                "ssthresh"
            } else {
                "delay"
            };

            tracing::debug!(
                old_cwnd_kb = current_cwnd / 1024,
                new_cwnd_kb = new_cwnd / 1024,
                ssthresh_kb = ssthresh / 1024,
                queuing_delay_ms = queuing_delay.as_millis(),
                delay_threshold_ms = delay_threshold.as_millis(),
                reason = exit_reason,
                total_exits = self.slow_start_exits.load(Ordering::Relaxed),
                "Exiting slow start"
            );
        } else {
            // Exponential growth: cwnd += bytes_acked (doubles per RTT)
            let new_cwnd = (current_cwnd + bytes_acked).min(self.max_cwnd);
            self.store_cwnd(new_cwnd);

            tracing::trace!(
                old_cwnd_kb = current_cwnd / 1024,
                new_cwnd_kb = new_cwnd / 1024,
                bytes_acked_kb = bytes_acked / 1024,
                queuing_delay_ms = queuing_delay.as_millis(),
                "Slow start growth"
            );
        }
    }

    /// Handle congestion state machine transitions.
    ///
    /// Returns true if the state machine handled this update (caller should return).
    fn handle_congestion_state(
        &self,
        bytes_acked: usize,
        queuing_delay: Duration,
        base_delay: Duration,
    ) -> bool {
        let now_nanos = self.time_source.now_nanos() - self.epoch_nanos;
        let state = self.congestion_state.load();

        match state {
            CongestionState::CongestionAvoidance => {
                // Check if it's time for the next scheduled slowdown
                let next_slowdown = self.next_slowdown_time_nanos.load(Ordering::Acquire);
                if now_nanos >= next_slowdown
                    && next_slowdown != u64::MAX
                    && self.start_slowdown(now_nanos, base_delay)
                {
                    return true;
                }
                false
            }
            CongestionState::WaitingForSlowdown => {
                let next_slowdown = self.next_slowdown_time_nanos.load(Ordering::Acquire);
                if now_nanos >= next_slowdown {
                    if self.start_slowdown(now_nanos, base_delay) {
                        return true;
                    }
                    self.congestion_state.enter_congestion_avoidance();
                    return false;
                }
                true
            }
            CongestionState::InSlowdown => {
                self.congestion_state.enter_frozen();
                self.slowdown_rtt_count.store(0, Ordering::Release);
                self.slowdown_phase_start_nanos
                    .store(now_nanos, Ordering::Release);
                true
            }
            CongestionState::Frozen => {
                let phase_start = self.slowdown_phase_start_nanos.load(Ordering::Acquire);
                let freeze_duration = base_delay.as_nanos() as u64 * SLOWDOWN_FREEZE_RTTS as u64;

                if now_nanos.saturating_sub(phase_start) >= freeze_duration {
                    self.congestion_state.enter_ramping_up();
                    tracing::debug!(
                        cwnd_kb = self.cwnd.load(Ordering::Relaxed) / 1024,
                        "LEDBAT++ slowdown: starting ramp-up phase"
                    );
                    return true;
                }
                let pre_slowdown = self.pre_slowdown_cwnd.load(Ordering::Relaxed);
                let frozen_cwnd = (pre_slowdown / SLOWDOWN_REDUCTION_FACTOR).max(self.min_cwnd);
                self.cwnd.store(frozen_cwnd, Ordering::Release);
                true
            }
            CongestionState::RampingUp => {
                let target_cwnd = self.pre_slowdown_cwnd.load(Ordering::Acquire);
                let current_cwnd = self.cwnd.load(Ordering::Acquire);

                if current_cwnd >= target_cwnd {
                    self.complete_slowdown(now_nanos, base_delay);
                    return true;
                }

                let has_recovered_substantially = current_cwnd * 20 >= target_cwnd * 17; // 85%

                if has_recovered_substantially && queuing_delay > self.target_delay {
                    self.complete_slowdown(now_nanos, base_delay);
                    return true;
                }

                let new_cwnd = (current_cwnd + bytes_acked)
                    .min(target_cwnd)
                    .min(self.max_cwnd);
                self.store_cwnd(new_cwnd);
                true
            }
            CongestionState::SlowStart => false,
        }
    }

    /// Start a periodic slowdown cycle.
    ///
    /// Returns `true` if slowdown was started, `false` if skipped (cwnd too small).
    pub(crate) fn start_slowdown(&self, now_nanos: u64, base_delay: Duration) -> bool {
        let current_cwnd = self.cwnd.load(Ordering::Acquire);

        if current_cwnd <= self.min_cwnd * SLOWDOWN_REDUCTION_FACTOR {
            tracing::debug!(
                cwnd_kb = current_cwnd / 1024,
                min_cwnd_kb = self.min_cwnd / 1024,
                threshold_kb = (self.min_cwnd * SLOWDOWN_REDUCTION_FACTOR) / 1024,
                "LEDBAT++ skipping futile slowdown: cwnd too small to reduce"
            );

            let min_slowdown_duration = base_delay.as_nanos() as u64 * SLOWDOWN_FREEZE_RTTS as u64;
            let min_interval = min_slowdown_duration * SLOWDOWN_INTERVAL_MULTIPLIER as u64;
            let extended_interval = min_interval * 2;
            let next_time = now_nanos + extended_interval;
            self.next_slowdown_time_nanos
                .store(next_time, Ordering::Release);

            return false;
        }

        let floor = self.calculate_adaptive_floor();
        let new_ssthresh = current_cwnd.max(floor);
        self.ssthresh.store(new_ssthresh, Ordering::Release);
        self.pre_slowdown_cwnd
            .store(current_cwnd, Ordering::Release);

        let slowdown_cwnd = (current_cwnd / SLOWDOWN_REDUCTION_FACTOR).max(self.min_cwnd);
        self.cwnd.store(slowdown_cwnd, Ordering::Release);

        self.congestion_state.enter_in_slowdown();
        self.slowdown_phase_start_nanos
            .store(now_nanos, Ordering::Release);
        self.periodic_slowdowns.fetch_add(1, Ordering::Relaxed);

        tracing::debug!(
            old_cwnd_kb = current_cwnd / 1024,
            new_cwnd_kb = slowdown_cwnd / 1024,
            ssthresh_kb = new_ssthresh / 1024,
            floor_kb = floor / 1024,
            reduction_factor = SLOWDOWN_REDUCTION_FACTOR,
            "LEDBAT++ periodic slowdown: reducing cwnd proportionally, ssthresh floored"
        );

        true
    }

    /// Complete a slowdown cycle and schedule the next one.
    pub(crate) fn complete_slowdown(&self, now_nanos: u64, base_delay: Duration) {
        let phase_start = self.slowdown_phase_start_nanos.load(Ordering::Acquire);
        let slowdown_duration = now_nanos.saturating_sub(phase_start);

        self.last_slowdown_duration_nanos
            .store(slowdown_duration, Ordering::Release);

        let next_interval = slowdown_duration * SLOWDOWN_INTERVAL_MULTIPLIER as u64;
        let min_slowdown_duration = base_delay.as_nanos() as u64 * SLOWDOWN_FREEZE_RTTS as u64;
        let min_interval = min_slowdown_duration * SLOWDOWN_INTERVAL_MULTIPLIER as u64;
        let actual_interval = next_interval.max(min_interval);

        self.next_slowdown_time_nanos
            .store(now_nanos + actual_interval, Ordering::Release);

        self.congestion_state.enter_congestion_avoidance();

        tracing::debug!(
            slowdown_duration_ms = slowdown_duration / 1_000_000,
            next_interval_ms = actual_interval / 1_000_000,
            "LEDBAT++ slowdown complete, scheduling next"
        );
    }

    /// Called when packet loss is detected (e.g., via duplicate ACKs).
    ///
    /// Reduces the congestion window by half (multiplicative decrease).
    /// If in slow start, transitions to congestion avoidance.
    pub fn on_loss(&self) {
        self.total_losses.fetch_add(1, Ordering::Relaxed);

        if self.congestion_state.is_slow_start() {
            self.congestion_state.enter_congestion_avoidance();
            self.slow_start_exits.fetch_add(1, Ordering::Relaxed);
        }

        let current_cwnd = self.cwnd.load(Ordering::Acquire);
        let new_cwnd = (current_cwnd / 2).max(self.min_cwnd);

        self.cwnd.store(new_cwnd, Ordering::Release);

        tracing::warn!(
            old_cwnd_kb = current_cwnd / 1024,
            new_cwnd_kb = new_cwnd / 1024,
            total_losses = self.total_losses.load(Ordering::Relaxed),
            "LEDBAT packet loss - halving cwnd"
        );
    }

    /// Detect if a significant path change has occurred based on RTT shift.
    pub(crate) fn has_path_changed(&self, exit_base_delay_nanos: u64) -> bool {
        let current_base_delay_nanos = self.base_delay().as_nanos() as u64;

        if exit_base_delay_nanos == 0 || current_base_delay_nanos == 0 {
            return false;
        }

        let ratio = if current_base_delay_nanos > exit_base_delay_nanos {
            current_base_delay_nanos as f64 / exit_base_delay_nanos as f64
        } else {
            exit_base_delay_nanos as f64 / current_base_delay_nanos as f64
        };

        ratio > PATH_CHANGE_RTT_THRESHOLD
    }

    /// Calculate adaptive min_ssthresh floor based on observed path characteristics.
    pub(crate) fn calculate_adaptive_floor(&self) -> usize {
        let spec_floor = self.min_cwnd * 2;

        if let Some(explicit_min) = self.min_ssthresh {
            let slow_start_exit = self.slow_start_exit_cwnd.load(Ordering::Acquire);
            let exit_base_delay_nanos = self
                .slow_start_exit_base_delay_nanos
                .load(Ordering::Acquire);

            let path_changed = self.has_path_changed(exit_base_delay_nanos);

            let adaptive = if slow_start_exit > 0 && !path_changed {
                slow_start_exit.min(explicit_min)
            } else {
                let base_delay_ms = self.base_delay().as_millis() as usize;
                if base_delay_ms > 0 {
                    (base_delay_ms * RTT_SCALING_BYTES_PER_MS).min(explicit_min)
                } else {
                    explicit_min
                }
            };

            let floor = adaptive.max(explicit_min).max(spec_floor);

            tracing::trace!(
                slow_start_exit_kb = slow_start_exit / 1024,
                explicit_min_kb = explicit_min / 1024,
                adaptive_kb = adaptive / 1024,
                final_floor_kb = floor / 1024,
                "Adaptive floor with explicit min_ssthresh"
            );

            return floor;
        }

        let slow_start_exit = self.slow_start_exit_cwnd.load(Ordering::Acquire);

        if slow_start_exit > 0 {
            let exit_base_delay_nanos = self
                .slow_start_exit_base_delay_nanos
                .load(Ordering::Acquire);

            let path_changed = self.has_path_changed(exit_base_delay_nanos);

            if !path_changed {
                let bdp_floor = slow_start_exit.min(self.initial_ssthresh);
                let floor = bdp_floor.max(spec_floor);

                tracing::trace!(
                    slow_start_exit_kb = slow_start_exit / 1024,
                    bdp_floor_kb = bdp_floor / 1024,
                    final_floor_kb = floor / 1024,
                    "Adaptive floor using BDP proxy"
                );

                return floor;
            }
        }

        tracing::trace!(
            spec_floor_kb = spec_floor / 1024,
            "Using spec-compliant floor (no adaptive data)"
        );

        spec_floor
    }

    /// Called on retransmission timeout (severe congestion).
    pub fn on_timeout(&self) {
        self.total_losses.fetch_add(1, Ordering::Relaxed);
        self.total_timeouts.fetch_add(1, Ordering::Relaxed);

        let old_cwnd = self.cwnd.load(Ordering::Acquire);

        // Calculate adaptive floor BEFORE setting cwnd, so both cwnd and ssthresh benefit.
        // This prevents the "death spiral" where cwnd collapses to 2.8KB on every timeout
        // while ssthresh stays at 100KB - slow start can't help if you start too low.
        let floor = self.calculate_adaptive_floor();

        // Use 1/4 of adaptive floor as minimum cwnd on timeout.
        // This keeps cwnd low enough to probe for available bandwidth while still
        // maintaining reasonable throughput on high-BDP paths (e.g., 25KB with 100KB floor).
        let adaptive_min_cwnd = (floor / 4).max(self.min_cwnd);
        let new_cwnd = MSS.max(adaptive_min_cwnd);
        self.cwnd.store(new_cwnd, Ordering::Release);

        let new_ssthresh = (old_cwnd / 2).max(floor);
        self.ssthresh.store(new_ssthresh, Ordering::Release);

        self.congestion_state.enter_slow_start();

        self.next_slowdown_time_nanos
            .store(u64::MAX, Ordering::Release);

        if old_cwnd != new_cwnd {
            tracing::warn!(
                old_cwnd_kb = old_cwnd / 1024,
                new_cwnd_kb = new_cwnd / 1024,
                new_ssthresh_kb = new_ssthresh / 1024,
                adaptive_floor_kb = floor / 1024,
                "LEDBAT retransmission timeout - reset to adaptive min_cwnd, entering SlowStart"
            );
        }
    }

    /// Get current congestion window (bytes).
    pub fn current_cwnd(&self) -> usize {
        self.cwnd.load(Ordering::Acquire)
    }

    /// Convert cwnd (bytes in flight) to rate (bytes/sec).
    pub fn current_rate(&self, rtt: Duration) -> usize {
        let cwnd = self.current_cwnd();
        let safe_rtt = rtt.max(Duration::from_millis(1));
        ((cwnd as f64) / safe_rtt.as_secs_f64()) as usize
    }

    /// Get current queuing delay (lock-free).
    pub fn queuing_delay(&self) -> Duration {
        Duration::from_nanos(self.queuing_delay_nanos.load(Ordering::Acquire))
    }

    /// Get base delay (lock-free).
    pub fn base_delay(&self) -> Duration {
        self.base_delay_history.base_delay()
    }

    /// Get current flightsize.
    pub fn flightsize(&self) -> usize {
        self.flightsize.load(Ordering::Relaxed)
    }

    /// Called when ACK received for a retransmitted packet.
    pub fn on_ack_without_rtt(&self, bytes_acked: usize) {
        self.flightsize
            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
                Some(current.saturating_sub(bytes_acked))
            })
            .ok();

        tracing::trace!(
            bytes_acked,
            new_flightsize = self.flightsize.load(Ordering::Relaxed),
            "Decremented flightsize for retransmitted packet ACK"
        );
    }

    /// Release an abandoned packet's bytes from flight size (issue #4345).
    ///
    /// Called when a packet is permanently abandoned after
    /// `MAX_PACKET_RETRANSMITS`. Saturating; does NOT touch cwnd or the delay
    /// controller.
    pub fn release_flightsize(&self, bytes: usize) {
        self.flightsize
            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
                Some(current.saturating_sub(bytes))
            })
            .ok();
    }

    /// Get LEDBAT congestion control statistics.
    pub fn stats(&self) -> LedbatStats {
        LedbatStats {
            cwnd: self.current_cwnd(),
            flightsize: self.flightsize(),
            queuing_delay: self.queuing_delay(),
            base_delay: self.base_delay(),
            peak_cwnd: self.peak_cwnd(),
            total_increases: self.total_increases.load(Ordering::Relaxed),
            total_decreases: self.total_decreases.load(Ordering::Relaxed),
            total_losses: self.total_losses.load(Ordering::Relaxed),
            min_cwnd_events: self.min_cwnd_events.load(Ordering::Relaxed),
            slow_start_exits: self.slow_start_exits.load(Ordering::Relaxed),
            periodic_slowdowns: self.periodic_slowdowns.load(Ordering::Relaxed),
            ssthresh: self.ssthresh.load(Ordering::Relaxed),
            min_ssthresh_floor: self.calculate_adaptive_floor(),
            total_timeouts: self.total_timeouts.load(Ordering::Relaxed),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::transport::ledbat::config::BASE_HISTORY_SIZE;
    use std::sync::atomic::Ordering;
    use std::time::Duration;

    #[test]
    fn test_ledbat_creation() {
        let controller = LedbatController::new(2848, 2848, 10_000_000);
        assert_eq!(controller.current_cwnd(), 2848);
        assert_eq!(controller.base_delay(), Duration::from_millis(10)); // Fallback
    }

    #[test]
    fn test_ledbat_base_delay_tracking() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        // First RTT sample sets base delay
        controller.on_ack(Duration::from_millis(50), 1000);
        assert_eq!(controller.base_delay(), Duration::from_millis(50));

        // Lower RTT updates base delay
        controller.on_ack(Duration::from_millis(40), 1000);
        assert_eq!(controller.base_delay(), Duration::from_millis(40));

        // Higher RTT doesn't update base delay
        controller.on_ack(Duration::from_millis(60), 1000);
        assert_eq!(controller.base_delay(), Duration::from_millis(40));
    }

    #[tokio::test]
    async fn test_ledbat_increases_when_below_target() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        // Track flightsize to avoid application-limited cap interference
        controller.on_send(20_000); // Send enough to keep flightsize high

        // Set base delay with multiple samples (need 2+ for filter)
        controller.on_ack(Duration::from_millis(10), 1000);
        controller.on_ack(Duration::from_millis(10), 1000);

        let initial_cwnd = controller.current_cwnd();

        // Wait for update interval
        tokio::time::sleep(Duration::from_millis(20)).await;

        // RTT below target (10ms + 20ms = 30ms < 100ms target)
        // Should increase
        controller.on_ack(Duration::from_millis(30), 5000);

        assert!(
            controller.current_cwnd() > initial_cwnd,
            "cwnd should increase when below target (was {}, now {})",
            initial_cwnd,
            controller.current_cwnd()
        );
    }

    #[tokio::test]
    async fn test_ledbat_decreases_when_above_target() {
        // Disable slow start to test pure LEDBAT congestion avoidance
        let config = LedbatConfig {
            initial_cwnd: 100_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            enable_slow_start: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Track flightsize to avoid application-limited cap interference
        controller.on_send(200_000); // High flightsize

        // Set base delay with low RTT samples
        controller.on_ack(Duration::from_millis(10), 1000);
        controller.on_ack(Duration::from_millis(10), 1000);

        // Wait for update interval
        tokio::time::sleep(Duration::from_millis(20)).await;

        // Fill the delay filter with high RTT samples (filter uses MIN over 4 samples)
        // We need enough high-RTT samples to push out the low ones
        for _ in 0..4 {
            controller.on_send(10_000);
            controller.on_ack(Duration::from_millis(160), 2000);
            tokio::time::sleep(Duration::from_millis(15)).await;
        }

        let initial_cwnd = controller.current_cwnd();

        // Wait for update interval
        tokio::time::sleep(Duration::from_millis(20)).await;

        // RTT above target (10ms base + 150ms queuing = 160ms > 100ms target)
        // Now filter has [160, 160, 160, 160], filtered_delay = 160ms
        // queuing_delay = 160ms - 10ms = 150ms > 100ms target
        // off_target = -50ms (negative = decrease)
        controller.on_send(10_000);
        controller.on_ack(Duration::from_millis(160), 5000);

        let new_cwnd = controller.current_cwnd();

        assert!(
            new_cwnd < initial_cwnd,
            "cwnd should decrease when above target (was {}, now {})",
            initial_cwnd,
            new_cwnd
        );
    }

    #[test]
    fn test_ledbat_min_cwnd_enforcement() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        // Set base delay
        controller.on_ack(Duration::from_millis(10), 0); // Just for base delay
        controller.on_ack(Duration::from_millis(10), 0);

        // Very high queuing delay (should decrease aggressively)
        // LEDBAT decreases gradually, need many iterations
        // Keep flightsize valid by sending before each ACK
        for _ in 0..50 {
            controller.on_send(1000);
            controller.on_ack(Duration::from_millis(500), 1000);
            std::thread::sleep(std::time::Duration::from_millis(15)); // Wait for update interval
        }

        // Should have hit min_cwnd (or very close)
        assert!(
            controller.current_cwnd() <= 2848 + 1000,
            "Should be close to min_cwnd, got {}",
            controller.current_cwnd()
        );
    }

    #[test]
    fn test_ledbat_rate_conversion() {
        let controller = LedbatController::new(100_000, 2848, 10_000_000);

        // cwnd = 100KB, RTT = 100ms
        // rate = 100KB / 0.1s = 1000 KB/s = 1 MB/s
        let rate = controller.current_rate(Duration::from_millis(100));
        assert!(
            (rate as i64 - 1_000_000).abs() < 10_000,
            "rate should be ~1 MB/s, got {}",
            rate
        );
    }

    #[test]
    fn test_ledbat_loss_handling() {
        let controller = LedbatController::new(100_000, 2848, 10_000_000);

        let before = controller.current_cwnd();
        controller.on_loss();
        let after = controller.current_cwnd();

        // Should halve (approximately)
        assert!(
            (after as f64) < (before as f64 * 0.6),
            "should halve on loss"
        );
        assert!(
            (after as f64) > (before as f64 * 0.4),
            "should halve on loss"
        );
    }

    #[test]
    fn test_ledbat_timeout_handling() {
        let controller = LedbatController::new(100_000, 2848, 10_000_000);

        controller.on_timeout();

        // Should reset to 1*MSS (or min_cwnd)
        assert_eq!(controller.current_cwnd(), 2848); // max(MSS, min_cwnd)
    }

    #[test]
    fn test_ledbat_flightsize_tracking() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        assert_eq!(controller.flightsize(), 0);

        controller.on_send(5000);
        assert_eq!(controller.flightsize(), 5000);

        controller.on_ack(Duration::from_millis(50), 2000);
        assert_eq!(controller.flightsize(), 3000);

        controller.on_ack(Duration::from_millis(50), 3000);
        assert_eq!(controller.flightsize(), 0);
    }

    #[test]
    fn test_on_ack_without_rtt_decrements_flightsize() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        // Send bytes to track
        controller.on_send(5000);
        assert_eq!(controller.flightsize(), 5000);

        // on_ack_without_rtt should decrement flightsize without updating RTT state
        controller.on_ack_without_rtt(2000);
        assert_eq!(controller.flightsize(), 3000);

        // Should not have updated base_delay (no RTT sample provided)
        // Base delay should still be at the default fallback value
        let base_delay = controller.base_delay();
        assert!(
            base_delay == Duration::from_millis(10) || base_delay.is_zero(),
            "base_delay should be fallback value, got {:?}",
            base_delay
        );

        // Continue decrementing
        controller.on_ack_without_rtt(3000);
        assert_eq!(controller.flightsize(), 0);
    }

    #[test]
    fn test_on_ack_without_rtt_saturates_on_underflow() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        // Send a small amount
        controller.on_send(1000);
        assert_eq!(controller.flightsize(), 1000);

        // ACK more than we sent - should saturate to 0, not wrap
        controller.on_ack_without_rtt(5000);
        assert_eq!(
            controller.flightsize(),
            0,
            "should saturate to 0, not underflow"
        );
    }

    #[tokio::test]
    async fn test_ledbat_cwnd_formula_correctness() {
        // Disable slow start to test pure LEDBAT congestion avoidance formula
        let config = LedbatConfig {
            initial_cwnd: 10_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            enable_slow_start: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Track flightsize for application-limited cap
        controller.on_send(50_000); // Send enough to cover all acks

        // Set base delay with filter (need 2+ samples)
        for _ in 0..4 {
            controller.on_ack(Duration::from_millis(10), 1000);
        }

        // Wait for update interval
        tokio::time::sleep(Duration::from_millis(20)).await;

        // Fill the delay filter with 30ms RTT samples (below target)
        // This establishes a consistent filtered delay
        for _ in 0..4 {
            controller.on_send(5000);
            controller.on_ack(Duration::from_millis(30), 1000);
            tokio::time::sleep(Duration::from_millis(15)).await;
        }

        let initial_cwnd = controller.current_cwnd();

        // Wait for update interval
        tokio::time::sleep(Duration::from_millis(20)).await;

        // RTT below target: 30ms (10ms base + 20ms queuing) < 100ms target
        // Now filter has [30, 30, 30, 30], filtered_delay = 30ms
        // queuing_delay = 30ms - 10ms = 20ms < 100ms target
        // off_target = +80ms (positive = increase)
        // Δcwnd = GAIN * (80/100) * bytes_acked * MSS / cwnd
        controller.on_send(10_000);
        controller.on_ack(Duration::from_millis(30), 5000);

        let new_cwnd = controller.current_cwnd();

        // Should have increased (below target)
        assert!(
            new_cwnd > initial_cwnd,
            "cwnd should increase when below target (was {}, now {})",
            initial_cwnd,
            new_cwnd
        );

        // Verify the change is reasonable (not more than flightsize + ALLOWED_INCREASE)
        let max_allowed = controller.flightsize() + (2 * MSS);
        assert!(
            new_cwnd <= max_allowed,
            "cwnd should be capped by application-limited (cwnd={}, max_allowed={})",
            new_cwnd,
            max_allowed
        );
    }

    #[test]
    fn test_ledbat_delay_filtering() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        // Feed samples with noise: [50ms, 45ms, 55ms, 48ms]
        controller.on_ack(Duration::from_millis(50), 1000);
        controller.on_ack(Duration::from_millis(45), 1000);
        controller.on_ack(Duration::from_millis(55), 1000);
        controller.on_ack(Duration::from_millis(48), 1000);

        // Base delay should be minimum: 45ms
        assert_eq!(controller.base_delay(), Duration::from_millis(45));
    }

    // NOTE: Base delay history bucket rollover test removed - would require
    // 121 seconds of real time. Bucket logic is tested implicitly by other tests.

    #[test]
    fn test_ledbat_stats() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        controller.on_send(5000);
        controller.on_ack(Duration::from_millis(50), 1000);

        let stats = controller.stats();
        assert_eq!(stats.cwnd, 10_000);
        assert_eq!(stats.flightsize, 4000);
        assert_eq!(stats.base_delay, Duration::from_millis(50));
    }

    #[test]
    #[should_panic(expected = "min_cwnd (10000) must be <= initial_cwnd (5000)")]
    fn test_config_validation_min_greater_than_initial() {
        let config = LedbatConfig {
            initial_cwnd: 5_000,
            min_cwnd: 10_000, // min > initial - invalid!
            max_cwnd: 100_000,
            ..Default::default()
        };
        LedbatController::new_with_config(config);
    }

    #[test]
    #[should_panic(expected = "initial_cwnd (150000) must be <= max_cwnd (100000)")]
    fn test_config_validation_initial_greater_than_max() {
        let config = LedbatConfig {
            initial_cwnd: 150_000, // initial > max - invalid!
            min_cwnd: 2_848,
            max_cwnd: 100_000,
            ..Default::default()
        };
        LedbatController::new_with_config(config);
    }

    #[test]
    #[should_panic(expected = "delay_exit_threshold (1.5) must be in range [0.0, 1.0]")]
    fn test_config_validation_threshold_out_of_range() {
        let config = LedbatConfig {
            delay_exit_threshold: 1.5, // > 1.0 - invalid!
            ..Default::default()
        };
        LedbatController::new_with_config(config);
    }

    #[test]
    fn test_config_validation_valid_config() {
        // Should not panic with valid config
        let config = LedbatConfig {
            initial_cwnd: 50_000,
            min_cwnd: 2_848,
            max_cwnd: 100_000,
            delay_exit_threshold: 0.5,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        assert_eq!(controller.current_cwnd(), 50_000);
    }

    #[test]
    fn test_ssthresh_randomization_uses_different_values() {
        // Create multiple controllers with randomization enabled
        // Verify they don't all get the same ssthresh (would indicate poor entropy)
        let mut ssthresh_values = std::collections::HashSet::new();

        for _ in 0..10 {
            let config = LedbatConfig {
                ssthresh: 100_000,
                randomize_ssthresh: true,
                ..Default::default()
            };
            let controller = LedbatController::new_with_config(config);
            // ssthresh is private, but we can observe it indirectly via slow start exit
            // For this test, just verify construction succeeds with randomization
            assert!(controller.current_cwnd() > 0);

            // The jitter should produce values in range [80_000, 120_000]
            // We can't directly observe ssthresh, but at least verify creation works
            ssthresh_values.insert(controller.current_cwnd());
        }

        // With proper randomization, we should see some variation
        // (initial_cwnd is fixed, so this just verifies no crashes with RNG)
        assert!(!ssthresh_values.is_empty());
    }

    /// Stress test: multiple threads calling on_ack concurrently.
    /// Validates lock-free correctness under contention.
    #[test]
    fn test_concurrent_on_ack_stress() {
        use std::sync::Arc;
        use std::thread;

        let controller = Arc::new(LedbatController::new(100_000, 2848, 10_000_000));
        let num_threads = 8;
        let iterations_per_thread = 1000;

        // Pre-send enough data to cover all ACKs
        controller.on_send(num_threads * iterations_per_thread * 1000);

        let handles: Vec<_> = (0..num_threads)
            .map(|i| {
                let controller = Arc::clone(&controller);
                thread::spawn(move || {
                    for j in 0..iterations_per_thread {
                        // Vary RTT to exercise different code paths
                        let rtt_ms = 20 + (i * 10) + (j % 50);
                        controller.on_ack(Duration::from_millis(rtt_ms as u64), 1000);
                    }
                })
            })
            .collect();

        for handle in handles {
            handle.join().expect("Thread panicked");
        }

        // Verify controller is in a valid state
        let stats = controller.stats();
        assert!(stats.cwnd >= controller.min_cwnd);
        assert!(stats.cwnd <= controller.max_cwnd);
        // Base delay should be set (minimum RTT was 20ms)
        assert!(stats.base_delay <= Duration::from_millis(100));
    }

    /// Stress test: concurrent on_send and on_ack from different threads.
    /// Validates flightsize tracking under contention.
    #[test]
    fn test_concurrent_send_ack_stress() {
        use std::sync::Arc;
        use std::thread;

        let controller = Arc::new(LedbatController::new(100_000, 2848, 10_000_000));
        let iterations = 500;

        // Sender thread
        let controller_send = Arc::clone(&controller);
        let sender = thread::spawn(move || {
            for _ in 0..iterations {
                controller_send.on_send(1000);
                thread::yield_now(); // Encourage interleaving
            }
        });

        // ACK thread
        let controller_ack = Arc::clone(&controller);
        let acker = thread::spawn(move || {
            for i in 0..iterations {
                let rtt_ms = 30 + (i % 20);
                controller_ack.on_ack(Duration::from_millis(rtt_ms as u64), 1000);
                thread::yield_now();
            }
        });

        sender.join().expect("Sender panicked");
        acker.join().expect("Acker panicked");

        // Flightsize should be reasonable (may not be exactly 0 due to timing)
        // Just verify no underflow (would wrap to huge value)
        assert!(controller.flightsize() < 1_000_000);
    }

    /// Stress test: concurrent loss events.
    /// Validates cwnd halving under contention.
    #[test]
    fn test_concurrent_loss_stress() {
        use std::sync::Arc;
        use std::thread;

        let controller = Arc::new(LedbatController::new(1_000_000, 2848, 10_000_000));
        let num_threads = 4;

        let handles: Vec<_> = (0..num_threads)
            .map(|_| {
                let controller = Arc::clone(&controller);
                thread::spawn(move || {
                    for _ in 0..10 {
                        controller.on_loss();
                        thread::yield_now();
                    }
                })
            })
            .collect();

        for handle in handles {
            handle.join().expect("Thread panicked");
        }

        // After many loss events, cwnd should be at or near min_cwnd
        assert!(
            controller.current_cwnd() <= 10_000,
            "cwnd should be small after many losses, got {}",
            controller.current_cwnd()
        );
        assert!(controller.current_cwnd() >= controller.min_cwnd);

        // Total losses should be num_threads * 10
        let stats = controller.stats();
        assert_eq!(stats.total_losses, num_threads * 10);
    }

    /// Stress test: concurrent state transitions (timeout vs ACK during slowdown).
    ///
    /// This test verifies that racing `on_timeout` and `on_ack` calls during
    /// slowdown states don't cause panics or invalid states. Due to the
    /// non-atomic nature of compound state transitions, the final state
    /// may vary, but the state machine should always remain valid.
    #[test]
    fn test_concurrent_state_transition_stress() {
        use std::sync::Arc;
        use std::thread;

        let config = LedbatConfig {
            initial_cwnd: 100_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: false, // Start in CongestionAvoidance
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = Arc::new(LedbatController::new_with_config(config));

        // Set up initial state for slowdown cycle
        controller.on_send(500_000);
        controller
            .base_delay_history
            .update(Duration::from_millis(50));

        let num_threads = 4;
        let iterations = 50;

        let handles: Vec<_> = (0..num_threads)
            .map(|i| {
                let controller = Arc::clone(&controller);
                thread::spawn(move || {
                    for j in 0..iterations {
                        // Alternate between timeout and ACK to stress state transitions
                        if (i + j) % 3 == 0 {
                            controller.on_timeout();
                        } else {
                            controller.on_ack(Duration::from_millis(50 + (j % 20) as u64), 1000);
                        }
                        thread::yield_now();
                    }
                })
            })
            .collect();

        for handle in handles {
            handle
                .join()
                .expect("Thread panicked during state transition stress test");
        }

        // Verify state machine is still valid (load should succeed without panic)
        let final_state = controller.congestion_state.load();

        // State should be one of the valid states
        assert!(
            matches!(
                final_state,
                CongestionState::SlowStart
                    | CongestionState::CongestionAvoidance
                    | CongestionState::WaitingForSlowdown
                    | CongestionState::InSlowdown
                    | CongestionState::Frozen
                    | CongestionState::RampingUp
            ),
            "Final state should be valid: {:?}",
            final_state
        );

        // cwnd should be within bounds
        let final_cwnd = controller.current_cwnd();
        assert!(
            final_cwnd >= controller.min_cwnd && final_cwnd <= controller.max_cwnd,
            "cwnd should be within bounds: {}",
            final_cwnd
        );
    }

    /// Test multiplicative decrease cap at -W/2 (LEDBAT++)
    #[tokio::test]
    async fn test_multiplicative_decrease_capped_at_half() {
        // Disable slow start and periodic slowdown to test pure congestion avoidance
        let config = LedbatConfig {
            initial_cwnd: 100_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            enable_slow_start: false,
            enable_periodic_slowdown: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Track flightsize
        controller.on_send(200_000);

        // Set low base delay
        controller.on_ack(Duration::from_millis(10), 1000);
        controller.on_ack(Duration::from_millis(10), 1000);

        // Wait for update interval
        tokio::time::sleep(Duration::from_millis(20)).await;

        // Fill delay filter with very high RTT (extreme congestion)
        for _ in 0..4 {
            controller.on_send(10_000);
            controller.on_ack(Duration::from_millis(500), 2000);
            tokio::time::sleep(Duration::from_millis(15)).await;
        }

        let initial_cwnd = controller.current_cwnd();

        // Wait for update and apply extreme congestion signal
        tokio::time::sleep(Duration::from_millis(20)).await;
        controller.on_send(50_000);
        controller.on_ack(Duration::from_millis(500), 10_000);

        let new_cwnd = controller.current_cwnd();

        // The decrease should be capped at 50% per RTT
        // Even with extreme queuing delay, cwnd should not drop below initial/2
        assert!(
            new_cwnd >= initial_cwnd / 2 - 1000, // Allow some tolerance
            "cwnd decrease should be capped at 50%, was {} -> {} (more than half)",
            initial_cwnd,
            new_cwnd
        );
    }

    /// Test periodic slowdown config option
    #[test]
    fn test_periodic_slowdown_config() {
        // With periodic slowdown enabled (default)
        let config = LedbatConfig::default();
        assert!(
            config.enable_periodic_slowdown,
            "Periodic slowdown should be enabled by default"
        );

        // Can be disabled
        let config = LedbatConfig {
            enable_periodic_slowdown: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        assert!(!controller.enable_periodic_slowdown);
    }

    /// Test congestion state machine initialization
    #[test]
    fn test_congestion_state_initial_state() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        // Initial state should be SlowStart (default config has enable_slow_start=true)
        let state = controller.congestion_state.load();
        assert_eq!(
            state,
            CongestionState::SlowStart,
            "Initial state should be SlowStart when enable_slow_start=true"
        );

        // initial_slow_start_completed should be false
        assert!(
            !controller
                .initial_slow_start_completed
                .load(Ordering::Relaxed),
            "initial_slow_start_completed should be false initially"
        );

        // No slowdown scheduled yet
        let next_slowdown = controller.next_slowdown_time_nanos.load(Ordering::Relaxed);
        assert_eq!(
            next_slowdown,
            u64::MAX,
            "No slowdown should be scheduled initially"
        );
    }

    /// Test that periodic slowdown statistics are tracked
    #[test]
    fn test_periodic_slowdown_stats() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        let stats = controller.stats();
        assert_eq!(
            stats.periodic_slowdowns, 0,
            "Initial periodic_slowdowns should be 0"
        );
    }

    /// Integration test: slow start exit schedules initial slowdown
    #[tokio::test]
    async fn test_slow_start_exit_schedules_slowdown() {
        let config = LedbatConfig {
            initial_cwnd: 15_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 20_000, // Low threshold so we exit slow start quickly
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Send data and establish RTT
        controller.on_send(100_000);

        // Initial samples to set base delay (need 2+ for filter)
        controller.on_ack(Duration::from_millis(20), 1000);
        controller.on_ack(Duration::from_millis(20), 1000);

        // Wait for update interval (based on base_delay ~20ms)
        tokio::time::sleep(Duration::from_millis(25)).await;

        // This ACK should trigger slow start growth: 15000 + 10000 = 25000 >= 20000 ssthresh
        // Should trigger exit check
        controller.on_ack(Duration::from_millis(20), 10_000);

        // Wait for another RTT to ensure the exit check runs
        // (the first ACK after the wait may just do growth, second triggers exit check)
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        // Wait for yet another RTT to make sure the exit is processed
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        // Should have exited slow start (cwnd grew past ssthresh)
        let state = controller.congestion_state.load();
        let cwnd = controller.current_cwnd();
        assert!(
            state != CongestionState::SlowStart,
            "Should have exited slow start (cwnd={}, ssthresh=20000, state={:?})",
            cwnd,
            state
        );

        // Should have marked initial slow start as completed
        assert!(
            controller
                .initial_slow_start_completed
                .load(Ordering::Acquire),
            "initial_slow_start_completed should be true after slow start exit"
        );

        // Slowdown should be scheduled (not u64::MAX)
        let next_slowdown = controller.next_slowdown_time_nanos.load(Ordering::Acquire);
        assert!(
            next_slowdown != u64::MAX,
            "A slowdown should be scheduled after slow start exit"
        );

        // State should be WaitingForSlowdown
        let state = controller.congestion_state.load();
        assert_eq!(
            state,
            CongestionState::WaitingForSlowdown,
            "State should be WaitingForSlowdown"
        );
    }

    /// Test initial slow start ramp-up: cwnd should grow exponentially
    #[tokio::test]
    async fn test_initial_slow_start_ramp_up() {
        let config = LedbatConfig {
            initial_cwnd: 10_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 500_000, // High threshold so we stay in slow start
            enable_slow_start: true,
            enable_periodic_slowdown: false, // Disable to focus on slow start
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        controller.on_send(500_000);

        // Establish base delay
        controller.on_ack(Duration::from_millis(20), 1000);
        controller.on_ack(Duration::from_millis(20), 1000);

        let initial_cwnd = controller.current_cwnd();

        // First RTT: grow by bytes_acked
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 5_000);
        let cwnd_after_1 = controller.current_cwnd();

        // Verify exponential growth (cwnd += bytes_acked)
        assert!(
            cwnd_after_1 > initial_cwnd,
            "cwnd should grow: {} -> {}",
            initial_cwnd,
            cwnd_after_1
        );

        // Second RTT: grow more
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 10_000);
        let cwnd_after_2 = controller.current_cwnd();

        assert!(
            cwnd_after_2 > cwnd_after_1,
            "cwnd should continue growing: {} -> {}",
            cwnd_after_1,
            cwnd_after_2
        );

        // Should still be in slow start
        assert_eq!(
            controller.congestion_state.load(),
            CongestionState::SlowStart,
            "Should still be in slow start (cwnd={}, ssthresh=500000)",
            cwnd_after_2
        );

        println!(
            "Slow start ramp-up: {} -> {} -> {} bytes",
            initial_cwnd, cwnd_after_1, cwnd_after_2
        );
    }

    /// Test proportional slowdown reduction (4x, not to minimum)
    #[tokio::test]
    async fn test_proportional_slowdown_reduction() {
        let config = LedbatConfig {
            initial_cwnd: 320_000, // Start high to see proportional reduction clearly
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 100_000, // Below initial so we exit slow start immediately
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        controller.on_send(500_000);

        // Establish base delay and trigger slow start exit
        controller.on_ack(Duration::from_millis(20), 1000);
        controller.on_ack(Duration::from_millis(20), 1000);

        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        // Should have exited slow start (initial_cwnd > ssthresh)
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        assert!(
            controller.congestion_state.load() != CongestionState::SlowStart,
            "Should have exited slow start"
        );

        // Get cwnd before slowdown (should be ~320KB * 0.9 after slow start exit reduction)
        let cwnd_before_slowdown = controller.current_cwnd();
        println!("cwnd before slowdown: {} bytes", cwnd_before_slowdown);

        // Wait for slowdown to trigger (2 RTTs = 40ms)
        tokio::time::sleep(Duration::from_millis(50)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        // Should now be in slowdown
        let state = controller.congestion_state.load();
        assert!(
            state == CongestionState::InSlowdown || state == CongestionState::Frozen,
            "Should be in slowdown state, got {:?}",
            state
        );

        // Trigger transition to frozen
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        let cwnd_during_slowdown = controller.current_cwnd();
        println!("cwnd during slowdown: {} bytes", cwnd_during_slowdown);

        // Verify proportional reduction: should be pre_slowdown / 16, not min_cwnd
        let pre_slowdown = controller.pre_slowdown_cwnd.load(Ordering::Acquire);
        let expected_slowdown_cwnd = (pre_slowdown / SLOWDOWN_REDUCTION_FACTOR).max(2848);

        assert_eq!(
            cwnd_during_slowdown, expected_slowdown_cwnd,
            "Slowdown cwnd should be pre_slowdown/16 = {}/16 = {}, got {}",
            pre_slowdown, expected_slowdown_cwnd, cwnd_during_slowdown
        );

        // Verify it's NOT just min_cwnd (unless pre_slowdown was very small)
        if pre_slowdown > SLOWDOWN_REDUCTION_FACTOR * 2848 {
            assert!(
                cwnd_during_slowdown > 2848,
                "With large pre_slowdown ({}), slowdown cwnd ({}) should be > min_cwnd (2848)",
                pre_slowdown,
                cwnd_during_slowdown
            );
        }
    }

    /// Test complete slowdown cycle: frozen → ramp-up → normal
    #[tokio::test]
    async fn test_complete_slowdown_cycle() {
        let config = LedbatConfig {
            initial_cwnd: 160_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000, // Exit slow start quickly
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        controller.on_send(1_000_000);

        // Phase 1: Exit slow start
        controller.on_ack(Duration::from_millis(20), 1000);
        controller.on_ack(Duration::from_millis(20), 1000);
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        assert!(
            controller.congestion_state.load() != CongestionState::SlowStart,
            "Should have exited slow start"
        );
        let pre_slowdown_cwnd = controller.current_cwnd();
        println!(
            "Phase 1 - After slow start exit: cwnd = {}",
            pre_slowdown_cwnd
        );

        // Phase 2: Wait for slowdown to trigger (2 RTTs)
        tokio::time::sleep(Duration::from_millis(50)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        let state = controller.congestion_state.load();
        println!("Phase 2 - After wait: state = {:?}", state);

        // Phase 3: Enter frozen state
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        let frozen_cwnd = controller.current_cwnd();
        let state = controller.congestion_state.load();
        println!(
            "Phase 3 - Frozen: cwnd = {}, state = {:?}",
            frozen_cwnd, state
        );

        assert_eq!(state, CongestionState::Frozen, "Should be in Frozen state");

        // Verify proportional reduction
        let expected_frozen = (controller.pre_slowdown_cwnd.load(Ordering::Relaxed)
            / SLOWDOWN_REDUCTION_FACTOR)
            .max(2848);
        assert_eq!(
            frozen_cwnd, expected_frozen,
            "Frozen cwnd should be proportional"
        );

        // Phase 4: Wait for freeze duration (2 RTTs) then start ramp-up
        tokio::time::sleep(Duration::from_millis(50)).await;
        controller.on_ack(Duration::from_millis(20), 5000);

        let state = controller.congestion_state.load();
        println!("Phase 4 - After freeze: state = {:?}", state);

        // Should now be ramping up
        assert_eq!(
            state,
            CongestionState::RampingUp,
            "Should be in RampingUp state"
        );

        // Phase 5: Ramp up until reaching pre_slowdown_cwnd
        let target = controller.pre_slowdown_cwnd.load(Ordering::Acquire);
        let mut ramp_iterations = 0;
        while controller.current_cwnd() < target && ramp_iterations < 20 {
            tokio::time::sleep(Duration::from_millis(25)).await;
            controller.on_ack(Duration::from_millis(20), 20_000);
            ramp_iterations += 1;
        }

        // One more ACK to trigger state transition (state changes after cwnd >= target)
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        let final_cwnd = controller.current_cwnd();
        let final_state = controller.congestion_state.load();
        println!(
            "Phase 5 - After ramp-up: cwnd = {}, state = {:?}, iterations = {}",
            final_cwnd, final_state, ramp_iterations
        );

        // Should have returned to normal
        assert_eq!(
            final_state,
            CongestionState::CongestionAvoidance,
            "Should be back to Normal state"
        );

        // Verify next slowdown is scheduled
        let next_slowdown = controller.next_slowdown_time_nanos.load(Ordering::Acquire);
        assert!(
            next_slowdown != u64::MAX,
            "Next slowdown should be scheduled"
        );

        // Verify slowdown count increased
        let slowdown_count = controller.periodic_slowdowns.load(Ordering::Relaxed);
        assert_eq!(slowdown_count, 1, "Should have completed 1 slowdown");
    }

    /// Test that slowdown interval is 9x the slowdown duration
    #[tokio::test]
    async fn test_slowdown_interval_is_9x_duration() {
        let config = LedbatConfig {
            initial_cwnd: 160_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        controller.on_send(1_000_000);

        // Exit slow start
        for _ in 0..4 {
            controller.on_ack(Duration::from_millis(20), 1000);
            tokio::time::sleep(Duration::from_millis(25)).await;
        }

        // Wait for and complete slowdown
        tokio::time::sleep(Duration::from_millis(50)).await;
        controller.on_ack(Duration::from_millis(20), 1000);

        // Go through frozen phase
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);
        tokio::time::sleep(Duration::from_millis(50)).await;
        controller.on_ack(Duration::from_millis(20), 5000);

        // Ramp up
        let target = controller.pre_slowdown_cwnd.load(Ordering::Acquire);
        while controller.current_cwnd() < target {
            tokio::time::sleep(Duration::from_millis(25)).await;
            controller.on_ack(Duration::from_millis(20), 30_000);
        }

        // Get the slowdown duration and next interval
        let slowdown_duration = controller
            .last_slowdown_duration_nanos
            .load(Ordering::Acquire);
        let now_nanos = controller.elapsed_nanos();
        let next_slowdown = controller.next_slowdown_time_nanos.load(Ordering::Acquire);
        let next_interval = next_slowdown.saturating_sub(now_nanos);

        println!(
            "Slowdown duration: {}ms, Next interval from now: {}ms",
            slowdown_duration / 1_000_000,
            next_interval / 1_000_000
        );

        // The next interval should be approximately 9x the slowdown duration
        // (with some tolerance for timing variations)
        let expected_interval = slowdown_duration * SLOWDOWN_INTERVAL_MULTIPLIER as u64;
        let min_expected = expected_interval / 2; // Allow 50% tolerance

        assert!(
            next_interval >= min_expected || next_interval >= 20_000_000, // or at least 1 RTT
            "Next interval ({}) should be >= 9x slowdown duration ({}) = {}",
            next_interval / 1_000_000,
            slowdown_duration / 1_000_000,
            expected_interval / 1_000_000
        );
    }

    /// Regression test: min_interval should be 18 RTTs, not 1 RTT
    ///
    /// On high-latency paths (100+ms RTT), if slowdown cycles complete quickly
    /// (e.g., ramp-up finishes immediately because cwnd is already at target),
    /// the 9x multiplier on a short duration could be less than 1 RTT.
    ///
    /// BUG: The original code used `min_interval = 1 RTT`, causing slowdowns
    /// every RTT on high-latency paths. With 137ms RTT, this caused 224 slowdowns
    /// in 30 seconds, keeping cwnd perpetually low.
    ///
    /// FIX: min_interval = 9 * freeze_duration = 9 * 2 * RTT = 18 RTTs
    ///
    /// This test verifies the min_interval calculation directly by calling
    /// complete_slowdown with a very short slowdown_duration.
    #[test]
    fn test_min_interval_is_18_rtts_not_1_rtt() {
        let high_latency_rtt = Duration::from_millis(137); // WAN RTT

        let config = LedbatConfig {
            initial_cwnd: 160_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Simulate having just started a slowdown (set phase_start to now)
        let now_nanos = controller.elapsed_nanos();
        controller
            .slowdown_phase_start_nanos
            .store(now_nanos, Ordering::Release);

        // Set base delay history so base_delay() returns the high RTT
        controller.base_delay_history.update(high_latency_rtt);

        // Call complete_slowdown immediately (simulating very short slowdown_duration)
        // This is the scenario that caused the bug
        let completion_nanos = controller.elapsed_nanos();
        controller.complete_slowdown(completion_nanos, high_latency_rtt);

        // Get the scheduled next slowdown time
        let next_slowdown = controller.next_slowdown_time_nanos.load(Ordering::Acquire);
        let next_interval_nanos = next_slowdown.saturating_sub(completion_nanos);
        let next_interval_ms = next_interval_nanos / 1_000_000;

        // Calculate expected minimum: 18 RTTs (9 * 2 * RTT)
        let one_rtt_ms = high_latency_rtt.as_millis() as u64;
        let min_18_rtts_ms =
            one_rtt_ms * SLOWDOWN_FREEZE_RTTS as u64 * SLOWDOWN_INTERVAL_MULTIPLIER as u64;

        let slowdown_duration_ms = controller
            .last_slowdown_duration_nanos
            .load(Ordering::Acquire)
            / 1_000_000;

        println!(
            "High-latency min_interval test: RTT={}ms",
            high_latency_rtt.as_millis()
        );
        println!(
            "  slowdown_duration={}ms (very short, simulated)",
            slowdown_duration_ms
        );
        println!("  next_interval={}ms", next_interval_ms);
        println!("  1 RTT = {}ms (old buggy min)", one_rtt_ms);
        println!("  18 RTTs = {}ms (new correct min)", min_18_rtts_ms);

        // The key assertion: even with a very short slowdown_duration,
        // the min_interval should be 18 RTTs (2466ms), NOT 1 RTT (137ms)
        assert!(
            next_interval_ms >= min_18_rtts_ms - 100, // Allow small tolerance for timing
            "Next interval ({} ms) should be >= 18 RTTs ({} ms). \
         Old code would give ~1 RTT ({} ms), causing slowdowns every RTT!",
            next_interval_ms,
            min_18_rtts_ms,
            one_rtt_ms
        );

        // Also verify it's significantly more than 1 RTT
        assert!(
            next_interval_ms > one_rtt_ms * 10,
            "Next interval ({} ms) should be >> 1 RTT ({} ms)",
            next_interval_ms,
            one_rtt_ms
        );
    }

    /// Regression test: when a scheduled slowdown is skipped because cwnd is too
    /// small, congestion avoidance must still run to allow cwnd to grow.
    ///
    /// Previously, handle_congestion_state() ignored the return value of
    /// start_slowdown(), always returning true even when the slowdown was skipped.
    /// This prevented congestion avoidance from running, causing cwnd to remain
    /// stuck at low values indefinitely.
    ///
    /// This bug caused 21-29 second transfer times on high-latency paths (e.g.,
    /// nova → technic with 137ms RTT) instead of the expected ~7 seconds.
    #[test]
    fn test_skipped_slowdown_allows_congestion_avoidance_to_run() {
        let rtt = Duration::from_millis(137); // High-latency WAN RTT

        let config = LedbatConfig {
            initial_cwnd: 10_000, // Start small, below skip threshold
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: false, // Skip slow start for this test
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Set up state: cwnd is below skip threshold, next slowdown is due NOW
        let now_nanos = controller.elapsed_nanos();
        controller
            .next_slowdown_time_nanos
            .store(now_nanos, Ordering::Release);

        // Set base delay so off_target is positive (no queuing = cwnd should grow)
        controller.base_delay_history.update(rtt);

        // Track flight size for app-limited cap
        controller.on_send(50_000);

        let initial_cwnd = controller.current_cwnd();
        println!("Initial cwnd: {} bytes", initial_cwnd);

        // Verify we're in CongestionAvoidance state
        assert_eq!(
            controller.congestion_state.load(),
            CongestionState::CongestionAvoidance
        );

        // Verify cwnd is below skip threshold (min_cwnd * 4 = 11,392)
        let skip_threshold = controller.min_cwnd * SLOWDOWN_REDUCTION_FACTOR;
        assert!(
            initial_cwnd <= skip_threshold,
            "Test setup: cwnd {} should be <= skip threshold {}",
            initial_cwnd,
            skip_threshold
        );

        // Simulate multiple RTTs worth of ACKs
        // With the bug: cwnd would stay at initial value (congestion avoidance never runs)
        // With the fix: cwnd should grow through congestion avoidance
        //
        // Note: LEDBAT rate-limits cwnd updates to once per RTT, so we need to
        // sleep for at least one RTT between meaningful updates.
        for i in 0..5 {
            // Sleep for one RTT to allow update to proceed (rate-limiting check)
            std::thread::sleep(rtt);

            // Simulate sending more data to maintain flightsize (avoids app-limited cap)
            controller.on_send(5000);

            // ACK with zero queuing delay (below target) -> cwnd should increase
            controller.on_ack(rtt, 5000); // Multiple MSS worth acked

            println!(
                "After {} RTTs: cwnd = {} bytes",
                i + 1,
                controller.current_cwnd()
            );
        }

        let final_cwnd = controller.current_cwnd();
        println!(
            "Final cwnd: {} bytes (started at {})",
            final_cwnd, initial_cwnd
        );

        // Key assertion: cwnd must have grown (congestion avoidance ran)
        // With the bug, cwnd would remain at initial_cwnd
        assert!(
            final_cwnd > initial_cwnd,
            "cwnd should grow when slowdown is skipped and congestion avoidance runs. \
         Initial: {}, Final: {}. If equal, the bug is present!",
            initial_cwnd,
            final_cwnd
        );

        // Verify the slowdown was actually skipped (not started)
        assert_eq!(
            controller.congestion_state.load(),
            CongestionState::CongestionAvoidance,
            "Should remain in CongestionAvoidance when slowdown is skipped"
        );
    }

    /// Test cwnd values at each phase of slowdown
    #[tokio::test]
    async fn test_cwnd_values_through_slowdown_phases() {
        let config = LedbatConfig {
            initial_cwnd: 256_000, // 256KB - nice round number divisible by 16
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 100_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        controller.on_send(1_000_000);

        // Record cwnd at each phase
        let mut cwnd_history: Vec<(String, usize)> = Vec::new();

        cwnd_history.push(("initial".to_string(), controller.current_cwnd()));

        // Exit slow start
        for i in 0..4 {
            controller.on_ack(Duration::from_millis(20), 1000);
            tokio::time::sleep(Duration::from_millis(25)).await;
            if i == 3 {
                cwnd_history.push((
                    "after_slow_start_exit".to_string(),
                    controller.current_cwnd(),
                ));
            }
        }

        // Trigger slowdown
        tokio::time::sleep(Duration::from_millis(50)).await;
        controller.on_ack(Duration::from_millis(20), 1000);
        cwnd_history.push(("slowdown_triggered".to_string(), controller.current_cwnd()));

        // Enter frozen
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 1000);
        cwnd_history.push(("frozen".to_string(), controller.current_cwnd()));

        // Start ramp-up
        tokio::time::sleep(Duration::from_millis(50)).await;
        controller.on_ack(Duration::from_millis(20), 5000);
        cwnd_history.push(("ramp_up_start".to_string(), controller.current_cwnd()));

        // Mid ramp-up
        tokio::time::sleep(Duration::from_millis(25)).await;
        controller.on_ack(Duration::from_millis(20), 20_000);
        cwnd_history.push(("ramp_up_mid".to_string(), controller.current_cwnd()));

        // Complete ramp-up
        let target = controller.pre_slowdown_cwnd.load(Ordering::Acquire);
        while controller.current_cwnd() < target {
            tokio::time::sleep(Duration::from_millis(25)).await;
            controller.on_ack(Duration::from_millis(20), 30_000);
        }
        cwnd_history.push(("ramp_up_complete".to_string(), controller.current_cwnd()));

        // Print cwnd history
        println!("\n=== CWND History Through Slowdown ===");
        for (phase, cwnd) in &cwnd_history {
            println!("  {}: {} KB", phase, cwnd / 1024);
        }
        println!("=====================================\n");

        // Verify key transitions:
        // 1. After slow start exit should be ~90% of initial (0.9 reduction)
        let after_exit = cwnd_history
            .iter()
            .find(|(p, _)| p == "after_slow_start_exit")
            .map(|(_, c)| *c)
            .unwrap();
        assert!(
            after_exit < 256_000,
            "After exit should be reduced from initial"
        );

        // 2. Frozen should be ~1/16 of pre_slowdown
        let frozen = cwnd_history
            .iter()
            .find(|(p, _)| p == "frozen")
            .map(|(_, c)| *c)
            .unwrap();
        let pre_slowdown = controller.pre_slowdown_cwnd.load(Ordering::Relaxed);
        let expected_frozen = (pre_slowdown / SLOWDOWN_REDUCTION_FACTOR).max(2848);
        assert_eq!(
            frozen, expected_frozen,
            "Frozen should be pre_slowdown/{} = {}/{} = {}",
            SLOWDOWN_REDUCTION_FACTOR, pre_slowdown, SLOWDOWN_REDUCTION_FACTOR, expected_frozen
        );

        // 3. Ramp-up complete should be back near pre_slowdown
        let complete = cwnd_history
            .iter()
            .find(|(p, _)| p == "ramp_up_complete")
            .map(|(_, c)| *c)
            .unwrap();
        assert!(
            complete >= pre_slowdown,
            "After ramp-up should be >= pre_slowdown: {} >= {}",
            complete,
            pre_slowdown
        );
    }

    /// Helper to test slowdown behavior at a given RTT
    async fn test_slowdown_at_rtt(rtt_ms: u64) -> (usize, usize, usize, u32) {
        let config = LedbatConfig {
            initial_cwnd: 20_000, // Start small to allow slow start growth
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 150_000, // High threshold so we grow before exit
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        let rtt = Duration::from_millis(rtt_ms);

        controller.on_send(1_000_000);

        // Exit slow start with appropriate timing for this RTT
        // Need more iterations to grow cwnd and hit ssthresh
        for _ in 0..10 {
            controller.on_ack(rtt, 15_000); // Larger ACKs for faster growth
            tokio::time::sleep(rtt + Duration::from_millis(5)).await;
        }
        let post_exit_cwnd = controller.current_cwnd();

        // Wait for slowdown trigger (2 RTTs + margin)
        tokio::time::sleep(rtt * 3).await;
        controller.on_ack(rtt, 1000);

        // Transition through states
        tokio::time::sleep(rtt + Duration::from_millis(5)).await;
        controller.on_ack(rtt, 1000);
        let frozen_cwnd = controller.current_cwnd();

        // Wait for freeze duration (2 RTTs)
        tokio::time::sleep(rtt * 3).await;
        controller.on_ack(rtt, 5000);

        // Ramp up
        let target = controller.pre_slowdown_cwnd.load(Ordering::Acquire);
        let mut ramp_iterations = 0u32;
        while controller.current_cwnd() < target && ramp_iterations < 50 {
            tokio::time::sleep(rtt).await;
            controller.on_ack(rtt, 30_000);
            ramp_iterations += 1;
        }
        let final_cwnd = controller.current_cwnd();

        (post_exit_cwnd, frozen_cwnd, final_cwnd, ramp_iterations)
    }

    /// Parametrized test for slowdown behavior at different RTTs
    /// Tests: 10ms (LAN), 50ms (regional), 100ms (intercontinental), 200ms (satellite), 300ms (high-latency WAN)
    #[rstest::rstest]
    #[case::low_latency_10ms(10, 20)]
    #[case::medium_latency_50ms(50, 30)]
    #[case::high_latency_100ms(100, 40)]
    #[case::very_high_latency_200ms(200, 50)]
    #[case::extreme_latency_300ms(300, 60)]
    #[tokio::test]
    async fn test_slowdown_at_various_latencies(#[case] rtt_ms: u64, #[case] max_iterations: u32) {
        let (post_exit, frozen, final_cwnd, iterations) = test_slowdown_at_rtt(rtt_ms).await;

        println!(
            "{}ms RTT: post_exit={}KB, frozen={}KB, final={}KB, iterations={}",
            rtt_ms,
            post_exit / 1024,
            frozen / 1024,
            final_cwnd / 1024,
            iterations
        );

        // Verify key behaviors:
        // 1. Frozen should be ~1/4 of pre_slowdown (SLOWDOWN_REDUCTION_FACTOR = 4)
        assert!(
            frozen < post_exit / 2,
            "{}ms RTT: Frozen ({}) should be less than half of post_exit ({})",
            rtt_ms,
            frozen,
            post_exit
        );

        // 2. Final cwnd should recover to at least 95% of pre_slowdown
        assert!(
            final_cwnd >= post_exit * 95 / 100,
            "{}ms RTT: Final ({}) should recover to near pre_slowdown ({})",
            rtt_ms,
            final_cwnd,
            post_exit
        );

        // 3. Ramp-up should complete within reasonable iterations for this latency
        assert!(
            iterations < max_iterations,
            "{}ms RTT: Should ramp up within {} iterations, got {}",
            rtt_ms,
            max_iterations,
            iterations
        );
    }

    /// Test dynamic GAIN calculation at different latencies
    #[test]
    fn test_dynamic_gain_across_latencies() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        // GAIN = 1 / min(16, ceil(2 * TARGET / base_delay))
        // TARGET = 60ms

        // 10ms base delay: divisor = ceil(2 * 60 / 10) = 12, GAIN = 1/12
        let gain_10ms = controller.calculate_dynamic_gain(Duration::from_millis(10));
        assert!(
            (gain_10ms - 1.0 / 12.0).abs() < 0.001,
            "10ms: expected 1/12, got {}",
            gain_10ms
        );

        // 5ms base delay: divisor = ceil(2 * 60 / 5) = 24 -> capped at 16, GAIN = 1/16
        let gain_5ms = controller.calculate_dynamic_gain(Duration::from_millis(5));
        assert!(
            (gain_5ms - 1.0 / 16.0).abs() < 0.001,
            "5ms: expected 1/16 (capped), got {}",
            gain_5ms
        );

        // 30ms base delay: divisor = ceil(2 * 60 / 30) = 4, GAIN = 1/4
        let gain_30ms = controller.calculate_dynamic_gain(Duration::from_millis(30));
        assert!(
            (gain_30ms - 1.0 / 4.0).abs() < 0.001,
            "30ms: expected 1/4, got {}",
            gain_30ms
        );

        // 60ms base delay: divisor = ceil(2 * 60 / 60) = 2, GAIN = 1/2
        let gain_60ms = controller.calculate_dynamic_gain(Duration::from_millis(60));
        assert!(
            (gain_60ms - 1.0 / 2.0).abs() < 0.001,
            "60ms: expected 1/2, got {}",
            gain_60ms
        );

        // 120ms base delay: divisor = ceil(2 * 60 / 120) = 1, GAIN = 1/1 = 1
        let gain_120ms = controller.calculate_dynamic_gain(Duration::from_millis(120));
        assert!(
            (gain_120ms - 1.0).abs() < 0.001,
            "120ms: expected 1, got {}",
            gain_120ms
        );

        println!("Dynamic GAIN values:");
        println!("  5ms RTT:   {:.4} (capped at 1/16)", gain_5ms);
        println!("  10ms RTT:  {:.4}", gain_10ms);
        println!("  30ms RTT:  {:.4}", gain_30ms);
        println!("  60ms RTT:  {:.4}", gain_60ms);
        println!("  120ms RTT: {:.4}", gain_120ms);
    }

    // =========================================================================
    // E2E Simulation Tests - Visualize behavior at different latencies
    // =========================================================================
    //
    // These tests simulate data transfers at various RTT latencies and visualize
    // the LEDBAT controller's behavior (cwnd evolution, slow start, etc.).
    //
    // ## Running Larger Transfers
    //
    // The default tests use small transfer sizes (256KB-1MB) to keep execution
    // fast. To test larger transfers for performance analysis:
    //
    // ### Option 1: Modify existing tests temporarily
    // ```rust
    // // Change transfer size from 512KB to 10MB:
    // let (snapshots, duration_ms) = simulate_transfer_sync(50, 10 * 1024, 50);
    // ```
    //
    // ### Option 2: Add custom test cases to the parametrized test
    // ```rust
    // #[case::large_transfer_50ms(50, 10 * 1024)]  // 10MB @ 50ms
    // #[case::huge_transfer_100ms(100, 100 * 1024)] // 100MB @ 100ms
    // ```
    //
    // ### Option 3: Create a dedicated large transfer test (ignored by default)
    // ```rust
    // #[test]
    // #[ignore] // Run with: cargo test --ignored test_large_transfer
    // fn test_large_transfer() {
    //     let (snapshots, duration_ms) = simulate_transfer_sync(100, 50 * 1024, 1000);
    //     // 50MB @ 100ms RTT, snapshot every 1 second
    // }
    // ```
    //
    // ## Execution Time Estimates (real-time sleeps per RTT)
    //
    // | Transfer Size | 10ms RTT | 50ms RTT | 100ms RTT | 200ms RTT |
    // |---------------|----------|----------|-----------|-----------|
    // | 256KB         | ~0.1s    | ~0.2s    | ~0.3s     | ~0.6s     |
    // | 1MB           | ~0.3s    | ~0.5s    | ~0.6s     | ~1.8s     |
    // | 10MB          | ~1s      | ~2s      | ~4s       | ~10s      |
    // | 100MB         | ~8s      | ~20s     | ~40s      | ~100s     |
    //
    // Note: These are rough estimates. Actual time depends on slow start behavior
    // and steady-state throughput which varies with RTT.
    //
    // ## Enabling Periodic Slowdown in Simulations
    //
    // The simulation disables periodic slowdowns by default since they require
    // wall-clock time. To test with slowdowns, set `enable_periodic_slowdown: true`
    // in the config and accept the longer test duration (slowdown cycles are ~9x RTT).

    /// Snapshot of controller state at a point in time
    #[derive(Debug, Clone)]
    #[allow(dead_code)] // Fields used for debugging and analysis in visualization tests
    struct StateSnapshot {
        time_ms: u64,
        cwnd_kb: usize,
        state: CongestionState,
        throughput_mbps: f64,
        data_transferred_kb: usize,
    }

    /// Simulate a transfer and collect state snapshots (synchronous, no real-time delays)
    ///
    /// This uses a controlled test clock to simulate time passing, allowing the test
    /// to run instantly regardless of simulated RTT.
    ///
    /// Note: Since the controller uses wall-clock time for rate limiting, we need to
    /// reset the rate limiter before each simulated RTT to allow updates.
    fn simulate_transfer_sync(
        rtt_ms: u64,
        transfer_size_kb: usize,
        sample_interval_ms: u64,
    ) -> (Vec<StateSnapshot>, u64) {
        let rtt = Duration::from_millis(rtt_ms);
        let config = LedbatConfig {
            initial_cwnd: 38_000, // IW26
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 100_000,
            enable_slow_start: true,
            // Disable periodic slowdown since it depends on wall-clock time
            // See test_complete_slowdown_cycle for slowdown tests
            enable_periodic_slowdown: false,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        let mut snapshots = Vec::new();
        let mut total_data_kb: usize = 0;
        let mut elapsed_ms: u64 = 0;

        // Simulate flightsize - enough to not bottleneck
        controller.on_send(10_000_000);

        // Max simulation time (5 minutes virtual time)
        let max_time_ms = 300_000u64;

        // Prime the delay filter with initial samples
        for _ in 0..4 {
            controller.delay_filter.add_sample(rtt);
        }
        controller.base_delay_history.update(rtt);

        // Simulate transfer - each iteration represents one RTT
        while total_data_kb < transfer_size_kb && elapsed_ms < max_time_ms {
            elapsed_ms += rtt_ms;

            // Wait for at least base_delay so the rate limiter allows updates
            // This is the minimum real-time wait needed for accurate simulation
            std::thread::sleep(rtt);

            // Reset bytes accumulated and set last_update to allow this update
            controller.last_update_nanos.store(0, Ordering::Release);

            // Calculate bytes we can send this RTT (cwnd worth)
            let cwnd = controller.current_cwnd();
            let remaining_bytes = (transfer_size_kb * 1024).saturating_sub(total_data_kb * 1024);
            let bytes_this_rtt = cwnd.min(remaining_bytes);

            if bytes_this_rtt == 0 {
                // Edge case: cwnd too small, simulate at least one packet
                let min_packet = 1464usize;
                controller.on_ack(rtt, min_packet);
                total_data_kb += min_packet / 1024;
            } else {
                // Simulate ACK for sent data - simulate slight queuing delay for realism
                // Use 10% of target delay as typical queuing delay (6ms)
                let queued_rtt = Duration::from_nanos(rtt.as_nanos() as u64 + 6_000_000);
                controller.on_ack(queued_rtt, bytes_this_rtt);
                total_data_kb += bytes_this_rtt / 1024;
            }

            // Take snapshot at intervals
            if elapsed_ms % sample_interval_ms == 0 || elapsed_ms <= rtt_ms * 2 {
                let throughput = if elapsed_ms > 0 {
                    (total_data_kb as f64 * 1024.0 * 8.0)
                        / (elapsed_ms as f64 / 1000.0)
                        / 1_000_000.0
                } else {
                    0.0
                };

                snapshots.push(StateSnapshot {
                    time_ms: elapsed_ms,
                    cwnd_kb: controller.current_cwnd() / 1024,
                    state: controller.congestion_state.load(),
                    throughput_mbps: throughput,
                    data_transferred_kb: total_data_kb,
                });
            }
        }

        (snapshots, elapsed_ms)
    }

    /// Render ASCII throughput chart
    #[allow(dead_code)] // Visualization helper for debugging
    fn render_throughput_chart(snapshots: &[StateSnapshot], max_throughput: f64, width: usize) {
        let height = 10;
        let mut chart = vec![vec![' '; width]; height];

        // Plot data points
        for (i, snap) in snapshots.iter().enumerate() {
            if i >= width {
                break;
            }
            let normalized = (snap.throughput_mbps / max_throughput).min(1.0);
            let row = height - 1 - (normalized * (height - 1) as f64) as usize;
            chart[row][i] = match snap.state {
                CongestionState::SlowStart => '█',
                CongestionState::CongestionAvoidance => '█',
                CongestionState::WaitingForSlowdown => '▓',
                CongestionState::InSlowdown => '▒',
                CongestionState::Frozen => '░',
                CongestionState::RampingUp => '▄',
            };
        }

        // Print chart
        println!("\n    Throughput over time (█=Normal ░=Frozen ▄=RampingUp)");
        println!("    {:.1} Mbps ┤", max_throughput);
        for row in &chart {
            print!("            │");
            for &c in row {
                print!("{}", c);
            }
            println!();
        }
        println!(
            "      0 Mbps ┼{}┬──────────────────────────────────────────►",
            "─".repeat(width.saturating_sub(45))
        );
        println!("              0                                              time");
    }

    /// Render state timeline
    #[allow(dead_code)] // Visualization helper for debugging
    fn render_state_timeline(snapshots: &[StateSnapshot]) {
        fn state_char(state: CongestionState) -> char {
            match state {
                CongestionState::SlowStart | CongestionState::CongestionAvoidance => '─',
                CongestionState::WaitingForSlowdown => '╌',
                CongestionState::InSlowdown => '▼',
                CongestionState::Frozen => '═',
                CongestionState::RampingUp => '╱',
            }
        }

        print!("    State: ");
        let mut last_state: Option<CongestionState> = None;
        for snap in snapshots.iter().take(60) {
            if last_state != Some(snap.state) {
                print!("{}", state_char(snap.state));
                last_state = Some(snap.state);
            } else {
                print!("{}", state_char(snap.state));
            }
        }
        println!();
        println!("           (─=Normal ═=Frozen ╱=RampingUp ▼=Slowdown)");
    }

    /// E2E test: 512KB transfer at 50ms RTT with visualization
    ///
    /// This test uses real-time sleeps to properly test the LEDBAT controller's
    /// slow start behavior. Uses smaller transfer size for faster execution.
    /// See test_complete_slowdown_cycle for slowdown mechanism tests.
    #[test]
    fn test_e2e_transfer_50ms_rtt() {
        println!("\n========== E2E Test: 512KB Transfer @ 50ms RTT ==========");

        let (snapshots, duration_ms) = simulate_transfer_sync(50, 512, 50);

        println!("\n--- Transfer Summary ---");
        println!("RTT:              50ms");
        println!("Transfer size:    512 KB");
        println!(
            "Duration:         {} ms ({:.2}s)",
            duration_ms,
            duration_ms as f64 / 1000.0
        );
        println!(
            "Avg throughput:   {:.2} Mbps",
            (512.0 * 8.0) / (duration_ms as f64 / 1000.0) / 1000.0
        );

        // Print key snapshots showing slow start growth
        println!("\n--- cwnd Evolution ---");
        println!("{:>8} {:>8} {:>10}", "Time(ms)", "cwnd(KB)", "Data(KB)");
        for snap in &snapshots {
            println!(
                "{:>8} {:>8} {:>10}",
                snap.time_ms, snap.cwnd_kb, snap.data_transferred_kb
            );
        }

        // Verify slow start doubled cwnd (initial 37KB should grow)
        let max_cwnd = snapshots.iter().map(|s| s.cwnd_kb).max().unwrap_or(0);
        assert!(
            max_cwnd >= 74,
            "Slow start should double cwnd (37 -> 74+), got max {}KB",
            max_cwnd
        );

        // Verify transfer completed
        let final_data = snapshots.last().map(|s| s.data_transferred_kb).unwrap_or(0);
        assert!(
            final_data >= 512,
            "Should have transferred 512KB, got {}KB",
            final_data
        );
    }

    /// E2E test: 1MB transfer at 200ms RTT with visualization
    #[test]
    fn test_e2e_transfer_200ms_rtt() {
        println!("\n========== E2E Test: 1MB Transfer @ 200ms RTT ==========");

        let (snapshots, duration_ms) = simulate_transfer_sync(200, 1024, 200);

        println!("\n--- Transfer Summary ---");
        println!("RTT:              200ms");
        println!("Transfer size:    1 MB");
        println!(
            "Duration:         {} ms ({:.2}s)",
            duration_ms,
            duration_ms as f64 / 1000.0
        );
        println!(
            "Avg throughput:   {:.2} Mbps",
            (1024.0 * 8.0) / (duration_ms as f64 / 1000.0) / 1000.0
        );

        // Print key snapshots
        println!("\n--- cwnd Evolution ---");
        println!("{:>8} {:>8} {:>10}", "Time(ms)", "cwnd(KB)", "Data(KB)");
        for snap in &snapshots {
            println!(
                "{:>8} {:>8} {:>10}",
                snap.time_ms, snap.cwnd_kb, snap.data_transferred_kb
            );
        }

        // Verify slow start worked
        let max_cwnd = snapshots.iter().map(|s| s.cwnd_kb).max().unwrap_or(0);
        assert!(
            max_cwnd >= 74,
            "Slow start should grow cwnd, got max {}KB",
            max_cwnd
        );

        // Verify transfer completed
        let final_data = snapshots.last().map(|s| s.data_transferred_kb).unwrap_or(0);
        assert!(
            final_data >= 1024,
            "Should have transferred 1MB, got {}KB",
            final_data
        );
    }

    /// Parametrized test for multiple latency scenarios
    ///
    /// Uses small transfer sizes (256KB) to keep test execution fast while
    /// still validating slow start behavior at different RTTs.
    #[rstest::rstest]
    #[case::lan_10ms(10, 256)] // 256KB @ 10ms
    #[case::regional_50ms(50, 256)] // 256KB @ 50ms
    #[case::continental_100ms(100, 256)] // 256KB @ 100ms
    #[case::satellite_200ms(200, 256)] // 256KB @ 200ms
    fn test_e2e_latency_scenarios(#[case] rtt_ms: u64, #[case] size_kb: usize) {
        let (snapshots, duration_ms) = simulate_transfer_sync(rtt_ms, size_kb, rtt_ms);

        let avg_throughput = (size_kb as f64 * 8.0) / (duration_ms as f64 / 1000.0) / 1000.0;

        println!("\n{}ms RTT, {}KB transfer:", rtt_ms, size_kb);
        println!(
            "  Duration: {}ms ({:.2}s)",
            duration_ms,
            duration_ms as f64 / 1000.0
        );
        println!("  Throughput: {:.2} Mbps", avg_throughput);
        println!("  Snapshots: {}", snapshots.len());

        // Verify slow start worked (cwnd should grow from initial 37KB)
        let max_cwnd = snapshots.iter().map(|s| s.cwnd_kb).max().unwrap_or(0);
        assert!(
            max_cwnd >= 74,
            "{}ms RTT: Slow start should grow cwnd to 74KB+, got {}KB",
            rtt_ms,
            max_cwnd
        );

        // Verify we actually transferred the data
        let final_data = snapshots.last().map(|s| s.data_transferred_kb).unwrap_or(0);
        assert!(
            final_data >= size_kb,
            "Should have transferred {}KB, got {}KB",
            size_kb,
            final_data
        );
    }

    /// Test that visualizes cwnd evolution through complete slowdown cycle
    #[tokio::test]
    async fn test_e2e_visualize_slowdown_cycle() {
        println!("\n========== Visualizing Complete Slowdown Cycle ==========");

        let rtt = Duration::from_millis(100);
        let config = LedbatConfig {
            initial_cwnd: 38_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 80_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        controller.on_send(1_000_000);

        let mut timeline: Vec<(u64, usize, &str)> = Vec::new();
        let mut elapsed_ms = 0u64;

        fn state_name(state: CongestionState) -> &'static str {
            match state {
                CongestionState::SlowStart => "SlowStart",
                CongestionState::CongestionAvoidance => "Normal",
                CongestionState::WaitingForSlowdown => "Waiting",
                CongestionState::InSlowdown => "Slowdown",
                CongestionState::Frozen => "Frozen",
                CongestionState::RampingUp => "RampingUp",
            }
        }

        // Phase 1: Slow start
        println!("\n--- Phase 1: Slow Start ---");
        for _ in 0..15 {
            tokio::time::sleep(rtt).await;
            elapsed_ms += 100;
            controller.on_ack(rtt, 20_000);

            let state = controller.congestion_state.load();
            timeline.push((
                elapsed_ms,
                controller.current_cwnd() / 1024,
                state_name(state),
            ));

            if state != CongestionState::SlowStart {
                println!(
                    "  Slow start exit at {}ms, cwnd={}KB",
                    elapsed_ms,
                    controller.current_cwnd() / 1024
                );
                break;
            }
        }

        // Phase 2: Wait for slowdown
        println!("\n--- Phase 2: Waiting for Slowdown ---");
        for _ in 0..10 {
            tokio::time::sleep(rtt).await;
            elapsed_ms += 100;
            controller.on_ack(rtt, 10_000);

            let state = controller.congestion_state.load();
            timeline.push((
                elapsed_ms,
                controller.current_cwnd() / 1024,
                state_name(state),
            ));

            if matches!(
                state,
                CongestionState::InSlowdown | CongestionState::Frozen | CongestionState::RampingUp
            ) {
                println!(
                    "  Slowdown triggered at {}ms, cwnd={}KB",
                    elapsed_ms,
                    controller.current_cwnd() / 1024
                );
                break;
            }
        }

        // Phase 3: Freeze
        println!("\n--- Phase 3: Frozen ---");
        let pre_slowdown_cwnd = controller.pre_slowdown_cwnd.load(Ordering::Relaxed) / 1024;
        for _ in 0..5 {
            tokio::time::sleep(rtt).await;
            elapsed_ms += 100;
            controller.on_ack(rtt, 5_000);

            let state = controller.congestion_state.load();
            timeline.push((
                elapsed_ms,
                controller.current_cwnd() / 1024,
                state_name(state),
            ));

            if state == CongestionState::RampingUp {
                println!(
                    "  Ramp-up started at {}ms, cwnd={}KB",
                    elapsed_ms,
                    controller.current_cwnd() / 1024
                );
                break;
            }
        }

        // Phase 4: Ramp-up
        println!("\n--- Phase 4: Ramp-up ---");
        for _ in 0..10 {
            tokio::time::sleep(rtt).await;
            elapsed_ms += 100;
            controller.on_ack(rtt, 30_000);

            let state = controller.congestion_state.load();
            timeline.push((
                elapsed_ms,
                controller.current_cwnd() / 1024,
                state_name(state),
            ));

            if state == CongestionState::CongestionAvoidance {
                println!(
                    "  Back to normal at {}ms, cwnd={}KB",
                    elapsed_ms,
                    controller.current_cwnd() / 1024
                );
                break;
            }
        }

        // Render timeline
        println!("\n--- cwnd Timeline ---");
        println!("{:>8} {:>10} {:>12}", "Time(ms)", "cwnd(KB)", "State");
        println!("{}", "-".repeat(32));
        for (t, cwnd, state) in &timeline {
            let bar_len = (*cwnd / 10).min(30);
            let bar: String = "█".repeat(bar_len);
            println!("{:>8} {:>10} {:>12} {}", t, cwnd, state, bar);
        }

        // Verify the cycle completed (should be in CongestionAvoidance)
        let final_state = controller.congestion_state.load();
        assert_eq!(
            final_state,
            CongestionState::CongestionAvoidance,
            "Should return to CongestionAvoidance state after slowdown cycle"
        );

        let final_cwnd = controller.current_cwnd();
        assert!(
            final_cwnd >= pre_slowdown_cwnd * 1024 * 95 / 100,
            "Should recover to ~pre_slowdown level: {} >= {}",
            final_cwnd / 1024,
            pre_slowdown_cwnd * 95 / 100
        );

        println!("\n✓ Complete slowdown cycle verified");
        println!("  Pre-slowdown: {}KB", pre_slowdown_cwnd);
        println!(
            "  Frozen: {}KB (expected {}KB)",
            timeline
                .iter()
                .filter(|(_, _, s)| *s == "Frozen")
                .map(|(_, c, _)| *c)
                .next()
                .unwrap_or(0),
            pre_slowdown_cwnd / 4
        );
        println!("  Recovered: {}KB", final_cwnd / 1024);
    }

    /// Regression test: Ramp-up should complete even when queuing_delay > target_delay
    ///
    /// This tests the scenario where a high-latency path has persistent queuing:
    /// - base_delay is measured during low-traffic period (e.g., 50ms)
    /// - During ramp-up, RTT increases due to queuing (e.g., 200ms)
    /// - queuing_delay = 200ms - 50ms = 150ms > target_delay (60ms)
    ///
    /// BUG: The original code would exit ramp-up immediately when queuing_delay > target,
    /// causing cwnd to never recover and triggering rapid repeated slowdowns.
    ///
    /// FIX: Ramp-up should allow cwnd to grow for a minimum duration before checking
    /// queuing_delay, giving the connection time to recover.
    #[tokio::test]
    async fn test_rampup_completes_with_high_queuing_delay() {
        let config = LedbatConfig {
            initial_cwnd: 160_000, // 160KB
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000, // Exit slow start quickly
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        controller.on_send(1_000_000);

        // Phase 1: Establish low base_delay (50ms) during initial slow start
        // This simulates measuring RTT during a quiet period
        let low_rtt = Duration::from_millis(50);
        for _ in 0..4 {
            controller.on_ack(low_rtt, 1000);
            tokio::time::sleep(Duration::from_millis(55)).await;
        }

        // Should have exited slow start
        assert!(
            controller.congestion_state.load() != CongestionState::SlowStart,
            "Should have exited slow start"
        );

        let pre_slowdown_cwnd = controller.current_cwnd();
        println!("Pre-slowdown cwnd: {} bytes", pre_slowdown_cwnd);

        // Phase 2: Trigger slowdown
        tokio::time::sleep(Duration::from_millis(110)).await; // Wait for slowdown to trigger (2 RTTs)
        controller.on_ack(low_rtt, 1000);

        // Phase 3: Go through frozen state
        tokio::time::sleep(Duration::from_millis(55)).await;
        controller.on_ack(low_rtt, 1000);

        assert_eq!(
            controller.congestion_state.load(),
            CongestionState::Frozen,
            "Should be in Frozen state"
        );

        // Phase 4: Wait for freeze to complete, enter RampingUp
        tokio::time::sleep(Duration::from_millis(110)).await; // 2 RTTs for freeze
        controller.on_ack(low_rtt, 5000);

        assert_eq!(
            controller.congestion_state.load(),
            CongestionState::RampingUp,
            "Should be in RampingUp state"
        );

        let rampup_start_cwnd = controller.current_cwnd();
        println!("Ramp-up start cwnd: {} bytes", rampup_start_cwnd);

        // Phase 5: Simulate high RTT during ramp-up (queuing builds up)
        // RTT = 200ms, base_delay = 50ms, so queuing_delay = 150ms >> target (60ms)
        // BUG: Original code would exit ramp-up immediately here
        let high_rtt = Duration::from_millis(200);

        // Send multiple ACKs with high RTT to simulate ramp-up under congestion
        let mut ramp_iterations = 0;
        let target_cwnd = controller.pre_slowdown_cwnd.load(Ordering::Acquire);

        while controller.congestion_state.load() == CongestionState::RampingUp
            && ramp_iterations < 30
        {
            tokio::time::sleep(Duration::from_millis(55)).await;
            controller.on_ack(high_rtt, 20_000);
            ramp_iterations += 1;

            let current_cwnd = controller.current_cwnd();
            println!(
                "Ramp-up iteration {}: cwnd = {} bytes, target = {}",
                ramp_iterations, current_cwnd, target_cwnd
            );
        }

        let final_cwnd = controller.current_cwnd();
        let final_state = controller.congestion_state.load();

        println!(
            "Final: cwnd = {}, state = {:?}, iterations = {}",
            final_cwnd, final_state, ramp_iterations
        );

        // CRITICAL ASSERTION: cwnd should recover to near target_cwnd during ramp-up
        // The bug causes ramp-up to exit early when queuing_delay > target_delay,
        // even though some queuing is expected during ramp-up as cwnd grows.
        //
        // Without the fix: cwnd only reaches ~67% of target before early exit
        // With the fix: cwnd should reach at least 90% of target
        let recovery_ratio = final_cwnd as f64 / target_cwnd as f64;
        assert!(
            recovery_ratio >= 0.90,
            "cwnd should recover to at least 90% of target during ramp-up. \
         Got {} / {} = {:.1}%. This indicates the bug where high queuing_delay \
         causes premature ramp-up exit before cwnd fully recovers.",
            final_cwnd,
            target_cwnd,
            recovery_ratio * 100.0
        );

        // Verify we returned to Normal state (completed the slowdown cycle)
        assert_eq!(
            final_state,
            CongestionState::CongestionAvoidance,
            "Should return to Normal state after ramp-up completes"
        );

        println!("✓ Ramp-up completed successfully despite high queuing_delay");
        println!("  Iterations: {}", ramp_iterations);
        println!(
            "  cwnd growth: {} -> {} bytes ({:.1}x)",
            rampup_start_cwnd,
            final_cwnd,
            final_cwnd as f64 / rampup_start_cwnd as f64
        );
    }

    /// Test ramp-up with small pre_slowdown_cwnd near min_cwnd.
    ///
    /// Edge case: When pre_slowdown_cwnd is close to min_cwnd:
    /// - pre_slowdown_cwnd = 5000 bytes (close to min_cwnd = 2848)
    /// - Frozen at ~15000/4 = 3750 bytes
    /// - 85% threshold = ~12750 bytes
    ///
    /// This tests that the 85% recovery threshold works correctly with a
    /// moderately small pre-slowdown cwnd.
    ///
    /// Note: With Nacho's fix, slowdowns are skipped when cwnd <= min_cwnd * 4 (11392 bytes),
    /// so we need pre_slowdown_cwnd > 11392 for the slowdown to actually fire.
    #[tokio::test]
    async fn test_rampup_with_small_pre_slowdown_cwnd() {
        let min_cwnd = 2848;
        let config = LedbatConfig {
            initial_cwnd: 20_000, // Above skip threshold (min_cwnd * 4 = 11392)
            min_cwnd,
            max_cwnd: 10_000_000,
            ssthresh: 15_000, // Below initial_cwnd so slow start exits immediately
            enable_slow_start: true, // Enable slow start to schedule initial slowdown
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        controller.on_send(100_000);

        // Phase 1: Establish base_delay and exit slow start
        // Slow start will exit immediately since initial_cwnd (20000) > ssthresh (15000)
        let rtt = Duration::from_millis(50);
        for _ in 0..5 {
            controller.on_ack(rtt, 1000);
            tokio::time::sleep(Duration::from_millis(55)).await;
        }

        // May have already progressed to Frozen or RampingUp due to timing
        let state = controller.congestion_state.load();
        assert!(
            state == CongestionState::WaitingForSlowdown
                || state == CongestionState::InSlowdown
                || state == CongestionState::Frozen
                || state == CongestionState::RampingUp,
            "Should be in slowdown cycle, got {:?}",
            state
        );

        let current_cwnd = controller.current_cwnd();
        let slowdown_skip_threshold = min_cwnd * 4; // 11392 bytes
        println!("Current cwnd: {} bytes", current_cwnd);
        println!("min_cwnd: {} bytes", min_cwnd);
        println!("Slowdown skip threshold: {} bytes", slowdown_skip_threshold);
        println!("Current state: {:?}", state);

        // Advance through the slowdown cycle with multiple ACKs
        // The exact state depends on timing, but we should end up in RampingUp or later
        for _ in 0..10 {
            tokio::time::sleep(Duration::from_millis(55)).await;
            controller.on_ack(rtt, 2000);
        }

        let current_state = controller.congestion_state.load();
        let current_cwnd = controller.current_cwnd();
        println!(
            "After slowdown cycle: state={:?}, cwnd={}",
            current_state, current_cwnd
        );

        // cwnd should always be at least min_cwnd
        assert!(current_cwnd >= min_cwnd, "cwnd should be at least min_cwnd");

        // Continue advancing until we're back in CongestionAvoidance or WaitingForSlowdown
        let high_rtt = Duration::from_millis(200);
        let mut iterations = 0;
        while iterations < 30 {
            tokio::time::sleep(Duration::from_millis(55)).await;
            controller.on_ack(high_rtt, 2000);
            iterations += 1;

            let state = controller.congestion_state.load();
            if state == CongestionState::CongestionAvoidance
                || state == CongestionState::WaitingForSlowdown
            {
                break;
            }
        }

        let final_cwnd = controller.current_cwnd();
        let final_state = controller.congestion_state.load();

        println!(
            "Final: cwnd = {}, state = {:?}, iterations = {}",
            final_cwnd, final_state, iterations
        );

        // cwnd should have recovered to a reasonable level
        assert!(
            final_cwnd >= min_cwnd * 2,
            "cwnd should recover to at least 2x min_cwnd, got {}",
            final_cwnd
        );

        // Should be in a stable state
        assert!(
            final_state == CongestionState::CongestionAvoidance
                || final_state == CongestionState::WaitingForSlowdown,
            "Should be in stable state after slowdown cycle, got {:?}",
            final_state
        );

        println!("✓ Small cwnd ramp-up completed successfully");
    }

    /// Regression test: High-latency paths should not have excessive slowdowns
    ///
    /// This test simulates a sustained transfer on a high-latency path (100+ms RTT)
    /// with periodic slowdowns enabled, verifying that:
    /// 1. Slowdowns don't fire every RTT (the bug we fixed)
    /// 2. Total slowdowns are reasonable for the transfer duration
    /// 3. Average cwnd stays reasonable (not stuck at minimum)
    ///
    /// The bug (min_interval = 1 RTT) caused 224 slowdowns in 30s on a 137ms RTT path,
    /// keeping cwnd perpetually low. The fix (min_interval = 18 RTTs) should result
    /// in ~10-15 slowdowns for the same duration.
    #[tokio::test]
    async fn test_high_latency_slowdown_rate() {
        let high_latency_rtt = Duration::from_millis(100); // WAN RTT
        let transfer_duration = Duration::from_secs(10); // Simulate 10 second transfer

        let config = LedbatConfig {
            initial_cwnd: 160_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true, // Critical: enable periodic slowdowns
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        controller.on_send(1_000_000);

        // Exit slow start
        for _ in 0..4 {
            controller.on_ack(high_latency_rtt, 1000);
            tokio::time::sleep(Duration::from_millis(110)).await;
        }

        // Record initial slowdown count (should be 0 or 1 after slow start exit)
        let initial_slowdowns = controller.periodic_slowdowns.load(Ordering::Relaxed);

        // Simulate sustained transfer for transfer_duration
        // Process one ACK per RTT interval
        let start = tokio::time::Instant::now();
        let mut ack_count = 0;
        let mut cwnd_sum: u64 = 0;

        while start.elapsed() < transfer_duration {
            controller.on_ack(high_latency_rtt, 10_000);
            cwnd_sum += controller.current_cwnd() as u64;
            ack_count += 1;
            tokio::time::sleep(high_latency_rtt).await;
        }

        let final_slowdowns = controller.periodic_slowdowns.load(Ordering::Relaxed);
        let slowdowns_during_transfer = final_slowdowns - initial_slowdowns;
        let avg_cwnd = cwnd_sum / ack_count.max(1);
        let elapsed = start.elapsed();

        println!("High-latency slowdown rate test:");
        println!("  RTT: {}ms", high_latency_rtt.as_millis());
        println!("  Duration: {:.1}s", elapsed.as_secs_f64());
        println!("  ACKs processed: {}", ack_count);
        println!(
            "  Slowdowns: {} (initial: {}, final: {})",
            slowdowns_during_transfer, initial_slowdowns, final_slowdowns
        );
        println!("  Avg cwnd: {} KB", avg_cwnd / 1024);

        // With 18 RTT min_interval (1.8s at 100ms RTT), expect ~5-6 slowdowns in 10s
        // With buggy 1 RTT min_interval, we'd see ~100 slowdowns in 10s
        let max_expected_slowdowns = (elapsed.as_secs_f64() / 1.5) as usize + 5; // ~1 per 1.5s + margin
        let min_buggy_slowdowns = (elapsed.as_secs_f64() * 8.0) as usize; // ~8 per second with bug

        assert!(
            slowdowns_during_transfer < max_expected_slowdowns,
            "Too many slowdowns ({}) in {:.1}s. Expected < {} with fixed min_interval. \
         This many slowdowns suggests the 1-RTT min_interval bug is present.",
            slowdowns_during_transfer,
            elapsed.as_secs_f64(),
            max_expected_slowdowns
        );

        // Also verify we didn't have the buggy behavior
        assert!(
            slowdowns_during_transfer < min_buggy_slowdowns / 2,
            "Slowdowns ({}) are suspiciously high (buggy behavior would show {}). \
         Verify min_interval is 18 RTTs, not 1 RTT.",
            slowdowns_during_transfer,
            min_buggy_slowdowns
        );

        // Verify cwnd didn't collapse
        assert!(
            avg_cwnd > 20_000,
            "Average cwnd ({} bytes) is too low. Expected > 20KB. \
         This suggests cwnd was stuck low due to excessive slowdowns.",
            avg_cwnd
        );
    }

    // =========================================================================
    // Calculation-Only Tests - Verify formulas at extreme RTTs (instant)
    // =========================================================================

    /// Test dynamic GAIN calculation at extreme RTTs (1ms datacenter, 500ms satellite)
    ///
    /// Verifies the GAIN formula handles edge cases:
    /// - Very low RTT: GAIN should be capped at 1/16 (conservative)
    /// - Very high RTT: GAIN should be 1.0 (aggressive to utilize bandwidth)
    #[test]
    fn test_dynamic_gain_extreme_latencies() {
        let controller = LedbatController::new(10_000, 2848, 10_000_000);

        // GAIN = 1 / min(16, ceil(2 * TARGET / base_delay))
        // TARGET = 60ms

        // 1ms base delay (datacenter): divisor = ceil(2 * 60 / 1) = 120 -> capped at 16
        let gain_1ms = controller.calculate_dynamic_gain(Duration::from_millis(1));
        assert!(
            (gain_1ms - 1.0 / 16.0).abs() < 0.001,
            "1ms: expected 1/16 (capped), got {}",
            gain_1ms
        );

        // 500ms base delay (satellite): divisor = ceil(2 * 60 / 500) = 1, GAIN = 1
        let gain_500ms = controller.calculate_dynamic_gain(Duration::from_millis(500));
        assert!(
            (gain_500ms - 1.0).abs() < 0.001,
            "500ms: expected 1.0, got {}",
            gain_500ms
        );

        // 300ms base delay: divisor = ceil(2 * 60 / 300) = 1, GAIN = 1
        let gain_300ms = controller.calculate_dynamic_gain(Duration::from_millis(300));
        assert!(
            (gain_300ms - 1.0).abs() < 0.001,
            "300ms: expected 1.0, got {}",
            gain_300ms
        );

        // Verify GAIN is always in valid range [1/16, 1]
        for rtt_ms in [1, 2, 5, 10, 50, 100, 200, 300, 500, 1000] {
            let gain = controller.calculate_dynamic_gain(Duration::from_millis(rtt_ms));
            assert!(
                (1.0 / 16.0..=1.0).contains(&gain),
                "{}ms: GAIN {} out of valid range [1/16, 1]",
                rtt_ms,
                gain
            );
        }
    }

    /// Test min_interval calculation at extreme RTTs
    ///
    /// Verifies the 18 RTT minimum is correctly applied at both extremes:
    /// - 1ms RTT: min_interval = 18ms
    /// - 500ms RTT: min_interval = 9000ms (9 seconds)
    #[test]
    fn test_min_interval_extreme_latencies() {
        for (rtt_ms, expected_min_ms) in [(1, 18), (5, 90), (500, 9000)] {
            let rtt = Duration::from_millis(rtt_ms);

            let config = LedbatConfig {
                initial_cwnd: 160_000,
                min_cwnd: 2848,
                max_cwnd: 10_000_000,
                ssthresh: 50_000,
                enable_slow_start: true,
                enable_periodic_slowdown: true,
                randomize_ssthresh: false,
                ..Default::default()
            };
            let controller = LedbatController::new_with_config(config);

            // Set up base delay
            controller.base_delay_history.update(rtt);

            // Simulate immediate slowdown completion
            let now_nanos = controller.elapsed_nanos();
            controller
                .slowdown_phase_start_nanos
                .store(now_nanos, Ordering::Release);

            let completion_nanos = controller.elapsed_nanos();
            controller.complete_slowdown(completion_nanos, rtt);

            let next_slowdown = controller.next_slowdown_time_nanos.load(Ordering::Acquire);
            let next_interval_ms = next_slowdown.saturating_sub(completion_nanos) / 1_000_000;

            // Allow some tolerance for timing
            assert!(
                next_interval_ms >= expected_min_ms - 5,
                "{}ms RTT: min_interval ({} ms) should be >= {} ms (18 RTTs)",
                rtt_ms,
                next_interval_ms,
                expected_min_ms
            );

            println!(
                "  {}ms RTT: min_interval = {}ms (expected >= {}ms)",
                rtt_ms, next_interval_ms, expected_min_ms
            );
        }
    }

    /// Test rate conversion at extreme RTTs
    ///
    /// Verifies the rate = cwnd / RTT formula handles edge cases:
    /// - Very low RTT (sub-millisecond): should be capped to 1ms minimum
    /// - Very high RTT: rate should decrease proportionally
    #[test]
    fn test_rate_conversion_extreme_latencies() {
        let controller = LedbatController::new(100_000, 2848, 10_000_000); // 100KB cwnd

        // Sub-millisecond RTT should be capped to 1ms to prevent unrealistic rates
        // current_rate enforces 1ms minimum
        let rate_submillis = controller.current_rate(Duration::from_micros(100));
        let rate_1ms = controller.current_rate(Duration::from_millis(1));

        // Both should give same result due to 1ms floor
        assert_eq!(
            rate_submillis, rate_1ms,
            "Sub-millisecond RTT should be capped to 1ms"
        );

        // Rate at 1ms: 100KB / 0.001s = 100 MB/s = 100_000_000 bytes/s
        assert!(
            (90_000_000..=110_000_000).contains(&rate_1ms),
            "1ms RTT rate should be ~100 MB/s, got {} bytes/s",
            rate_1ms
        );

        // Rate at 500ms: 100KB / 0.5s = 200 KB/s = 200_000 bytes/s
        let rate_500ms = controller.current_rate(Duration::from_millis(500));
        assert!(
            (190_000..=210_000).contains(&rate_500ms),
            "500ms RTT rate should be ~200 KB/s, got {} bytes/s",
            rate_500ms
        );

        // Rate should scale inversely with RTT
        let rate_100ms = controller.current_rate(Duration::from_millis(100));
        let rate_200ms = controller.current_rate(Duration::from_millis(200));
        assert!(
            rate_100ms > rate_200ms * 19 / 10, // Should be ~2x, allow some tolerance
            "Rate at 100ms ({}) should be ~2x rate at 200ms ({})",
            rate_100ms,
            rate_200ms
        );
    }

    // =========================================================================
    // State Machine Edge Case Tests - Verify behavior under adverse conditions
    // =========================================================================

    /// Test that loss during Frozen state doesn't corrupt state machine
    ///
    /// When packet loss occurs during the Frozen phase:
    /// - cwnd should be halved (standard loss response)
    /// - State should remain Frozen (not reset to Normal)
    /// - Slowdown cycle should continue normally after freeze completes
    #[test]
    fn test_loss_during_frozen_state() {
        let config = LedbatConfig {
            initial_cwnd: 100_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Manually set state to Frozen
        controller.congestion_state.enter_frozen();
        controller.cwnd.store(50_000, Ordering::Release); // 50KB during freeze

        let cwnd_before_loss = controller.current_cwnd();
        let state_before_loss = controller.congestion_state.load();

        // Trigger packet loss
        controller.on_loss();

        let cwnd_after_loss = controller.current_cwnd();
        let state_after_loss = controller.congestion_state.load();

        // cwnd should be halved
        assert_eq!(
            cwnd_after_loss,
            (cwnd_before_loss / 2).max(controller.min_cwnd),
            "cwnd should be halved on loss"
        );

        // State should remain Frozen (loss doesn't reset state machine)
        assert_eq!(
            state_before_loss, state_after_loss,
            "Slowdown state should not change on loss (was {:?}, now {:?})",
            state_before_loss, state_after_loss
        );

        // Loss counter should increment
        assert_eq!(
            controller.total_losses.load(Ordering::Relaxed),
            1,
            "Loss counter should increment"
        );
    }

    /// Test that timeout during RampingUp state resets appropriately
    ///
    /// When timeout occurs during RampingUp:
    /// - cwnd should reset to min_cwnd (severe congestion response)
    /// - State remains RampingUp (timeout doesn't change slowdown state)
    /// - Slow start flag should remain true (for fast recovery after timeout)
    ///
    /// Note: Prior to the slow-start-reentry fix, timeout would exit slow start,
    /// leaving the connection stuck in slow linear growth. Now timeout re-enters
    /// slow start for fast exponential recovery.
    #[test]
    fn test_timeout_during_rampup_state() {
        let config = LedbatConfig {
            initial_cwnd: 100_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Manually set state to RampingUp
        controller.congestion_state.enter_ramping_up();
        let pre_timeout_cwnd = 80_000;
        controller.cwnd.store(pre_timeout_cwnd, Ordering::Release); // Mid-rampup

        // Trigger timeout
        controller.on_timeout();

        let cwnd_after = controller.current_cwnd();
        let state_after = controller.congestion_state.load();
        let ssthresh_after = controller.ssthresh.load(Ordering::Acquire);

        // cwnd should reset to min_cwnd (or MSS, whichever is larger)
        assert!(
            cwnd_after <= controller.min_cwnd.max(MSS),
            "cwnd should reset to min on timeout, got {}",
            cwnd_after
        );

        // With unified state machine, timeout always transitions to SlowStart
        // for fast exponential recovery
        assert_eq!(
            state_after,
            CongestionState::SlowStart,
            "Should transition to SlowStart on timeout for fast recovery"
        );

        // ssthresh should be set to half the pre-timeout cwnd
        let expected_ssthresh = pre_timeout_cwnd / 2;
        assert_eq!(
            ssthresh_after, expected_ssthresh,
            "ssthresh should be half the pre-timeout cwnd ({}/2 = {})",
            pre_timeout_cwnd, expected_ssthresh
        );
    }

    /// Test that timeout during Frozen state resets appropriately
    ///
    /// When timeout occurs during Frozen state (part of slowdown cycle):
    /// - cwnd should reset to min_cwnd (severe congestion response)
    /// - State should transition to SlowStart for fast recovery
    /// - ssthresh should be set to half the pre-timeout cwnd
    ///
    /// This is a regression test for issue #2559 which unified the state machine.
    /// Previously, overlapping flags could cause inconsistent behavior.
    #[test]
    fn test_timeout_during_frozen_state() {
        let config = LedbatConfig {
            initial_cwnd: 100_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Manually set state to Frozen (simulating mid-slowdown)
        controller.congestion_state.enter_frozen();
        let pre_timeout_cwnd = 60_000;
        controller.cwnd.store(pre_timeout_cwnd, Ordering::Release); // Mid-freeze cwnd

        // Trigger timeout
        controller.on_timeout();

        let cwnd_after = controller.current_cwnd();
        let state_after = controller.congestion_state.load();
        let ssthresh_after = controller.ssthresh.load(Ordering::Acquire);

        // cwnd should reset to min_cwnd (or MSS, whichever is larger)
        assert!(
            cwnd_after <= controller.min_cwnd.max(MSS),
            "cwnd should reset to min on timeout, got {}",
            cwnd_after
        );

        // With unified state machine, timeout always transitions to SlowStart
        // This cleanly exits the Frozen state and enables fast recovery
        assert_eq!(
            state_after,
            CongestionState::SlowStart,
            "Should transition to SlowStart on timeout for fast recovery"
        );

        // ssthresh should be set to half the pre-timeout cwnd
        let expected_ssthresh = pre_timeout_cwnd / 2;
        assert_eq!(
            ssthresh_after, expected_ssthresh,
            "ssthresh should be half the pre-timeout cwnd ({}/2 = {})",
            pre_timeout_cwnd, expected_ssthresh
        );
    }

    /// Test that timeout during WaitingForSlowdown state resets appropriately
    ///
    /// When timeout occurs while waiting for the next scheduled slowdown:
    /// - cwnd should reset to min_cwnd
    /// - State should transition to SlowStart for fast recovery
    ///
    /// This ensures the unified state machine handles all slowdown-related states.
    #[test]
    fn test_timeout_during_waiting_for_slowdown_state() {
        let config = LedbatConfig {
            initial_cwnd: 100_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Manually set state to WaitingForSlowdown
        controller.congestion_state.enter_waiting_for_slowdown();
        controller.cwnd.store(90_000, Ordering::Release);

        // Trigger timeout
        controller.on_timeout();

        let cwnd_after = controller.current_cwnd();
        let state_after = controller.congestion_state.load();

        // cwnd should reset to min_cwnd
        assert!(
            cwnd_after <= controller.min_cwnd.max(MSS),
            "cwnd should reset to min on timeout, got {}",
            cwnd_after
        );

        // Should transition to SlowStart
        assert_eq!(
            state_after,
            CongestionState::SlowStart,
            "Should transition to SlowStart on timeout"
        );
    }

    /// Test state machine integrity: all states are reachable and valid
    #[test]
    fn test_congestion_state_machine_integrity() {
        // Verify all state values are unique and in valid range
        let states = [
            (CongestionState::SlowStart, 0u8),
            (CongestionState::CongestionAvoidance, 1u8),
            (CongestionState::WaitingForSlowdown, 2u8),
            (CongestionState::InSlowdown, 3u8),
            (CongestionState::Frozen, 4u8),
            (CongestionState::RampingUp, 5u8),
        ];

        for (state, expected_value) in states {
            assert_eq!(
                state as u8, expected_value,
                "State {:?} should have value {}",
                state, expected_value
            );
        }

        // Verify from_u8 returns None for invalid values
        assert!(
            CongestionState::from_u8(6).is_none(),
            "from_u8(6) should return None"
        );
        assert!(
            CongestionState::from_u8(100).is_none(),
            "from_u8(100) should return None"
        );
        assert!(
            CongestionState::from_u8(255).is_none(),
            "from_u8(255) should return None"
        );

        // Verify from_u8 works for valid values
        for (expected_state, value) in states {
            assert_eq!(
                CongestionState::from_u8(value),
                Some(expected_state),
                "from_u8({}) should return Some({:?})",
                value,
                expected_state
            );
        }

        // Verify controller starts in Normal state
        // Default config has enable_slow_start=true, so starts in SlowStart
        let config = LedbatConfig::default();
        let controller = LedbatController::new_with_config(config);
        assert_eq!(
            controller.congestion_state.load(),
            CongestionState::SlowStart,
            "Controller should start in SlowStart state when enable_slow_start=true"
        );

        // With slow start disabled, should start in CongestionAvoidance
        let config_no_ss = LedbatConfig {
            enable_slow_start: false,
            ..LedbatConfig::default()
        };
        let controller_no_ss = LedbatController::new_with_config(config_no_ss);
        assert_eq!(
            controller_no_ss.congestion_state.load(),
            CongestionState::CongestionAvoidance,
            "Controller should start in CongestionAvoidance state when enable_slow_start=false"
        );
    }

    /// Test that multiple rapid losses don't cause cwnd underflow
    #[test]
    fn test_rapid_losses_no_underflow() {
        let config = LedbatConfig {
            initial_cwnd: 10_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Simulate 20 rapid losses (each halves cwnd)
        for i in 0..20 {
            let cwnd_before = controller.current_cwnd();
            controller.on_loss();
            let cwnd_after = controller.current_cwnd();

            // cwnd should never go below min_cwnd
            assert!(
                cwnd_after >= controller.min_cwnd,
                "Iteration {}: cwnd {} went below min_cwnd {}",
                i,
                cwnd_after,
                controller.min_cwnd
            );

            // cwnd should either halve or stay at min
            if cwnd_before > controller.min_cwnd * 2 {
                assert_eq!(
                    cwnd_after,
                    cwnd_before / 2,
                    "Iteration {}: cwnd should halve",
                    i
                );
            } else {
                assert!(
                    cwnd_after >= controller.min_cwnd,
                    "Iteration {}: cwnd should stay at min",
                    i
                );
            }
        }

        // Final cwnd should be at min_cwnd
        assert_eq!(
            controller.current_cwnd(),
            controller.min_cwnd,
            "After many losses, cwnd should be at min_cwnd"
        );

        // Loss counter should be accurate
        assert_eq!(
            controller.total_losses.load(Ordering::Relaxed),
            20,
            "Should have recorded 20 losses"
        );
    }

    /// Test base delay tracking with jittery RTT samples
    ///
    /// Simulates RTT jitter (±30%) and verifies base_delay tracks the minimum
    #[test]
    fn test_base_delay_with_jitter() {
        let config = LedbatConfig::default();
        let controller = LedbatController::new_with_config(config);

        let base_rtt_ms = 100u64;
        let jitter_pct = 30u64; // ±30%

        // Add jittery samples
        let samples = [
            base_rtt_ms,                      // 100ms (base)
            base_rtt_ms + jitter_pct,         // 130ms (+30%)
            base_rtt_ms - jitter_pct / 2,     // 85ms  (-15%)
            base_rtt_ms + jitter_pct / 2,     // 115ms (+15%)
            base_rtt_ms - jitter_pct,         // 70ms  (-30%) - this is the min
            base_rtt_ms,                      // 100ms
            base_rtt_ms + jitter_pct * 2 / 3, // 120ms
        ];

        for &rtt_ms in &samples {
            controller
                .base_delay_history
                .update(Duration::from_millis(rtt_ms));
        }

        let measured_base = controller.base_delay();
        let expected_min = Duration::from_millis(70); // Minimum sample

        assert_eq!(
            measured_base, expected_min,
            "Base delay should track minimum: expected {:?}, got {:?}",
            expected_min, measured_base
        );
    }

    /// Test that slowdown doesn't occur when disabled
    #[test]
    fn test_slowdown_disabled() {
        let config = LedbatConfig {
            initial_cwnd: 100_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: false, // Disabled to start in CongestionAvoidance
            enable_periodic_slowdown: false, // Disabled!
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // State should be CongestionAvoidance (slow start disabled)
        assert_eq!(
            controller.congestion_state.load(),
            CongestionState::CongestionAvoidance,
            "Initial state should be CongestionAvoidance"
        );

        // Schedule a slowdown manually and verify it's not processed
        controller
            .next_slowdown_time_nanos
            .store(0, Ordering::Release); // Due now

        // Process some ACKs
        controller.on_send(100_000);
        for _ in 0..10 {
            controller
                .base_delay_history
                .update(Duration::from_millis(50));
            controller.on_ack(Duration::from_millis(50), 5000);
        }

        // Slowdown counter should remain 0
        assert_eq!(
            controller.periodic_slowdowns.load(Ordering::Relaxed),
            0,
            "No slowdowns should occur when disabled"
        );

        // State should still be CongestionAvoidance
        let state = controller.congestion_state.load();
        assert_eq!(
            state,
            CongestionState::CongestionAvoidance,
            "State should remain CongestionAvoidance when slowdowns disabled"
        );
    }

    /// Test ramp-up completes correctly with RTT jitter
    ///
    /// Simulates a network path with ±30% RTT jitter during the ramp-up phase
    /// of a slowdown cycle and verifies:
    /// 1. State machine transitions from Frozen -> RampingUp -> Normal
    /// 2. cwnd recovers to target despite variable RTT
    /// 3. base_delay tracks the minimum RTT correctly
    ///
    /// This catches issues where variable RTT could:
    /// - Cause premature ramp-up exit (the bug from PR #2510)
    /// - Corrupt base_delay measurements
    /// - Prevent cwnd recovery
    #[tokio::test]
    async fn test_slowdown_rampup_with_rtt_jitter() {
        let base_rtt_ms = 50u64;

        let config = LedbatConfig {
            initial_cwnd: 20_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 150_000,
            enable_slow_start: true,
            enable_periodic_slowdown: true,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Helper to generate jittery RTT (±30% deterministic pattern)
        let jittery_rtt = |iteration: u64| -> Duration {
            let offsets_ms: [i64; 10] = [-15, 8, -5, 12, -10, 3, 15, -8, 5, -3];
            let offset = offsets_ms[(iteration % 10) as usize];
            let rtt_ms = (base_rtt_ms as i64 + offset).max(20) as u64;
            Duration::from_millis(rtt_ms)
        };

        controller.on_send(1_000_000);

        // Phase 1: Initialize with jittery RTT samples via on_ack (proper way)
        // This populates both delay_filter and base_delay_history
        for i in 0..4 {
            let rtt = jittery_rtt(i);
            controller.on_ack(rtt, 5_000);
            tokio::time::sleep(Duration::from_millis(20)).await;
        }

        // Phase 2: Exit slow start and enter slowdown cycle
        // Add high-RTT samples to trigger slow start exit
        for i in 4..8 {
            let rtt = jittery_rtt(i) + Duration::from_millis(50); // Add queuing
            controller.on_ack(rtt, 10_000);
            tokio::time::sleep(Duration::from_millis(20)).await;
        }

        let pre_slowdown_cwnd = controller.current_cwnd();
        let base_delay = controller.base_delay();

        println!("After slow start exit:");
        println!("  cwnd: {} KB", pre_slowdown_cwnd / 1024);
        println!("  base_delay: {:?}", base_delay);

        // Phase 3: Wait for slowdown to trigger and complete frozen phase
        // Process ACKs until we see the ramp-up state
        let mut iteration = 8u64;
        let mut found_rampup = false;

        for _ in 0..20 {
            let rtt = jittery_rtt(iteration);
            iteration += 1;
            tokio::time::sleep(Duration::from_millis(base_rtt_ms)).await;
            controller.on_ack(rtt, 10_000);

            let state = controller.congestion_state.load();
            if state == CongestionState::RampingUp {
                found_rampup = true;
                println!("Entered RampingUp state at iteration {}", iteration);
                break;
            }
        }

        // If we didn't naturally hit ramp-up, manually set it up for testing
        if !found_rampup {
            println!("Manually triggering ramp-up state for test");
            controller.congestion_state.enter_ramping_up();
            let target = controller.current_cwnd().max(80_000);
            controller
                .pre_slowdown_cwnd
                .store(target, Ordering::Release);
            controller.cwnd.store(target / 4, Ordering::Release);
        }

        let rampup_start_cwnd = controller.current_cwnd();
        let target_cwnd = controller.pre_slowdown_cwnd.load(Ordering::Acquire);

        println!("\nRamp-up starting:");
        println!("  cwnd: {} KB", rampup_start_cwnd / 1024);
        println!("  target: {} KB", target_cwnd / 1024);

        // Phase 4: Go through ramp-up with jittery RTT
        let mut ramp_iterations = 0u32;
        let max_ramp_iterations = 15u32;

        while controller.congestion_state.load() == CongestionState::RampingUp
            && ramp_iterations < max_ramp_iterations
        {
            let rtt = jittery_rtt(iteration);
            iteration += 1;
            ramp_iterations += 1;

            tokio::time::sleep(Duration::from_millis(base_rtt_ms)).await;
            controller.on_ack(rtt, 20_000);
        }

        let final_cwnd = controller.current_cwnd();
        let final_state = controller.congestion_state.load();
        let final_base_delay = controller.base_delay();

        println!("\nFinal state:");
        println!(
            "  cwnd: {} KB (target: {} KB)",
            final_cwnd / 1024,
            target_cwnd / 1024
        );
        println!(
            "  state: {:?} (SlowStart, CongestionAvoidance, RampingUp, etc.)",
            final_state
        );
        println!("  base_delay: {:?}", final_base_delay);
        println!("  ramp_iterations: {}", ramp_iterations);

        // Verify cwnd grew during ramp-up (even if not fully complete)
        assert!(
            final_cwnd > rampup_start_cwnd,
            "cwnd should grow during ramp-up: {} -> {}",
            rampup_start_cwnd,
            final_cwnd
        );

        // Verify base_delay is sensible (should track minimum from jittery samples)
        // Minimum jittered RTT is base_rtt - 15ms = 35ms
        let min_expected = Duration::from_millis(30);
        let max_expected = Duration::from_millis(70);
        assert!(
            final_base_delay >= min_expected && final_base_delay <= max_expected,
            "base_delay {:?} should be in range [{:?}, {:?}]",
            final_base_delay,
            min_expected,
            max_expected
        );
    }

    /// Regression test for slow start re-entry after timeout.
    ///
    /// Issue: After a retransmission timeout, on_timeout() resets cwnd to min_cwnd
    /// and sets in_slow_start = false. This puts the connection in congestion
    /// avoidance mode with minimum cwnd. On high-RTT paths with zero queuing,
    /// congestion avoidance growth is extremely slow (~188 bytes per 264ms RTT),
    /// causing transfers to stall for tens of seconds.
    ///
    /// The fix: Re-enter slow start after timeout to enable exponential recovery.
    ///
    /// This test verifies that after a timeout, cwnd can recover quickly (via
    /// slow start exponential growth) rather than being stuck in slow linear
    /// congestion avoidance growth.
    #[tokio::test]
    async fn test_timeout_reenters_slow_start_for_fast_recovery() {
        let config = LedbatConfig {
            initial_cwnd: 50_000,
            min_cwnd: 2_848,
            max_cwnd: 10_000_000,
            ssthresh: 100_000,
            enable_slow_start: true,
            enable_periodic_slowdown: false, // Disable to isolate timeout behavior
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);

        // Establish base delay with some samples
        controller.on_ack(Duration::from_millis(100), 1000);
        controller.on_ack(Duration::from_millis(100), 1000);

        // Simulate initial working state with good cwnd
        let pre_timeout_cwnd = controller.current_cwnd();
        assert!(
            pre_timeout_cwnd >= 50_000,
            "pre-timeout cwnd should be at initial value: {}",
            pre_timeout_cwnd
        );

        // Trigger a timeout - this should reset cwnd but allow fast recovery
        controller.on_timeout();

        let post_timeout_cwnd = controller.current_cwnd();
        assert!(
            post_timeout_cwnd <= 3000,
            "post-timeout cwnd should be at minimum: {}",
            post_timeout_cwnd
        );

        // Now try to recover via ACKs
        // With slow start (exponential growth), we should recover quickly
        // With congestion avoidance (linear growth), recovery would be very slow
        //
        // At 100ms RTT with zero queuing delay, slow start should roughly double
        // cwnd every RTT. Starting from ~2.8KB:
        // - After 1 RTT: ~5.6KB
        // - After 2 RTTs: ~11KB
        // - After 3 RTTs: ~22KB
        // - After 4 RTTs: ~44KB
        //
        // In congestion avoidance, growth would be ~150-200 bytes per RTT, so
        // after 4 RTTs we'd only be at ~3.4KB (barely any growth).

        // Simulate 4 RTTs worth of ACKs with zero queuing delay
        let bytes_per_ack = 5_000;
        for i in 0..4 {
            // Keep flightsize high enough to not trigger application-limited cap
            controller.on_send(50_000);

            // Wait for rate-limiting interval (base_delay = 100ms)
            tokio::time::sleep(Duration::from_millis(110)).await;

            // Zero queuing delay (RTT = base delay) - this is the problematic case
            // because off_target = 0 means no congestion avoidance growth
            controller.on_ack(Duration::from_millis(100), bytes_per_ack);

            let current_cwnd = controller.current_cwnd();
            println!(
                "After ACK {}: cwnd = {} bytes ({} KB)",
                i + 1,
                current_cwnd,
                current_cwnd / 1024
            );
        }

        let recovered_cwnd = controller.current_cwnd();

        // After 4 RTTs in slow start, cwnd should have grown significantly
        // We're lenient here: just checking it grew more than 10KB (proving
        // exponential growth, not the ~1KB we'd see from linear growth)
        assert!(
            recovered_cwnd >= 10_000,
            "After 4 RTTs, cwnd should have recovered via slow start to at least 10KB. \
         Got {} bytes. This suggests slow start was NOT re-enabled after timeout, \
         leaving cwnd stuck in slow linear congestion avoidance growth.",
            recovered_cwnd
        );

        // Also verify we're in SlowStart state
        assert_eq!(
            controller.congestion_state.load(),
            CongestionState::SlowStart,
            "Should be in SlowStart state after timeout for fast recovery"
        );

        println!(
            "✓ Timeout recovery test passed: cwnd recovered from {} to {} bytes",
            post_timeout_cwnd, recovered_cwnd
        );
    }

    // =========================================================================
    // VARIABLE RTT TESTS
    //
    // These tests exercise LEDBAT behavior under variable network latency,
    // which is critical for real-world performance but was not previously
    // covered by fixed-latency tests.
    //
    // Key scenarios tested:
    // 1. Queue buildup simulation (parametrized across latencies)
    // 2. Latency spike recovery (parametrized across spike magnitudes)
    // 3. Continuous jitter with stability analysis
    // 4. Base delay shift detection
    // 5. Extreme RTT variation stress test
    // 6. Timeout recovery with degrading conditions
    //
    // Implementation notes:
    // - Duration comparisons use tolerance where exact equality is fragile.
    // - LEDBAT uses TimeSource trait for deterministic simulation testing.
    // - Tests use tokio::time which is controlled by Turmoil in simulation mode.
    // =========================================================================

    /// Helper for approximate Duration comparison with tolerance.
    /// Returns true if `a` and `b` differ by at most `tolerance_ms` milliseconds.
    fn duration_approx_eq(a: Duration, b: Duration, tolerance_ms: u64) -> bool {
        let diff = a.abs_diff(b);
        diff <= Duration::from_millis(tolerance_ms)
    }

    /// Test cwnd recovery with continuously variable RTT (jittery network).
    ///
    /// This simulates a network path with inherent variability (e.g., WiFi,
    /// mobile networks) where RTT fluctuates constantly within a range.
    /// LEDBAT should:
    /// 1. Track the minimum RTT as base_delay
    /// 2. Respond appropriately to the variable queuing delay
    /// 3. Not "hunt" excessively (oscillate cwnd rapidly)
    #[tokio::test]
    async fn test_variable_rtt_continuous_jitter() {
        println!("\n========== Variable RTT: Continuous Jitter Test ==========");

        let config = LedbatConfig {
            initial_cwnd: 60_000,
            min_cwnd: 2848,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: true,
            enable_periodic_slowdown: false,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        controller.on_send(1_000_000);

        // Simulate jittery RTT: base 40ms with ±20ms variation (20-60ms range)
        let base_jitter = 40u64;
        let jitter_range = 20i64;

        println!("\n--- Jittery RTT simulation (40ms ± 20ms) ---");
        println!(
            "{:>6} {:>8} {:>12} {:>10}",
            "ACK#", "RTT(ms)", "Base(ms)", "Cwnd(KB)"
        );
        println!("{}", "-".repeat(42));

        let mut min_rtt_seen = Duration::from_millis(1000);
        let mut cwnd_samples: Vec<usize> = Vec::new();

        // Simulate 20 ACKs with jittery RTT
        for i in 0..20 {
            // Generate jittery RTT using a simple pattern (not random, for reproducibility)
            let jitter = ((i as i64 * 7) % (jitter_range * 2 + 1)) - jitter_range;
            let rtt_ms = ((base_jitter as i64) + jitter).max(20) as u64;
            let rtt = Duration::from_millis(rtt_ms);

            if rtt < min_rtt_seen {
                min_rtt_seen = rtt;
            }

            tokio::time::sleep(Duration::from_millis(rtt_ms + 5)).await;
            controller.on_ack(rtt, 5000);

            let cwnd = controller.current_cwnd();
            cwnd_samples.push(cwnd);

            if i % 4 == 0 || i == 19 {
                println!(
                    "{:>6} {:>8} {:>12} {:>10}",
                    i + 1,
                    rtt_ms,
                    controller.base_delay().as_millis(),
                    cwnd / 1024
                );
            }
        }

        // Analyze cwnd stability (variance shouldn't be too high)
        let avg_cwnd: usize = cwnd_samples.iter().sum::<usize>() / cwnd_samples.len();
        let variance: f64 = cwnd_samples
            .iter()
            .map(|&c| {
                let diff = c as f64 - avg_cwnd as f64;
                diff * diff
            })
            .sum::<f64>()
            / cwnd_samples.len() as f64;
        let std_dev = variance.sqrt();
        let coefficient_of_variation = std_dev / avg_cwnd as f64;

        println!("\n--- Stability Analysis ---");
        println!("  Min RTT seen: {}ms", min_rtt_seen.as_millis());
        println!("  Base delay:   {}ms", controller.base_delay().as_millis());
        println!("  Avg cwnd:     {} KB", avg_cwnd / 1024);
        println!("  Std dev:      {} KB", std_dev as usize / 1024);
        println!("  CoV:          {:.2}", coefficient_of_variation);

        // Verify base delay tracks minimum (with 1ms tolerance for timing edge cases)
        assert!(
            duration_approx_eq(controller.base_delay(), min_rtt_seen, 1),
            "Base delay {:?} should track minimum RTT {:?}",
            controller.base_delay(),
            min_rtt_seen
        );

        // Cwnd should not be excessively unstable.
        // CoV < 0.5 threshold rationale: In congestion control literature, CoV < 0.5
        // indicates "low to moderate variability" (std dev < half of mean). This ensures
        // LEDBAT isn't over-reacting to jitter with excessive cwnd oscillation, which
        // would hurt throughput and fairness. Real-world measurements of stable TCP
        // connections typically show CoV between 0.1-0.3 for cwnd.
        assert!(
            coefficient_of_variation < 0.5,
            "Cwnd should be reasonably stable under jitter (CoV={:.2} < 0.5)",
            coefficient_of_variation
        );
    }

    /// Test base delay shift detection (network path change).
    ///
    /// This simulates a scenario where the network path changes permanently,
    /// causing a new baseline latency. This test verifies that:
    /// 1. The connection remains functional after a path change
    /// 2. LEDBAT continues operating correctly even if base delay reflects
    ///    historical minimum rather than current path minimum
    ///
    /// Note on LEDBAT base delay behavior:
    /// LEDBAT's base delay history (BASE_HISTORY_SIZE samples) preserves the
    /// minimum RTT ever seen within the window. This is intentional - it prevents
    /// the algorithm from being fooled by persistent queuing into thinking the
    /// path latency has increased. The tradeoff is that after a genuine path
    /// change to higher latency, LEDBAT may be overly aggressive until the
    /// historical minimum ages out. This test validates the connection remains
    /// usable in this scenario.
    #[tokio::test]
    async fn test_variable_rtt_base_delay_shift() {
        println!("\n========== Variable RTT: Base Delay Shift Test ==========");

        let min_cwnd = 2848usize;
        let config = LedbatConfig {
            initial_cwnd: 50_000,
            min_cwnd,
            max_cwnd: 10_000_000,
            ssthresh: 40_000,
            enable_slow_start: true,
            enable_periodic_slowdown: false,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        controller.on_send(1_000_000);

        // Phase 1: Establish original base delay at 20ms
        let old_rtt = Duration::from_millis(20);
        println!("\n--- Phase 1: Original path @ 20ms ---");
        for i in 0..BASE_HISTORY_SIZE {
            controller.on_ack(old_rtt, 3000);
            tokio::time::sleep(Duration::from_millis(25)).await;
            if i == BASE_HISTORY_SIZE - 1 {
                println!(
                    "  Established base_delay: {:?}, cwnd: {} KB",
                    controller.base_delay(),
                    controller.current_cwnd() / 1024
                );
            }
        }

        assert!(
            duration_approx_eq(controller.base_delay(), old_rtt, 1),
            "Base delay should be ~20ms initially, got {:?}",
            controller.base_delay()
        );

        // Phase 2: Path change - new baseline is 80ms
        // This could happen due to route change, VPN activation, etc.
        let new_rtt = Duration::from_millis(80);
        println!("\n--- Phase 2: Path change to 80ms ---");
        println!(
            "  (Simulating BASE_HISTORY_SIZE={} samples at new RTT)",
            BASE_HISTORY_SIZE
        );

        // Need enough samples to age out the old base delay
        // The base delay history keeps BASE_HISTORY_SIZE samples
        for i in 0..(BASE_HISTORY_SIZE * 2) {
            tokio::time::sleep(Duration::from_millis(85)).await;
            controller.on_ack(new_rtt, 3000);

            // The base delay history should eventually reflect the new minimum
            // Note: due to how base_delay_history works, it may take time
            if i % 5 == 0 {
                println!(
                    "  Sample {}: base_delay={:?}, cwnd={} KB",
                    i + 1,
                    controller.base_delay(),
                    controller.current_cwnd() / 1024
                );
            }
        }

        let final_base_delay = controller.base_delay();
        println!("\n--- Result ---");
        println!("  Old base delay: {:?}", old_rtt);
        println!("  New base delay: {:?}", final_base_delay);
        println!(
            "  Note: LEDBAT preserves historical minimum within {} samples",
            BASE_HISTORY_SIZE
        );

        // The connection should continue functioning (not stall due to permanent "queuing")
        // regardless of whether base delay updated to new path latency
        let final_cwnd = controller.current_cwnd();
        assert!(
            final_cwnd >= min_cwnd,
            "Cwnd should remain usable after path change: {}",
            final_cwnd
        );
    }

    /// Test behavior under extreme RTT variation (stress test).
    ///
    /// This tests LEDBAT's robustness under pathological network conditions
    /// with rapid, extreme RTT swings. The controller should:
    /// 1. Not crash or panic
    /// 2. Maintain cwnd within valid bounds
    /// 3. Continue making forward progress
    #[tokio::test]
    async fn test_variable_rtt_extreme_variation() {
        println!("\n========== Variable RTT: Extreme Variation Stress Test ==========");

        let min_cwnd = 2848usize;
        let max_cwnd = 10_000_000usize;
        let config = LedbatConfig {
            initial_cwnd: 50_000,
            min_cwnd,
            max_cwnd,
            ssthresh: 40_000,
            enable_slow_start: true,
            enable_periodic_slowdown: false,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        controller.on_send(1_000_000);

        // Extreme RTT pattern: 10ms, 200ms, 15ms, 300ms, 20ms, 50ms, 250ms, ...
        let extreme_rtts: Vec<u64> = vec![10, 200, 15, 300, 20, 50, 250, 10, 180, 25, 350, 30, 10];

        println!("\n--- Extreme RTT pattern ---");
        println!(
            "{:>6} {:>8} {:>12} {:>10}",
            "ACK#", "RTT(ms)", "Base(ms)", "Cwnd(KB)"
        );
        println!("{}", "-".repeat(42));

        let mut min_rtt = Duration::from_millis(1000);

        for (i, &rtt_ms) in extreme_rtts.iter().enumerate() {
            let rtt = Duration::from_millis(rtt_ms);
            if rtt < min_rtt {
                min_rtt = rtt;
            }

            // Advance time proportionally to RTT to simulate realistic timing
            tokio::time::sleep(Duration::from_millis(rtt_ms.min(100) + 5)).await;
            controller.on_ack(rtt, 3000);

            println!(
                "{:>6} {:>8} {:>12} {:>10}",
                i + 1,
                rtt_ms,
                controller.base_delay().as_millis(),
                controller.current_cwnd() / 1024
            );
        }

        // Verify invariants after stress test
        let final_cwnd = controller.current_cwnd();
        let final_base_delay = controller.base_delay();

        println!("\n--- Post-stress verification ---");
        println!("  Min RTT seen:    {}ms", min_rtt.as_millis());
        println!("  Final base_delay: {:?}", final_base_delay);
        println!("  Final cwnd:      {} KB", final_cwnd / 1024);

        // Invariant: cwnd must be within bounds
        assert!(
            final_cwnd >= min_cwnd,
            "Cwnd must be >= min_cwnd: {} >= {}",
            final_cwnd,
            min_cwnd
        );
        assert!(
            final_cwnd <= max_cwnd,
            "Cwnd must be <= max_cwnd: {} <= {}",
            final_cwnd,
            max_cwnd
        );

        // Invariant: base_delay should track minimum (with tolerance)
        assert!(
            duration_approx_eq(final_base_delay, min_rtt, 1),
            "Base delay {:?} should track minimum RTT {:?}",
            final_base_delay,
            min_rtt
        );
    }

    /// Parametrized test: variable RTT with different base latencies.
    ///
    /// Tests that LEDBAT behaves correctly across different network types
    /// with jittery latency. This complements the fixed-latency tests in
    /// `test_slowdown_at_various_latencies` by adding RTT variability.
    ///
    /// Network types tested:
    /// - LAN (10ms base, ±5ms jitter)
    /// - Regional (50ms base, ±20ms jitter)
    /// - Continental (100ms base, ±30ms jitter)
    /// - Satellite (600ms base, ±100ms jitter) - realistic GEO satellite latency
    #[rstest::rstest]
    #[case::lan(10, 5)]
    #[case::regional(50, 20)]
    #[case::continental(100, 30)]
    #[case::satellite(600, 100)]
    #[tokio::test]
    async fn test_variable_rtt_jitter_by_network_type(
        #[case] base_rtt_ms: u64,
        #[case] jitter_ms: u64,
    ) {
        let min_cwnd = 2848usize;
        let max_cwnd = 10_000_000usize;
        let config = LedbatConfig {
            initial_cwnd: 50_000,
            min_cwnd,
            max_cwnd,
            ssthresh: 40_000,
            enable_slow_start: true,
            enable_periodic_slowdown: false,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        controller.on_send(1_000_000);

        let mut min_rtt = Duration::from_millis(base_rtt_ms + jitter_ms);

        // Simulate 15 ACKs with jittery RTT
        for i in 0..15 {
            // Generate deterministic jitter pattern
            let jitter = ((i as i64 * 7) % ((jitter_ms as i64) * 2 + 1)) - (jitter_ms as i64);
            let rtt_ms = ((base_rtt_ms as i64) + jitter).max(5) as u64;
            let rtt = Duration::from_millis(rtt_ms);

            if rtt < min_rtt {
                min_rtt = rtt;
            }

            // Advance time appropriately for this RTT
            let wait_time = (rtt_ms / 2).max(10); // Wait half RTT minimum
            tokio::time::sleep(Duration::from_millis(wait_time)).await;

            controller.on_ack(rtt, 5000);
        }

        let final_cwnd = controller.current_cwnd();
        let final_base_delay = controller.base_delay();

        println!(
            "{}ms ± {}ms: base_delay={:?}, final_cwnd={} KB",
            base_rtt_ms,
            jitter_ms,
            final_base_delay,
            final_cwnd / 1024
        );

        // Verify base delay tracks minimum (with tolerance)
        assert!(
            duration_approx_eq(final_base_delay, min_rtt, 1),
            "{}ms base: Base delay {:?} should track minimum RTT {:?}",
            base_rtt_ms,
            final_base_delay,
            min_rtt
        );

        // Verify cwnd remains valid
        assert!(
            final_cwnd >= min_cwnd && final_cwnd <= max_cwnd,
            "{}ms base: Cwnd {} should be within bounds [{}, {}]",
            base_rtt_ms,
            final_cwnd,
            min_cwnd,
            max_cwnd
        );
    }

    /// Parametrized test: queue buildup at different base latencies.
    ///
    /// Tests LEDBAT's response to gradual queue buildup across different
    /// network conditions. The queue grows from base_rtt to base_rtt + 60ms
    /// (matching TARGET delay).
    #[rstest::rstest]
    #[case::lan(20, 60)] // 20ms -> 80ms (queuing = 60ms = TARGET)
    #[case::regional(50, 60)] // 50ms -> 110ms
    #[case::high_latency(100, 60)] // 100ms -> 160ms
    #[tokio::test]
    async fn test_variable_rtt_queue_buildup(
        #[case] base_rtt_ms: u64,
        #[case] queue_growth_ms: u64,
    ) {
        let min_cwnd = 2848usize;
        let config = LedbatConfig {
            initial_cwnd: 100_000,
            min_cwnd,
            max_cwnd: 10_000_000,
            ssthresh: 200_000,
            enable_slow_start: true,
            enable_periodic_slowdown: false,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        controller.on_send(1_000_000);

        // Establish baseline
        let base_rtt = Duration::from_millis(base_rtt_ms);
        for _ in 0..5 {
            controller.on_ack(base_rtt, 5000);
            tokio::time::sleep(Duration::from_millis(base_rtt_ms + 5)).await;
        }

        let baseline_base_delay = controller.base_delay();
        assert!(
            duration_approx_eq(baseline_base_delay, base_rtt, 1),
            "Base delay should be established at {:?}, got {:?}",
            base_rtt,
            baseline_base_delay
        );

        // Simulate gradual queue buildup
        let steps = 6u64;
        let step_size = queue_growth_ms / steps;
        for i in 1..=steps {
            let rtt_ms = base_rtt_ms + (step_size * i);
            let rtt = Duration::from_millis(rtt_ms);
            tokio::time::sleep(Duration::from_millis(rtt_ms + 5)).await;
            controller.on_ack(rtt, 5000);
        }

        let final_cwnd = controller.current_cwnd();
        let final_base_delay = controller.base_delay();

        println!(
            "{}ms base + {}ms queue: base_delay={:?}, final_cwnd={} KB",
            base_rtt_ms,
            queue_growth_ms,
            final_base_delay,
            final_cwnd / 1024
        );

        // Base delay should remain at minimum (with tolerance)
        assert!(
            duration_approx_eq(final_base_delay, base_rtt, 1),
            "Base delay should remain at minimum observed RTT {:?}, got {:?}",
            base_rtt,
            final_base_delay
        );

        // Cwnd should remain usable
        assert!(
            final_cwnd >= min_cwnd,
            "Cwnd should remain usable: {} >= {}",
            final_cwnd,
            min_cwnd
        );
    }

    /// Parametrized test: latency spike recovery at different magnitudes.
    ///
    /// Tests LEDBAT's ability to recover from latency spikes of various
    /// magnitudes. Larger spikes (relative to base delay) are more challenging.
    #[rstest::rstest]
    #[case::small_spike(30, 80)] // 30ms base, spike to 80ms (2.7x)
    #[case::medium_spike(30, 150)] // 30ms base, spike to 150ms (5x)
    #[case::large_spike(30, 300)] // 30ms base, spike to 300ms (10x)
    #[tokio::test]
    async fn test_variable_rtt_spike_recovery(#[case] base_rtt_ms: u64, #[case] spike_rtt_ms: u64) {
        let min_cwnd = 2848usize;
        let config = LedbatConfig {
            initial_cwnd: 80_000,
            min_cwnd,
            max_cwnd: 10_000_000,
            ssthresh: 50_000,
            enable_slow_start: true,
            enable_periodic_slowdown: false,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        controller.on_send(1_000_000);

        // Establish baseline
        let base_rtt = Duration::from_millis(base_rtt_ms);
        for _ in 0..5 {
            controller.on_ack(base_rtt, 10_000);
            tokio::time::sleep(Duration::from_millis(base_rtt_ms + 5)).await;
        }

        // Apply spike
        let spike_rtt = Duration::from_millis(spike_rtt_ms);
        for _ in 0..3 {
            tokio::time::sleep(Duration::from_millis(spike_rtt_ms.min(100) + 5)).await;
            controller.on_ack(spike_rtt, 2000);
        }

        // Recovery
        for _ in 0..8 {
            tokio::time::sleep(Duration::from_millis(base_rtt_ms + 5)).await;
            controller.on_ack(base_rtt, 10_000);
        }

        let final_cwnd = controller.current_cwnd();
        let final_base_delay = controller.base_delay();

        println!(
            "{}ms base, {}ms spike ({}x): base_delay={:?}, final_cwnd={} KB",
            base_rtt_ms,
            spike_rtt_ms,
            spike_rtt_ms / base_rtt_ms,
            final_base_delay,
            final_cwnd / 1024
        );

        // Base delay should remain at minimum (not polluted by spike), with tolerance
        assert!(
            duration_approx_eq(final_base_delay, base_rtt, 1),
            "Base delay should remain at minimum {:?} after spike, got {:?}",
            base_rtt,
            final_base_delay
        );

        // Cwnd should be usable after recovery
        assert!(
            final_cwnd >= min_cwnd,
            "Cwnd should be usable after recovery: {} >= {}",
            final_cwnd,
            min_cwnd
        );
    }

    /// Test timeout behavior with variable RTT leading up to timeout.
    ///
    /// This simulates a scenario where RTT becomes increasingly unreliable
    /// before eventually triggering a timeout. Tests the interaction between
    /// variable RTT handling and timeout recovery (fixed in PR #2549).
    #[tokio::test]
    async fn test_variable_rtt_timeout_recovery() {
        println!("\n========== Variable RTT: Timeout with Degrading Conditions ==========");

        let min_cwnd = 2848usize;
        let config = LedbatConfig {
            initial_cwnd: 80_000,
            min_cwnd,
            max_cwnd: 10_000_000,
            ssthresh: 60_000,
            enable_slow_start: true,
            enable_periodic_slowdown: false,
            randomize_ssthresh: false,
            ..Default::default()
        };
        let controller = LedbatController::new_with_config(config);
        controller.on_send(1_000_000);

        // Phase 1: Establish baseline at 50ms
        println!("\n--- Phase 1: Establish 50ms baseline ---");
        let baseline_rtt = Duration::from_millis(50);
        for _ in 0..5 {
            controller.on_ack(baseline_rtt, 5000);
            tokio::time::sleep(Duration::from_millis(55)).await;
        }

        let pre_degradation_cwnd = controller.current_cwnd();
        println!(
            "  Baseline established: cwnd={} KB, base_delay={:?}",
            pre_degradation_cwnd / 1024,
            controller.base_delay()
        );

        // Phase 2: Degrading conditions (RTT increases with high variance)
        println!("\n--- Phase 2: Degrading conditions ---");
        let degrading_rtts = [60, 80, 100, 150, 200, 250];
        for &rtt_ms in &degrading_rtts {
            let rtt = Duration::from_millis(rtt_ms);
            tokio::time::sleep(Duration::from_millis(rtt_ms + 10)).await;
            controller.on_ack(rtt, 2000);
            println!(
                "  RTT: {}ms, cwnd: {} KB",
                rtt_ms,
                controller.current_cwnd() / 1024
            );
        }

        let pre_timeout_cwnd = controller.current_cwnd();

        // Phase 3: Timeout occurs
        println!("\n--- Phase 3: Timeout ---");
        controller.on_timeout();
        let post_timeout_cwnd = controller.current_cwnd();
        println!(
            "  Timeout: cwnd {} KB → {} KB",
            pre_timeout_cwnd / 1024,
            post_timeout_cwnd / 1024
        );

        // Verify timeout reset cwnd to adaptive floor / 4.
        // With ssthresh=60KB and slow start exiting around there, the BDP proxy gives
        // floor ~60KB, so adaptive_min_cwnd ~15KB. This is higher than min_cwnd but
        // well below ssthresh, leaving room for slow start recovery.
        let ssthresh_after = controller.ssthresh.load(Ordering::Acquire);
        assert!(
            post_timeout_cwnd >= min_cwnd,
            "Timeout cwnd should be at least min_cwnd: {}",
            post_timeout_cwnd
        );
        assert!(
            post_timeout_cwnd <= ssthresh_after / 2,
            "Timeout cwnd should be well below ssthresh: cwnd={} ssthresh={}",
            post_timeout_cwnd,
            ssthresh_after
        );

        // Verify SlowStart state re-enabled for fast recovery
        assert_eq!(
            controller.congestion_state.load(),
            CongestionState::SlowStart,
            "SlowStart state should be set after timeout"
        );

        // Phase 4: Recovery with improving conditions
        // Start with moderate RTT to allow slow start growth before triggering exit
        println!("\n--- Phase 4: Recovery with improving RTT ---");
        let improving_rtts = [80, 70, 60, 55, 52, 50, 50, 50, 50, 50, 50, 50];
        for &rtt_ms in &improving_rtts {
            let rtt = Duration::from_millis(rtt_ms);
            controller.on_send(50_000);
            tokio::time::sleep(Duration::from_millis(rtt_ms + 5)).await;
            controller.on_ack(rtt, 8000);
        }

        let recovered_cwnd = controller.current_cwnd();
        println!(
            "\n  Recovery complete: cwnd={} KB (started at {} KB)",
            recovered_cwnd / 1024,
            post_timeout_cwnd / 1024
        );

        // Should have recovered from minimum cwnd via slow start.
        // Using 1.5x threshold (instead of 2x) to account for variability in
        // slow start behavior depending on when ssthresh is hit. The key invariant
        // is that cwnd grows meaningfully, not that it hits an exact multiple.
        let recovery_threshold = (post_timeout_cwnd * 3) / 2; // 1.5x
        assert!(
            recovered_cwnd >= recovery_threshold,
            "Should recover via slow start to at least 1.5x minimum: {} >= {}",
            recovered_cwnd,
            recovery_threshold
        );

        // Base delay should have returned to near baseline (with tolerance)
        let final_base_delay = controller.base_delay();
        assert!(
            final_base_delay <= Duration::from_millis(55),
            "Base delay should return to near baseline: {:?}",
            final_base_delay
        );
    }

    /// Issue #4345: `release_flightsize` drains an abandoned packet's bytes
    /// (saturating), while `on_timeout` leaves flight size unchanged. Mirrors
    /// the BBR/FixedRate `release_flightsize_drains_abandoned_bytes` tests for
    /// the non-default LEDBAT controller.
    #[test]
    fn test_issue_4345_release_flightsize_drains_abandoned_bytes() {
        let controller = LedbatController::new(100_000, 2848, 10_000_000);

        controller.on_send(5000);
        assert_eq!(controller.flightsize(), 5000);

        // on_timeout (per-RTO) must NOT change flight size — the packet is still
        // in flight, being retransmitted.
        controller.on_timeout();
        assert_eq!(
            controller.flightsize(),
            5000,
            "on_timeout() must not change flight size; only abandonment/ACK does"
        );

        // Abandonment releases the bytes.
        controller.release_flightsize(3000);
        assert_eq!(controller.flightsize(), 2000);

        // Saturating: releasing more than remains floors at 0, never underflows.
        controller.release_flightsize(9999);
        assert_eq!(controller.flightsize(), 0);
    }

    // ============================================================================
}
