//! Simple circuit breaker with Closed, Open, HalfOpen states.
//! Zero overhead when `self-heal` feature is absent.

#[cfg(feature = "self-heal")]
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
#[cfg(feature = "self-heal")]
use std::time::{SystemTime, UNIX_EPOCH};

#[cfg(feature = "self-heal")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CircuitState {
    /// Normal operation: requests pass through.
    Closed,
    /// Too many errors: requests are blocked.
    Open,
    /// Cooldown elapsed: one probe request allowed.
    HalfOpen,
}

pub struct CircuitBreaker {
    #[cfg(feature = "self-heal")] error_count: AtomicU32,
    /// Unix epoch seconds when the circuit was opened; 0 means closed.
    #[cfg(feature = "self-heal")] opened_at_secs: AtomicU64,
    #[cfg(feature = "self-heal")] threshold: u32,
    #[cfg(feature = "self-heal")] reset_secs: u64,
}

impl CircuitBreaker {
    /// Create a new circuit breaker.
    ///
    /// # Arguments
    /// * `threshold` — number of consecutive errors before opening.
    /// * `reset_secs` — seconds to wait before transitioning to HalfOpen.
    #[cfg(feature = "self-heal")]
    pub fn new(threshold: u32, reset_secs: u64) -> Self {
        Self {
            error_count: AtomicU32::new(0),
            opened_at_secs: AtomicU64::new(0),
            threshold,
            reset_secs,
        }
    }

    /// Create a circuit breaker (no-op version when feature is absent).
    #[cfg(not(feature = "self-heal"))]
    pub fn new(_threshold: u32, _reset_secs: u64) -> Self {
        Self {}
    }

    /// Returns `true` if the request should be allowed through.
    pub fn allow(&self) -> bool {
        #[cfg(feature = "self-heal")]
        {
            let opened_at = self.opened_at_secs.load(Ordering::Relaxed);
            if opened_at == 0 {
                // Circuit is Closed
                return true;
            }
            let now = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .map(|d| d.as_secs())
                .unwrap_or(0);
            if now.saturating_sub(opened_at) >= self.reset_secs {
                // Cooldown elapsed — HalfOpen: allow one probe
                return true;
            }
            // Circuit is Open
            return false;
        }
        #[cfg(not(feature = "self-heal"))]
        true
    }

    /// Record a successful operation. Resets the error count and closes the circuit.
    pub fn record_success(&self) {
        #[cfg(feature = "self-heal")]
        {
            self.error_count.store(0, Ordering::Relaxed);
            self.opened_at_secs.store(0, Ordering::Relaxed);
        }
    }

    /// Record a failed operation. Opens the circuit once the threshold is reached.
    pub fn record_failure(&self) {
        #[cfg(feature = "self-heal")]
        {
            let prev = self.error_count.fetch_add(1, Ordering::Relaxed);
            if prev + 1 >= self.threshold {
                let now = SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .map(|d| d.as_secs())
                    .unwrap_or(1);
                // Only set opened_at if not already open (CAS with 0 → now)
                let _ = self.opened_at_secs.compare_exchange(
                    0,
                    now,
                    Ordering::Relaxed,
                    Ordering::Relaxed,
                );
            }
        }
    }

    /// Return current circuit state.
    #[cfg(feature = "self-heal")]
    pub fn state(&self) -> CircuitState {
        let opened_at = self.opened_at_secs.load(Ordering::Relaxed);
        if opened_at == 0 {
            return CircuitState::Closed;
        }
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or(0);
        if now.saturating_sub(opened_at) >= self.reset_secs {
            CircuitState::HalfOpen
        } else {
            CircuitState::Open
        }
    }
}
