//! Data models for API key storage.

/// API key role determining access level.
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, Copy, PartialEq, Eq)]
#[archive(check_bytes)]
#[repr(u8)]
pub enum ApiKeyRole {
    /// Read-only access: GET endpoints only.
    ReadOnly = 0,
    /// Write access: Assert, Vote, Supersede operations.
    WriteAgent = 1,
    /// Full admin access: includes `/v1/admin/*` endpoints.
    Admin = 2,
}

impl ApiKeyRole {
    /// Check if this role allows write operations.
    pub fn can_write(&self) -> bool {
        matches!(self, Self::WriteAgent | Self::Admin)
    }

    /// Check if this role allows admin operations.
    pub fn can_admin(&self) -> bool {
        matches!(self, Self::Admin)
    }

    /// Convert role to a string representation.
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::ReadOnly => "read_only",
            Self::WriteAgent => "write_agent",
            Self::Admin => "admin",
        }
    }
}

/// Error type for parsing ApiKeyRole from a string.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseApiKeyRoleError(String);

impl std::fmt::Display for ParseApiKeyRoleError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Invalid role '{}'. Must be 'read_only', 'write_agent', or 'admin'", self.0)
    }
}

impl std::error::Error for ParseApiKeyRoleError {}

impl std::str::FromStr for ApiKeyRole {
    type Err = ParseApiKeyRoleError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "read_only" | "readonly" => Ok(Self::ReadOnly),
            "write_agent" | "writeagent" | "write" => Ok(Self::WriteAgent),
            "admin" => Ok(Self::Admin),
            _ => Err(ParseApiKeyRoleError(s.to_string())),
        }
    }
}

impl std::fmt::Display for ApiKeyRole {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

/// Stored API key record.
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq)]
#[archive(check_bytes)]
pub struct ApiKeyRecord {
    /// BLAKE3 hash of the raw API key.
    pub key_hash: [u8; 32],
    /// First 12 characters of the key for identification (e.g., "steme_live_a").
    pub key_prefix: String,
    /// Access role for this key.
    pub role: ApiKeyRole,
    /// Per-key rate limit in requests per hour (None = use default).
    pub rate_limit: Option<u64>,
    /// Human-readable name for this key.
    pub label: String,
    /// Unix timestamp when the key was created.
    pub created_at: u64,
    /// Unix timestamp when the key was last used.
    pub last_used_at: u64,
    /// Optional Unix timestamp when the key expires.
    pub expires_at: Option<u64>,
    /// Whether the key is currently enabled.
    pub enabled: bool,
}

impl ApiKeyRecord {
    /// Create a new API key record.
    pub fn new(
        key_hash: [u8; 32],
        key_prefix: String,
        role: ApiKeyRole,
        label: String,
        created_at: u64,
    ) -> Self {
        Self {
            key_hash,
            key_prefix,
            role,
            rate_limit: None,
            label,
            created_at,
            last_used_at: 0,
            expires_at: None,
            enabled: true,
        }
    }

    /// Check if this key has expired.
    pub fn is_expired(&self, now: u64) -> bool {
        self.expires_at.is_some_and(|exp| now > exp)
    }

    /// Check if this key is valid (enabled and not expired).
    pub fn is_valid(&self, now: u64) -> bool {
        self.enabled && !self.is_expired(now)
    }

    /// Get the effective rate limit for this key.
    pub fn effective_rate_limit(&self, default: u64) -> u64 {
        self.rate_limit.unwrap_or(default)
    }
}

/// Result of a rate limit check.
#[derive(Debug, Clone)]
pub struct RateLimitResult {
    /// Whether the request is allowed.
    pub allowed: bool,
    /// Remaining requests in this window.
    pub remaining: u64,
    /// Total limit for this window.
    pub limit: u64,
    /// Unix timestamp when this window resets.
    pub reset_at: u64,
}
