//! Cryptographic utilities for encrypting and decrypting sensitive data
//!
//! This module provides AES-256-GCM encryption for securing sensitive data such
//! as:
//! - Session user information (name, categories, tags, etc.)
//! - Configuration values (database URLs, API keys, etc.)
//! - Any other sensitive data that needs to be stored securely

use std::collections::HashMap;
use std::env;

use aes_gcm::{
    aead::{Aead, AeadCore, KeyInit, OsRng},
    Aes256Gcm, Key, Nonce,
};
use base64::{engine::general_purpose, Engine as _};
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum CryptoError {
    #[error("Encryption failed: {0}")]
    EncryptionError(String),
    #[error("Decryption failed: {0}")]
    DecryptionError(String),
    #[error("Invalid key format: {0}")]
    InvalidKeyFormat(String),
    #[error("Invalid data format: {0}")]
    InvalidDataFormat(String),
    #[error("Key generation failed: {0}")]
    KeyGenerationError(String),
    #[error("Serialization error: {0}")]
    SerializationError(String),
}

/// Encrypted data container with nonce and ciphertext
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptedData {
    pub nonce: Vec<u8>,
    pub ciphertext: Vec<u8>,
}

/// Crypto service for handling encryption/decryption operations
#[derive(Clone)]
pub struct CryptoService {
    cipher: Aes256Gcm,
    key: Key<Aes256Gcm>,
}

impl std::fmt::Debug for CryptoService {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("CryptoService")
            .field("cipher", &"[REDACTED]")
            .field("key", &"[REDACTED]")
            .finish()
    }
}

impl CryptoService {
    /// Create a new CryptoService with a key from environment or generate a new
    /// one
    pub fn new() -> Result<Self, CryptoError> {
        let key = Self::get_or_generate_key()?;
        let cipher = Aes256Gcm::new(&key);

        Ok(Self { cipher, key })
    }

    /// Create a CryptoService with a specific key
    pub fn with_key(key_bytes: &[u8]) -> Result<Self, CryptoError> {
        if key_bytes.len() != 32 {
            return Err(CryptoError::InvalidKeyFormat(
                "Key must be exactly 32 bytes".to_string(),
            ));
        }

        let key_arr: [u8; 32] = key_bytes.try_into().expect("validated 32 bytes");
        let key = Key::<Aes256Gcm>::from(key_arr);
        let cipher = Aes256Gcm::new(&key);

        Ok(Self { cipher, key })
    }

    /// Get encryption key from environment or generate a new one
    fn get_or_generate_key() -> Result<Key<Aes256Gcm>, CryptoError> {
        // Try to get key from environment first
        if let Ok(key_base64) = env::var("CRYPTO_KEY") {
            match general_purpose::STANDARD.decode(&key_base64) {
                Ok(key_bytes) => {
                    if key_bytes.len() == 32 {
                        let arr: [u8; 32] = key_bytes.as_slice().try_into().expect("checked 32");
                        return Ok(Key::<Aes256Gcm>::from(arr));
                    }
                }
                Err(_) => {
                    tracing::warn!("Invalid CRYPTO_KEY format in environment, generating new key");
                }
            }
        }

        // Generate a new key and warn about it
        tracing::warn!(
            "No valid CRYPTO_KEY found in environment, generating new key. Set CRYPTO_KEY \
             environment variable for production use."
        );
        let key = Aes256Gcm::generate_key(OsRng);
        let key_base64 = general_purpose::STANDARD.encode(&key);
        tracing::info!("Generated crypto key (base64): {}", key_base64);

        Ok(key)
    }

    /// Encrypt data to bytes
    pub fn encrypt_bytes(&self, data: &[u8]) -> Result<EncryptedData, CryptoError> {
        let nonce = Aes256Gcm::generate_nonce(&mut OsRng);

        match self.cipher.encrypt(&nonce, data) {
            Ok(ciphertext) => Ok(EncryptedData {
                nonce: nonce.to_vec(),
                ciphertext,
            }),
            Err(e) => Err(CryptoError::EncryptionError(format!(
                "AES encryption failed: {}",
                e
            ))),
        }
    }

    /// Decrypt bytes from encrypted data
    pub fn decrypt_bytes(&self, encrypted_data: &EncryptedData) -> Result<Vec<u8>, CryptoError> {
        let nonce_arr: [u8; 12] = encrypted_data
            .nonce
            .as_slice()
            .try_into()
            .map_err(|_| CryptoError::DecryptionError("nonce must be 12 bytes".to_string()))?;
        let nonce = Nonce::from(nonce_arr);

        match self
            .cipher
            .decrypt(&nonce, encrypted_data.ciphertext.as_ref())
        {
            Ok(plaintext) => Ok(plaintext),
            Err(e) => Err(CryptoError::DecryptionError(format!(
                "AES decryption failed: {}",
                e
            ))),
        }
    }

    /// Encrypt a string and return base64 encoded result
    pub fn encrypt_string(&self, data: &str) -> Result<String, CryptoError> {
        let encrypted = self.encrypt_bytes(data.as_bytes())?;
        let serialized = serde_json::to_vec(&encrypted)
            .map_err(|e| CryptoError::SerializationError(e.to_string()))?;
        Ok(general_purpose::STANDARD.encode(&serialized))
    }

    /// Decrypt a base64 encoded string
    pub fn decrypt_string(&self, encrypted_base64: &str) -> Result<String, CryptoError> {
        let serialized = general_purpose::STANDARD
            .decode(encrypted_base64)
            .map_err(|e| CryptoError::InvalidDataFormat(format!("Invalid base64: {}", e)))?;

        let encrypted: EncryptedData = serde_json::from_slice(&serialized).map_err(|e| {
            CryptoError::InvalidDataFormat(format!("Invalid encrypted data format: {}", e))
        })?;

        let decrypted_bytes = self.decrypt_bytes(&encrypted)?;
        String::from_utf8(decrypted_bytes)
            .map_err(|e| CryptoError::InvalidDataFormat(format!("Invalid UTF-8: {}", e)))
    }

    /// Encrypt a serializable object and return base64 encoded result
    pub fn encrypt_json<T: Serialize>(&self, data: &T) -> Result<String, CryptoError> {
        let json_bytes =
            serde_json::to_vec(data).map_err(|e| CryptoError::SerializationError(e.to_string()))?;
        let encrypted = self.encrypt_bytes(&json_bytes)?;
        let serialized = serde_json::to_vec(&encrypted)
            .map_err(|e| CryptoError::SerializationError(e.to_string()))?;
        Ok(general_purpose::STANDARD.encode(&serialized))
    }

    /// Decrypt a base64 encoded string to a deserializable object
    pub fn decrypt_json<T: for<'de> Deserialize<'de>>(
        &self,
        encrypted_base64: &str,
    ) -> Result<T, CryptoError> {
        let serialized = general_purpose::STANDARD
            .decode(encrypted_base64)
            .map_err(|e| CryptoError::InvalidDataFormat(format!("Invalid base64: {}", e)))?;

        let encrypted: EncryptedData = serde_json::from_slice(&serialized).map_err(|e| {
            CryptoError::InvalidDataFormat(format!("Invalid encrypted data format: {}", e))
        })?;

        let decrypted_bytes = self.decrypt_bytes(&encrypted)?;
        serde_json::from_slice(&decrypted_bytes)
            .map_err(|e| CryptoError::InvalidDataFormat(format!("Invalid JSON: {}", e)))
    }

    /// Generate a new random key and return it as base64
    pub fn generate_key_base64() -> String {
        let key = Aes256Gcm::generate_key(OsRng);
        general_purpose::STANDARD.encode(&key)
    }

    /// Get the current key as base64 (for backup/recovery purposes)
    pub fn get_key_base64(&self) -> String {
        general_purpose::STANDARD.encode(&self.key)
    }
}

/// Encrypted session data containing user information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptedSessionData {
    pub user_id: String,
    pub email: String,
    pub username: String,
    pub display_name: Option<String>,
    pub roles: Vec<String>,
    pub categories: Vec<String>,
    pub tags: Vec<String>,
    pub preferences: HashMap<String, String>,
    pub created_at: i64,
    pub expires_at: i64,
}

/// Encrypted configuration values for sensitive data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptedConfig {
    pub database_url: Option<String>,
    pub jwt_secret: Option<String>,
    pub oauth_secrets: HashMap<String, String>,
    pub email_password: Option<String>,
    pub api_keys: HashMap<String, String>,
    pub other_secrets: HashMap<String, String>,
}

impl EncryptedConfig {
    pub fn new() -> Self {
        Self {
            database_url: None,
            jwt_secret: None,
            oauth_secrets: HashMap::new(),
            email_password: None,
            api_keys: HashMap::new(),
            other_secrets: HashMap::new(),
        }
    }

    /// Set database URL
    pub fn with_database_url(mut self, url: String) -> Self {
        self.database_url = Some(url);
        self
    }

    /// Set JWT secret
    pub fn with_jwt_secret(mut self, secret: String) -> Self {
        self.jwt_secret = Some(secret);
        self
    }

    /// Add OAuth secret
    pub fn with_oauth_secret(mut self, provider: String, secret: String) -> Self {
        self.oauth_secrets.insert(provider, secret);
        self
    }

    /// Set email password
    pub fn with_email_password(mut self, password: String) -> Self {
        self.email_password = Some(password);
        self
    }

    /// Add API key
    pub fn with_api_key(mut self, name: String, key: String) -> Self {
        self.api_keys.insert(name, key);
        self
    }

    /// Add other secret
    pub fn with_secret(mut self, name: String, secret: String) -> Self {
        self.other_secrets.insert(name, secret);
        self
    }
}

impl Default for EncryptedConfig {
    fn default() -> Self {
        Self::new()
    }
}

/// Utility functions for common encryption operations
pub mod utils {
    use chrono::Utc;
    use rustelo_core_lib::auth::User;

    use super::*;

    /// Convert a User struct to EncryptedSessionData
    pub fn user_to_session_data(user: &User, expires_in_seconds: i64) -> EncryptedSessionData {
        let now = Utc::now().timestamp();

        EncryptedSessionData {
            user_id: user.id.to_string(),
            email: user.email.clone(),
            username: user.username.clone(),
            display_name: user.display_name.clone(),
            roles: user.roles.iter().map(|r| format!("{:?}", r)).collect(),
            categories: user.profile.categories.clone(),
            tags: user.profile.tags.clone(),
            preferences: user.profile.preferences.clone(),
            created_at: now,
            expires_at: now + expires_in_seconds,
        }
    }

    /// Check if session data is expired
    pub fn is_session_expired(session_data: &EncryptedSessionData) -> bool {
        let now = Utc::now().timestamp();
        session_data.expires_at < now
    }

    /// Encrypt user session data
    pub fn encrypt_user_session(
        crypto: &CryptoService,
        user: &User,
        expires_in_seconds: i64,
    ) -> Result<String, CryptoError> {
        let session_data = user_to_session_data(user, expires_in_seconds);
        crypto.encrypt_json(&session_data)
    }

    /// Decrypt user session data
    pub fn decrypt_user_session(
        crypto: &CryptoService,
        encrypted_session: &str,
    ) -> Result<EncryptedSessionData, CryptoError> {
        let session_data: EncryptedSessionData = crypto.decrypt_json(encrypted_session)?;

        // Check if expired
        if is_session_expired(&session_data) {
            return Err(CryptoError::DecryptionError("Session expired".to_string()));
        }

        Ok(session_data)
    }

    /// Mask sensitive data in logs (show only first and last 4 characters)
    pub fn mask_sensitive_data(data: &str) -> String {
        if data.len() <= 8 {
            "*".repeat(data.len())
        } else {
            format!("{}****{}", &data[..4], &data[data.len() - 4..])
        }
    }
}

/// Session management module for encrypted sessions
pub mod session;

/// Configuration management module for encrypted config values
pub mod config;

/// Integration examples and utilities for using crypto with auth system
pub mod integration;

#[cfg(test)]
mod tests {
    use chrono::Utc;
    use rustelo_core_lib::auth::{Role, User, UserProfile};
    use uuid::Uuid;

    use super::*;

    fn create_test_user() -> User {
        User {
            id: Uuid::new_v4(),
            email: "test@example.com".to_string(),
            username: "testuser".to_string(),
            display_name: Some("Test User".to_string()),
            avatar_url: None,
            roles: vec![Role::User],
            is_active: true,
            email_verified: true,
            created_at: Utc::now(),
            updated_at: Utc::now(),
            last_login: None,
            two_factor_enabled: false,
            profile: UserProfile {
                first_name: Some("Test".to_string()),
                last_name: Some("User".to_string()),
                bio: None,
                timezone: Some("UTC".to_string()),
                locale: Some("en".to_string()),
                preferences: [("theme".to_string(), "dark".to_string())].into(),
                categories: vec!["tech".to_string(), "programming".to_string()],
                tags: vec!["rust".to_string(), "web".to_string()],
            },
        }
    }

    #[test]
    fn test_crypto_service_creation() {
        let crypto = CryptoService::new();
        assert!(crypto.is_ok());
    }

    #[test]
    fn test_string_encryption_decryption() {
        let crypto = CryptoService::new().expect("Failed to create crypto service");
        let original = "Hello, World!";

        let encrypted = crypto
            .encrypt_string(original)
            .expect("Failed to encrypt test data");
        let decrypted = crypto
            .decrypt_string(&encrypted)
            .expect("Failed to decrypt test data");

        assert_eq!(original, decrypted);
    }

    #[test]
    fn test_json_encryption_decryption() {
        let crypto = CryptoService::new().expect("Failed to create crypto service");
        let original_config = EncryptedConfig::new()
            .with_database_url("postgresql://user:pass@localhost/db".to_string())
            .with_jwt_secret("super-secret-key".to_string());

        let encrypted = crypto.encrypt_json(&original_config).unwrap();
        let decrypted: EncryptedConfig = crypto.decrypt_json(&encrypted).unwrap();

        assert_eq!(original_config.database_url, decrypted.database_url);
        assert_eq!(original_config.jwt_secret, decrypted.jwt_secret);
    }

    #[test]
    fn test_user_session_encryption() {
        let crypto = CryptoService::new().expect("Failed to create crypto service");
        let user = create_test_user();

        let encrypted_session = super::utils::encrypt_user_session(&crypto, &user, 3600).unwrap();
        let decrypted_session =
            super::utils::decrypt_user_session(&crypto, &encrypted_session).unwrap();

        assert_eq!(user.id.to_string(), decrypted_session.user_id);
        assert_eq!(user.email, decrypted_session.email);
        assert_eq!(user.username, decrypted_session.username);
        assert_eq!(user.profile.categories, decrypted_session.categories);
        assert_eq!(user.profile.tags, decrypted_session.tags);
    }

    #[test]
    fn test_key_generation() {
        let key1 = CryptoService::generate_key_base64();
        let key2 = CryptoService::generate_key_base64();

        assert_ne!(key1, key2);
        assert_eq!(key1.len(), 44); // Base64 encoded 32 bytes
    }

    #[test]
    fn test_mask_sensitive_data() {
        assert_eq!(super::utils::mask_sensitive_data("short"), "*****");
        assert_eq!(
            super::utils::mask_sensitive_data("postgresql://user:password@localhost/db"),
            "post****t/db"
        );
        assert_eq!(
            super::utils::mask_sensitive_data("secret123"),
            "secr****t123"
        );
    }

    #[test]
    fn test_with_custom_key() {
        let key_bytes = [0u8; 32];
        let crypto = CryptoService::with_key(&key_bytes)
            .expect("Failed to create crypto service with custom key");

        let original = "test data";
        let encrypted = crypto
            .encrypt_string(original)
            .expect("Failed to encrypt with custom key");
        let decrypted = crypto
            .decrypt_string(&encrypted)
            .expect("Failed to decrypt with custom key");

        assert_eq!(original, decrypted);
    }

    #[test]
    fn test_invalid_key_size() {
        let key_bytes = [0u8; 16]; // Wrong size
        let result = CryptoService::with_key(&key_bytes);
        assert!(result.is_err());
    }

    #[test]
    fn test_session_expiry() {
        let crypto = CryptoService::new().expect("Failed to create crypto service");
        let user = create_test_user();

        // Create expired session (expires in -1 seconds)
        let encrypted_session = super::utils::encrypt_user_session(&crypto, &user, -1).unwrap();
        let result = super::utils::decrypt_user_session(&crypto, &encrypted_session);

        assert!(result.is_err());
    }
}
