//! Admission control data models.
//!
//! These types represent the admission status of an agent and the result of
//! admission checks. They are designed to be easily serialized for API responses.

use stemedb_core::types::{PowError, TrustTier, BASE_QUOTA_LIMIT};

/// Current admission status for an agent.
///
/// This snapshot represents the agent's standing in the admission control system
/// at a specific point in time. It includes all information needed by clients
/// to understand their quotas and PoW requirements.
#[derive(Debug, Clone, PartialEq)]
pub struct AdmissionStatus {
    /// The agent's trust tier based on their reputation score.
    pub tier: TrustTier,

    /// The agent's current trust score (0.0 to 1.0).
    pub trust_score: f32,

    /// Total number of assertions made by this agent.
    pub assertions_count: u64,

    /// Required PoW difficulty in bits (0 = exempt).
    pub pow_difficulty: u8,

    /// Whether PoW is required for this agent's next submission.
    pub pow_required: bool,

    /// Base quota limit (before tier multiplier).
    pub base_quota_limit: u64,

    /// Effective quota limit after tier multiplier.
    pub effective_quota_limit: u64,

    /// Quota multiplier for this tier.
    pub quota_multiplier: f32,
}

impl AdmissionStatus {
    /// Create a new admission status from trust rank data.
    ///
    /// # Arguments
    /// * `trust_score` - Agent's trust score (0.0-1.0)
    /// * `assertions_count` - Number of assertions made
    /// * `pow_difficulty` - Required PoW difficulty in bits
    pub fn new(trust_score: f32, assertions_count: u64, pow_difficulty: u8) -> Self {
        let tier = TrustTier::from_score(trust_score);
        let pow_required = pow_difficulty > 0;
        let quota_multiplier = tier.quota_multiplier();
        let base_quota_limit = BASE_QUOTA_LIMIT;
        let effective_quota_limit = tier.effective_quota_limit();

        Self {
            tier,
            trust_score,
            assertions_count,
            pow_difficulty,
            pow_required,
            base_quota_limit,
            effective_quota_limit,
            quota_multiplier,
        }
    }

    /// Create a status for a new/unknown agent with default values.
    ///
    /// New agents start at:
    /// - Trust score: 0.5 (Verified tier)
    /// - Assertions: 0
    /// - PoW difficulty: 16 bits (first 10 assertions)
    pub fn new_agent(initial_difficulty: u8) -> Self {
        Self::new(0.5, 0, initial_difficulty)
    }
}

/// Result of an admission check.
///
/// This enum represents the three possible outcomes when checking admission:
/// 1. Admitted - The agent can proceed (no PoW required, or valid PoW provided)
/// 2. PowRequired - The agent must provide a PoW proof (HTTP 428)
/// 3. PowFailed - The agent provided an invalid PoW proof (HTTP 428 with error)
#[derive(Debug, Clone)]
pub enum AdmissionStatusResult {
    /// Agent is admitted, can proceed with the request.
    Admitted(AdmissionStatus),

    /// Agent must provide proof-of-work to proceed.
    /// The status contains the required difficulty.
    PowRequired(AdmissionStatus),

    /// Agent provided an invalid proof-of-work.
    /// The status contains the required difficulty for retry.
    PowFailed {
        /// The agent's current status (for building retry response).
        status: AdmissionStatus,
        /// The specific error that caused verification to fail.
        error: PowError,
    },
}

impl AdmissionStatusResult {
    /// Check if the agent is admitted.
    #[must_use]
    pub fn is_admitted(&self) -> bool {
        matches!(self, AdmissionStatusResult::Admitted(_))
    }

    /// Check if proof-of-work is required.
    #[must_use]
    pub fn requires_pow(&self) -> bool {
        matches!(self, AdmissionStatusResult::PowRequired(_))
    }

    /// Check if the proof-of-work verification failed.
    #[must_use]
    pub fn pow_failed(&self) -> bool {
        matches!(self, AdmissionStatusResult::PowFailed { .. })
    }

    /// Get the admission status regardless of outcome.
    #[must_use]
    pub fn status(&self) -> &AdmissionStatus {
        match self {
            AdmissionStatusResult::Admitted(s) => s,
            AdmissionStatusResult::PowRequired(s) => s,
            AdmissionStatusResult::PowFailed { status, .. } => status,
        }
    }

    /// Get the PoW error if verification failed.
    #[must_use]
    pub fn pow_error(&self) -> Option<&PowError> {
        match self {
            AdmissionStatusResult::PowFailed { error, .. } => Some(error),
            _ => None,
        }
    }
}

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

    #[test]
    fn test_admission_status_new() {
        let status = AdmissionStatus::new(0.5, 10, 1);

        assert_eq!(status.tier, TrustTier::Verified);
        assert!((status.trust_score - 0.5).abs() < f32::EPSILON);
        assert_eq!(status.assertions_count, 10);
        assert_eq!(status.pow_difficulty, 1);
        assert!(status.pow_required);
        assert_eq!(status.base_quota_limit, 10_000);
        assert_eq!(status.effective_quota_limit, 10_000);
        assert!((status.quota_multiplier - 1.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_admission_status_new_agent() {
        let status = AdmissionStatus::new_agent(16);

        assert_eq!(status.tier, TrustTier::Verified);
        assert!((status.trust_score - 0.5).abs() < f32::EPSILON);
        assert_eq!(status.assertions_count, 0);
        assert_eq!(status.pow_difficulty, 16);
        assert!(status.pow_required);
    }

    #[test]
    fn test_admission_status_no_pow_required() {
        let status = AdmissionStatus::new(0.7, 100, 0);

        assert_eq!(status.tier, TrustTier::Trusted);
        assert!(!status.pow_required);
        assert_eq!(status.pow_difficulty, 0);
        assert_eq!(status.effective_quota_limit, 20_000);
    }

    #[test]
    fn test_admission_status_result_is_admitted() {
        let status = AdmissionStatus::new(0.5, 50, 0);
        let result = AdmissionStatusResult::Admitted(status);

        assert!(result.is_admitted());
        assert!(!result.requires_pow());
        assert!(!result.pow_failed());
    }

    #[test]
    fn test_admission_status_result_pow_required() {
        let status = AdmissionStatus::new(0.3, 5, 16);
        let result = AdmissionStatusResult::PowRequired(status);

        assert!(!result.is_admitted());
        assert!(result.requires_pow());
        assert!(!result.pow_failed());
    }

    #[test]
    fn test_admission_status_result_pow_failed() {
        let status = AdmissionStatus::new(0.3, 5, 16);
        let error = PowError::InsufficientDifficulty { required: 16, found: 8 };
        let result = AdmissionStatusResult::PowFailed { status, error };

        assert!(!result.is_admitted());
        assert!(!result.requires_pow());
        assert!(result.pow_failed());
        assert!(matches!(
            result.pow_error(),
            Some(PowError::InsufficientDifficulty { required: 16, found: 8 })
        ));
    }

    #[test]
    fn test_tier_quota_calculation() {
        // Untrusted: 0.1x = 1,000
        let status = AdmissionStatus::new(0.1, 0, 16);
        assert_eq!(status.effective_quota_limit, 1_000);

        // Limited: 0.5x = 5,000
        let status = AdmissionStatus::new(0.4, 0, 16);
        assert_eq!(status.effective_quota_limit, 5_000);

        // Authority: 10.0x = 100,000
        let status = AdmissionStatus::new(0.95, 0, 0);
        assert_eq!(status.effective_quota_limit, 100_000);
    }
}
