use std::future::Future;
use std::sync::Mutex;
use std::time::{Duration, Instant};

use rand::Rng;
use tracing::{debug, warn};

use crate::config::RetryConfig;
use crate::error::MonkError;

/// Executes async operations with exponential backoff, full jitter, and a
/// lightweight circuit breaker.
#[derive(Debug)]
pub(crate) struct RetryPolicy {
    config: RetryConfig,
    cb: CircuitBreaker,
}

impl RetryPolicy {
    pub fn new(config: RetryConfig) -> Self {
        let cb = CircuitBreaker::new(
            config.circuit_breaker_threshold,
            config.circuit_breaker_cooldown,
        );
        Self { config, cb }
    }

    /// Execute `op` with retries.  `op` is called repeatedly until it succeeds,
    /// returns a non-retryable error, or exhausts the retry budget.
    ///
    /// The closure receives the attempt number (0-based) and must return a
    /// `Result<T, MonkError>`.
    pub async fn execute<T, F, Fut>(&self, mut op: F) -> crate::error::Result<T>
    where
        F: FnMut(u32) -> Fut,
        Fut: Future<Output = crate::error::Result<T>>,
    {
        let deadline = self.config.deadline.map(|d| Instant::now() + d);

        let mut last_err: Option<MonkError> = None;

        for attempt in 0..=self.config.max_retries {
            if let Some(dl) = deadline {
                if Instant::now() >= dl {
                    debug!(attempt, "retry deadline exceeded");
                    break;
                }
            }

            if !self.cb.allow_request() {
                return Err(MonkError::CircuitOpen);
            }

            match op(attempt).await {
                Ok(val) => {
                    self.cb.record_success();
                    return Ok(val);
                }
                Err(e) => {
                    let retryable = e.is_retryable();
                    self.cb.record_failure();

                    if !retryable || attempt == self.config.max_retries {
                        return Err(e);
                    }

                    let backoff = self.jittered_delay(attempt);
                    let capped = if let Some(dl) = deadline {
                        let remaining = dl.saturating_duration_since(Instant::now());
                        backoff.min(remaining)
                    } else {
                        backoff
                    };

                    warn!(
                        attempt,
                        next_attempt = attempt + 1,
                        backoff_ms = capped.as_millis() as u64,
                        error = %e,
                        "retrying after transient error"
                    );

                    tokio::time::sleep(capped).await;
                    last_err = Some(e);
                }
            }
        }

        Err(last_err.unwrap_or(MonkError::Timeout))
    }

    /// Full-jitter exponential backoff: `uniform(0, min(max_delay, base * 2^attempt))`.
    fn jittered_delay(&self, attempt: u32) -> Duration {
        let exp = self
            .config
            .base_delay
            .saturating_mul(1u32.checked_shl(attempt).unwrap_or(u32::MAX));
        let ceiling = exp.min(self.config.max_delay);
        let jitter_nanos = rand::thread_rng().gen_range(0..=ceiling.as_nanos() as u64);
        Duration::from_nanos(jitter_nanos)
    }
}

// ── Lightweight Circuit Breaker ─────────────────────────────────────────────

/// Three-state circuit breaker: Closed (normal) -> Open (rejecting) -> HalfOpen (probing).
#[derive(Debug)]
struct CircuitBreaker {
    threshold: u32,
    cooldown: Duration,
    state: Mutex<CircuitState>,
}

#[derive(Debug)]
struct CircuitState {
    mode: CbMode,
    consecutive_failures: u32,
}

#[derive(Debug)]
enum CbMode {
    Closed,
    Open { since: Instant },
    HalfOpen,
}

impl CircuitBreaker {
    fn new(threshold: u32, cooldown: Duration) -> Self {
        Self {
            threshold,
            cooldown,
            state: Mutex::new(CircuitState {
                mode: CbMode::Closed,
                consecutive_failures: 0,
            }),
        }
    }

    /// Returns `true` if the request should be allowed through.
    fn allow_request(&self) -> bool {
        let mut state = self.state.lock().unwrap();
        match state.mode {
            CbMode::Closed => true,
            CbMode::Open { since } => {
                if since.elapsed() >= self.cooldown {
                    state.mode = CbMode::HalfOpen;
                    debug!("circuit breaker transitioning to half-open");
                    true
                } else {
                    false
                }
            }
            CbMode::HalfOpen => false,
        }
    }

    fn record_success(&self) {
        let mut state = self.state.lock().unwrap();
        if matches!(state.mode, CbMode::HalfOpen) {
            debug!("circuit breaker closing after successful probe");
        }
        state.mode = CbMode::Closed;
        state.consecutive_failures = 0;
    }

    fn record_failure(&self) {
        let mut state = self.state.lock().unwrap();
        state.consecutive_failures = state.consecutive_failures.saturating_add(1);
        if state.consecutive_failures >= self.threshold
            && matches!(state.mode, CbMode::Closed | CbMode::HalfOpen)
        {
            warn!(
                failures = state.consecutive_failures,
                "circuit breaker opening after consecutive failures"
            );
            state.mode = CbMode::Open {
                since: Instant::now(),
            };
        }
    }
}
