//! Secret management with automatic zeroization.
//!
//! Uses the `secrecy` crate to prevent accidental exposure of secrets
//! in logs, debug output, or error messages.

use zeroize::Zeroize;

/// Secret access token with automatic zeroization.
pub type SecretAccessToken = secrecy::SecretBox<AccessToken>;

/// Access token with expiration time.
#[derive(Clone, Default)]
pub struct AccessToken {
    token: String,
    expires_at: Option<chrono::DateTime<chrono::Utc>>,
}

impl AccessToken {
    /// Create a new access token.
    #[must_use]
    pub const fn new(token: String) -> Self {
        Self {
            token,
            expires_at: None,
        }
    }

    /// Create a new access token with expiration.
    #[must_use]
    pub const fn with_expiry(token: String, expires_at: chrono::DateTime<chrono::Utc>) -> Self {
        Self {
            token,
            expires_at: Some(expires_at),
        }
    }

    /// Get the token value.
    #[must_use]
    pub fn value(&self) -> &str {
        &self.token
    }

    /// Check if the token is expired.
    #[must_use]
    pub fn is_expired(&self) -> bool {
        self.expires_at.is_some_and(|exp| chrono::Utc::now() >= exp)
    }

    /// Get the expiration time.
    #[must_use]
    pub const fn expires_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
        self.expires_at
    }
}

impl Zeroize for AccessToken {
    fn zeroize(&mut self) {
        self.token.zeroize();
    }
}

impl secrecy::CloneableSecret for AccessToken {}

impl Drop for AccessToken {
    fn drop(&mut self) {
        self.zeroize();
    }
}

#[cfg(test)]
mod tests {
    use chrono::{Duration, Utc};
    use secrecy::{ExposeSecret, SecretString};

    use super::*;

    #[test]
    fn test_secret_string_redacted() {
        let secret: SecretString = "sensitive-data".to_string().into();
        let debug_output = format!("{:?}", secret);
        assert!(!debug_output.contains("sensitive-data"));
    }

    #[test]
    fn test_secret_string_expose() {
        let secret: SecretString = "my-secret".to_string().into();
        assert_eq!(secret.expose_secret(), "my-secret");
    }

    #[test]
    fn test_access_token_not_expired() {
        let token = AccessToken::with_expiry("token".into(), Utc::now() + Duration::hours(1));
        assert!(!token.is_expired());
    }

    #[test]
    fn test_access_token_expired() {
        let token = AccessToken::with_expiry("token".into(), Utc::now() - Duration::hours(1));
        assert!(token.is_expired());
    }

    #[test]
    fn test_access_token_no_expiry() {
        let token = AccessToken::new("token".into());
        assert!(!token.is_expired());
        assert!(token.expires_at().is_none());
    }
}
