use std::time::Duration;
use tokio::time::sleep;
use tracing::{info, warn};

/// Exponential backoff with jitter for WebSocket reconnection.
pub struct ReconnectPolicy {
    base_ms: u64,
    max_ms: u64,
    attempt: u32,
}

impl ReconnectPolicy {
    pub fn new(base_ms: u64, max_ms: u64) -> Self {
        Self {
            base_ms,
            max_ms,
            attempt: 0,
        }
    }

    /// Compute the next delay, increment the attempt counter, and sleep.
    pub async fn wait(&mut self, venue: &str) {
        let delay = self.next_delay();
        warn!(
            venue = venue,
            attempt = self.attempt,
            delay_ms = delay.as_millis() as u64,
            "reconnecting after delay"
        );
        sleep(delay).await;
    }

    /// Compute the next backoff delay with jitter (does NOT sleep).
    pub fn next_delay(&mut self) -> Duration {
        let exp_ms = self.base_ms.saturating_mul(1u64 << self.attempt.min(20));
        let capped_ms = exp_ms.min(self.max_ms);
        // Simple jitter: uniform random in [capped_ms/2, capped_ms]
        let jitter_ms = capped_ms / 2 + (pseudo_random() % (capped_ms / 2 + 1));
        self.attempt += 1;
        Duration::from_millis(jitter_ms)
    }

    /// Reset the attempt counter after a successful connection.
    pub fn reset(&mut self) {
        if self.attempt > 0 {
            info!(previous_attempts = self.attempt, "reconnect policy reset");
        }
        self.attempt = 0;
    }

    pub fn attempt(&self) -> u32 {
        self.attempt
    }
}

/// Simple pseudo-random using thread-local state seeded from pointer address.
/// Not cryptographically secure; sufficient for jitter.
fn pseudo_random() -> u64 {
    use std::cell::Cell;
    thread_local! {
        static STATE: Cell<u64> = Cell::new(0);
    }
    STATE.with(|s| {
        let mut x = s.get();
        if x == 0 {
            // Seed from stack address
            x = (&x as *const _ as u64) ^ 0x517cc1b727220a95;
        }
        // xorshift64
        x ^= x << 13;
        x ^= x >> 7;
        x ^= x << 17;
        s.set(x);
        x
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_delay_increases_exponentially() {
        let mut policy = ReconnectPolicy::new(1000, 60000);
        let d0 = policy.next_delay();
        let d1 = policy.next_delay();
        let d2 = policy.next_delay();

        // d0 is based on 1s base, d1 on 2s, d2 on 4s
        // With jitter in [half, full], d1 should generally be >= d0 lower bound
        // We verify the upper bounds are increasing: 1s, 2s, 4s
        assert!(d0.as_millis() <= 1000);
        assert!(d1.as_millis() <= 2000);
        assert!(d2.as_millis() <= 4000);

        // Lower bounds: 500, 1000, 2000
        assert!(d0.as_millis() >= 500);
        assert!(d1.as_millis() >= 1000);
        assert!(d2.as_millis() >= 2000);
    }

    #[test]
    fn test_delay_capped_at_max() {
        let mut policy = ReconnectPolicy::new(1000, 5000);
        // After enough attempts, delay should not exceed max
        for _ in 0..20 {
            let d = policy.next_delay();
            assert!(
                d.as_millis() <= 5000,
                "delay {} exceeded max 5000",
                d.as_millis()
            );
        }
    }

    #[test]
    fn test_reset_clears_attempt() {
        let mut policy = ReconnectPolicy::new(1000, 60000);
        policy.next_delay();
        policy.next_delay();
        assert_eq!(policy.attempt(), 2);
        policy.reset();
        assert_eq!(policy.attempt(), 0);

        // After reset, delay should be back to base range
        let d = policy.next_delay();
        assert!(d.as_millis() <= 1000);
        assert!(d.as_millis() >= 500);
    }

    #[test]
    fn test_jitter_varies() {
        // Run multiple times and check we don't always get the same value
        let mut delays = Vec::new();
        for _ in 0..10 {
            let mut p = ReconnectPolicy::new(1000, 60000);
            // Force different seed by creating on different iterations
            delays.push(p.next_delay().as_millis());
        }
        // With 10 attempts at base=1000 (range 500-1000), we should get some variation
        // At minimum, verify all are in valid range
        for d in &delays {
            assert!(*d >= 500 && *d <= 1000);
        }
    }
}
