//! Circuit breaker model types.
//!
//! Defines the state machine, failure types, and storage records
//! for per-agent circuit breakers.

use rkyv::{Archive, Deserialize, Serialize};

/// Circuit breaker state machine states.
///
/// - **Closed**: Normal operation, requests are allowed.
/// - **Open**: Circuit has tripped, requests are blocked.
/// - **HalfOpen**: Testing after timeout, one request allowed.
#[derive(Archive, Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[archive(check_bytes)]
pub enum CircuitState {
    /// Normal operation - requests allowed.
    #[default]
    Closed,

    /// Circuit tripped - requests blocked until timeout.
    Open,

    /// Testing state after timeout - one request allowed to test recovery.
    HalfOpen,
}

impl CircuitState {
    /// Human-readable name for the state.
    pub fn name(&self) -> &'static str {
        match self {
            Self::Closed => "closed",
            Self::Open => "open",
            Self::HalfOpen => "half_open",
        }
    }
}

/// Types of failures that trip the circuit breaker.
///
/// Each failure type counts toward the threshold. The type is recorded
/// for metrics and debugging purposes.
#[derive(Archive, Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
#[archive(check_bytes)]
pub enum FailureType {
    /// Invalid cryptographic signature on assertion.
    InvalidSignature,

    /// Malformed input (JSON parsing, field validation).
    InputValidation,

    /// Invalid proof-of-work solution.
    PowError,

    /// Agent exceeded their quota limit.
    QuotaExceeded,

    /// General application error caused by agent.
    ApplicationError,
}

impl FailureType {
    /// Human-readable name for metrics labels.
    pub fn name(&self) -> &'static str {
        match self {
            Self::InvalidSignature => "invalid_signature",
            Self::InputValidation => "input_validation",
            Self::PowError => "pow_error",
            Self::QuotaExceeded => "quota_exceeded",
            Self::ApplicationError => "application_error",
        }
    }

    /// Human-readable description.
    pub fn description(&self) -> &'static str {
        match self {
            Self::InvalidSignature => "Invalid cryptographic signature",
            Self::InputValidation => "Malformed input or validation failure",
            Self::PowError => "Invalid proof-of-work solution",
            Self::QuotaExceeded => "Quota limit exceeded",
            Self::ApplicationError => "Application error attributed to agent",
        }
    }
}

/// Configuration for circuit breaker behavior.
#[derive(Debug, Clone, Copy)]
pub struct CircuitBreakerConfig {
    /// Number of failures required to trip the circuit.
    pub failure_threshold: u32,

    /// Duration in seconds the circuit stays Open before transitioning to HalfOpen.
    pub open_duration_secs: u64,

    /// Time window in seconds for counting failures.
    /// Failures older than this are not counted toward the threshold.
    pub failure_window_secs: u64,

    /// Number of successes in HalfOpen state required to close the circuit.
    pub half_open_success_threshold: u32,
}

impl CircuitBreakerConfig {
    /// Create a new config with custom values.
    pub fn new(
        failure_threshold: u32,
        open_duration_secs: u64,
        failure_window_secs: u64,
        half_open_success_threshold: u32,
    ) -> Self {
        Self {
            failure_threshold,
            open_duration_secs,
            failure_window_secs,
            half_open_success_threshold,
        }
    }
}

impl Default for CircuitBreakerConfig {
    fn default() -> Self {
        Self {
            failure_threshold: 5,           // 5 failures to trip
            open_duration_secs: 30,         // 30 second ban
            failure_window_secs: 60,        // Count failures in last 60 seconds
            half_open_success_threshold: 1, // 1 success to close
        }
    }
}

/// A single failure event recorded against an agent.
#[derive(Archive, Deserialize, Serialize, Debug, Clone, PartialEq)]
#[archive(check_bytes)]
pub struct FailureEvent {
    /// Type of failure.
    pub failure_type: FailureType,

    /// Unix timestamp (seconds) when the failure occurred.
    pub timestamp: u64,
}

/// Persistent circuit breaker record for an agent.
///
/// Stored at `\x00CB:{agent_hex}` for O(1) lookup.
#[derive(Archive, Deserialize, Serialize, Debug, Clone, PartialEq)]
#[archive(check_bytes)]
pub struct CircuitBreakerRecord {
    /// Agent's Ed25519 public key.
    pub agent_id: [u8; 32],

    /// Current circuit state.
    pub state: CircuitState,

    /// Recent failure events (within the failure window).
    /// Pruned when failures age out of the window.
    pub failures: Vec<FailureEvent>,

    /// Total number of times this circuit has tripped (lifetime metric).
    pub trip_count: u64,

    /// Unix timestamp (seconds) when the circuit was last tripped (entered Open state).
    /// Used to calculate timeout for HalfOpen transition.
    pub last_trip_time: Option<u64>,

    /// Unix timestamp (seconds) of the most recent failure.
    pub last_failure_time: Option<u64>,

    /// Number of consecutive successes in HalfOpen state.
    /// Reset when entering HalfOpen, incremented on success.
    pub half_open_successes: u32,
}

impl CircuitBreakerRecord {
    /// Create a new circuit breaker record for an agent.
    ///
    /// Starts in Closed state with no failures.
    pub fn new(agent_id: [u8; 32]) -> Self {
        Self {
            agent_id,
            state: CircuitState::Closed,
            failures: Vec::new(),
            trip_count: 0,
            last_trip_time: None,
            last_failure_time: None,
            half_open_successes: 0,
        }
    }

    /// Add a failure event and prune old failures outside the window.
    ///
    /// Returns the current failure count within the window.
    pub fn add_failure(
        &mut self,
        failure_type: FailureType,
        timestamp: u64,
        window_secs: u64,
    ) -> usize {
        self.failures.push(FailureEvent { failure_type, timestamp });
        self.last_failure_time = Some(timestamp);

        // Prune failures outside the window
        let cutoff = timestamp.saturating_sub(window_secs);
        self.failures.retain(|f| f.timestamp >= cutoff);

        self.failures.len()
    }

    /// Trip the circuit (transition to Open state).
    pub fn trip(&mut self, timestamp: u64) {
        self.state = CircuitState::Open;
        self.last_trip_time = Some(timestamp);
        self.trip_count = self.trip_count.saturating_add(1);
    }

    /// Transition to HalfOpen state for testing.
    pub fn half_open(&mut self) {
        self.state = CircuitState::HalfOpen;
        self.half_open_successes = 0;
    }

    /// Close the circuit (return to normal operation).
    pub fn close(&mut self) {
        self.state = CircuitState::Closed;
        self.failures.clear();
        self.half_open_successes = 0;
    }

    /// Record a success in HalfOpen state.
    ///
    /// Returns `true` if the circuit should close (threshold met).
    pub fn record_half_open_success(&mut self, threshold: u32) -> bool {
        self.half_open_successes = self.half_open_successes.saturating_add(1);
        self.half_open_successes >= threshold
    }

    /// Check if the Open timeout has elapsed.
    pub fn open_timeout_elapsed(&self, current_time: u64, timeout_secs: u64) -> bool {
        match self.last_trip_time {
            Some(trip_time) => current_time >= trip_time.saturating_add(timeout_secs),
            None => true, // No trip time means we shouldn't be in Open state
        }
    }

    /// Get the number of seconds until the Open timeout expires.
    ///
    /// Returns 0 if already elapsed or not in Open state.
    pub fn seconds_until_retry(&self, current_time: u64, timeout_secs: u64) -> u64 {
        match (self.state, self.last_trip_time) {
            (CircuitState::Open, Some(trip_time)) => {
                let expiry = trip_time.saturating_add(timeout_secs);
                expiry.saturating_sub(current_time)
            }
            _ => 0,
        }
    }

    /// Count failures of a specific type within the window.
    pub fn count_failures_by_type(&self, failure_type: FailureType) -> usize {
        self.failures.iter().filter(|f| f.failure_type == failure_type).count()
    }

    /// Get the total failure count within the window.
    pub fn failure_count(&self) -> usize {
        self.failures.len()
    }
}

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

    #[test]
    fn test_circuit_state_default() {
        assert_eq!(CircuitState::default(), CircuitState::Closed);
    }

    #[test]
    fn test_circuit_state_names() {
        assert_eq!(CircuitState::Closed.name(), "closed");
        assert_eq!(CircuitState::Open.name(), "open");
        assert_eq!(CircuitState::HalfOpen.name(), "half_open");
    }

    #[test]
    fn test_failure_type_names() {
        assert_eq!(FailureType::InvalidSignature.name(), "invalid_signature");
        assert_eq!(FailureType::InputValidation.name(), "input_validation");
        assert_eq!(FailureType::PowError.name(), "pow_error");
        assert_eq!(FailureType::QuotaExceeded.name(), "quota_exceeded");
        assert_eq!(FailureType::ApplicationError.name(), "application_error");
    }

    #[test]
    fn test_config_default() {
        let config = CircuitBreakerConfig::default();
        assert_eq!(config.failure_threshold, 5);
        assert_eq!(config.open_duration_secs, 30);
        assert_eq!(config.failure_window_secs, 60);
        assert_eq!(config.half_open_success_threshold, 1);
    }

    #[test]
    fn test_record_new() {
        let agent_id = [1u8; 32];
        let record = CircuitBreakerRecord::new(agent_id);

        assert_eq!(record.agent_id, agent_id);
        assert_eq!(record.state, CircuitState::Closed);
        assert!(record.failures.is_empty());
        assert_eq!(record.trip_count, 0);
        assert!(record.last_trip_time.is_none());
        assert!(record.last_failure_time.is_none());
    }

    #[test]
    fn test_record_add_failure() {
        let mut record = CircuitBreakerRecord::new([1u8; 32]);

        let count = record.add_failure(FailureType::InvalidSignature, 1000, 60);
        assert_eq!(count, 1);
        assert_eq!(record.last_failure_time, Some(1000));

        let count = record.add_failure(FailureType::InputValidation, 1010, 60);
        assert_eq!(count, 2);
    }

    #[test]
    fn test_record_add_failure_prunes_old() {
        let mut record = CircuitBreakerRecord::new([1u8; 32]);

        // Add failures at t=1000, 1010, 1020
        record.add_failure(FailureType::InvalidSignature, 1000, 60);
        record.add_failure(FailureType::InvalidSignature, 1010, 60);
        record.add_failure(FailureType::InvalidSignature, 1020, 60);
        assert_eq!(record.failure_count(), 3);

        // Add failure at t=1070 with 60 second window
        // Only failures >= 1010 should remain
        let count = record.add_failure(FailureType::InvalidSignature, 1070, 60);
        assert_eq!(count, 3); // 1010, 1020, 1070 (1000 pruned)
    }

    #[test]
    fn test_record_trip() {
        let mut record = CircuitBreakerRecord::new([1u8; 32]);

        record.trip(1000);
        assert_eq!(record.state, CircuitState::Open);
        assert_eq!(record.last_trip_time, Some(1000));
        assert_eq!(record.trip_count, 1);

        record.trip(2000);
        assert_eq!(record.trip_count, 2);
    }

    #[test]
    fn test_record_half_open() {
        let mut record = CircuitBreakerRecord::new([1u8; 32]);
        record.trip(1000);

        record.half_open();
        assert_eq!(record.state, CircuitState::HalfOpen);
        assert_eq!(record.half_open_successes, 0);
    }

    #[test]
    fn test_record_close() {
        let mut record = CircuitBreakerRecord::new([1u8; 32]);
        record.add_failure(FailureType::InvalidSignature, 1000, 60);
        record.trip(1000);
        record.half_open();
        record.half_open_successes = 1;

        record.close();
        assert_eq!(record.state, CircuitState::Closed);
        assert!(record.failures.is_empty());
        assert_eq!(record.half_open_successes, 0);
    }

    #[test]
    fn test_record_half_open_success() {
        let mut record = CircuitBreakerRecord::new([1u8; 32]);
        record.half_open();

        // Need 1 success (default threshold)
        let should_close = record.record_half_open_success(1);
        assert!(should_close);
        assert_eq!(record.half_open_successes, 1);

        // Reset and test with higher threshold
        record.half_open();
        assert!(!record.record_half_open_success(3)); // 1 of 3
        assert!(!record.record_half_open_success(3)); // 2 of 3
        assert!(record.record_half_open_success(3)); // 3 of 3
    }

    #[test]
    fn test_record_open_timeout_elapsed() {
        let mut record = CircuitBreakerRecord::new([1u8; 32]);
        record.trip(1000);

        // 30 second timeout
        assert!(!record.open_timeout_elapsed(1010, 30)); // 20 seconds remaining
        assert!(!record.open_timeout_elapsed(1029, 30)); // 1 second remaining
        assert!(record.open_timeout_elapsed(1030, 30)); // Exactly at timeout
        assert!(record.open_timeout_elapsed(1050, 30)); // Past timeout
    }

    #[test]
    fn test_record_seconds_until_retry() {
        let mut record = CircuitBreakerRecord::new([1u8; 32]);
        record.trip(1000);

        // Still in Open state, 30 second timeout
        assert_eq!(record.seconds_until_retry(1010, 30), 20);
        assert_eq!(record.seconds_until_retry(1030, 30), 0);
        assert_eq!(record.seconds_until_retry(1050, 30), 0);

        // After transitioning to HalfOpen
        record.half_open();
        assert_eq!(record.seconds_until_retry(1010, 30), 0);

        // In Closed state
        record.close();
        assert_eq!(record.seconds_until_retry(1010, 30), 0);
    }

    #[test]
    fn test_record_count_failures_by_type() {
        let mut record = CircuitBreakerRecord::new([1u8; 32]);

        record.add_failure(FailureType::InvalidSignature, 1000, 60);
        record.add_failure(FailureType::InvalidSignature, 1010, 60);
        record.add_failure(FailureType::InputValidation, 1020, 60);
        record.add_failure(FailureType::PowError, 1030, 60);

        assert_eq!(record.count_failures_by_type(FailureType::InvalidSignature), 2);
        assert_eq!(record.count_failures_by_type(FailureType::InputValidation), 1);
        assert_eq!(record.count_failures_by_type(FailureType::PowError), 1);
        assert_eq!(record.count_failures_by_type(FailureType::QuotaExceeded), 0);
    }
}
