//! Encrypted configuration module for handling sensitive configuration values
//!
//! This module provides functionality to encrypt and decrypt sensitive
//! configuration values like database URLs, API keys, OAuth secrets, etc. It
//! extends the existing configuration system to support encrypted values.

use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::Path;
use std::sync::Arc;

use serde::{Deserialize, Serialize};
use tracing::{debug, info, warn};

use crate::crypto::{CryptoError, CryptoService};

/// Configuration value that can be either plain text or encrypted
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ConfigValue {
    /// Plain text value (for non-sensitive data)
    Plain(String),
    /// Encrypted value with metadata
    Encrypted {
        /// Base64 encoded encrypted data
        encrypted: String,
        /// Hint about what this value contains (for debugging)
        hint: Option<String>,
        /// Timestamp when encrypted
        encrypted_at: Option<i64>,
    },
}

impl ConfigValue {
    /// Create a new plain text config value
    pub fn plain(value: String) -> Self {
        Self::Plain(value)
    }

    /// Create a new encrypted config value
    pub fn encrypted(encrypted: String, hint: Option<String>, encrypted_at: Option<i64>) -> Self {
        Self::Encrypted {
            encrypted,
            hint,
            encrypted_at,
        }
    }

    /// Get the decrypted value
    pub fn decrypt(&self, crypto: &CryptoService) -> Result<String, CryptoError> {
        match self {
            Self::Plain(value) => Ok(value.clone()),
            Self::Encrypted { encrypted, .. } => crypto.decrypt_string(encrypted),
        }
    }

    /// Check if this value is encrypted
    pub fn is_encrypted(&self) -> bool {
        matches!(self, Self::Encrypted { .. })
    }

    /// Get hint about the encrypted value
    pub fn hint(&self) -> Option<&str> {
        match self {
            Self::Plain(_) => None,
            Self::Encrypted { hint, .. } => hint.as_deref(),
        }
    }
}

/// Encrypted configuration store
#[derive(Debug, Clone)]
pub struct EncryptedConfigStore {
    crypto: Arc<CryptoService>,
    config_file: Option<String>,
    values: HashMap<String, ConfigValue>,
}

impl EncryptedConfigStore {
    /// Create a new encrypted config store
    pub fn new(crypto: Arc<CryptoService>) -> Self {
        Self {
            crypto,
            config_file: None,
            values: HashMap::new(),
        }
    }

    /// Create a new encrypted config store with a config file
    pub fn with_file(crypto: Arc<CryptoService>, config_file: String) -> Self {
        Self {
            crypto,
            config_file: Some(config_file),
            values: HashMap::new(),
        }
    }

    /// Load encrypted config from file
    pub async fn load_from_file(&mut self, file_path: &str) -> Result<(), CryptoError> {
        if !Path::new(file_path).exists() {
            debug!("Encrypted config file does not exist: {}", file_path);
            return Ok(());
        }

        let content = fs::read_to_string(file_path).map_err(|e| {
            CryptoError::InvalidDataFormat(format!("Failed to read config file: {}", e))
        })?;

        let values: HashMap<String, ConfigValue> = serde_json::from_str(&content).map_err(|e| {
            CryptoError::InvalidDataFormat(format!("Failed to parse config file: {}", e))
        })?;

        self.values = values;
        self.config_file = Some(file_path.to_string());

        info!("Loaded {} encrypted config values", self.values.len());
        Ok(())
    }

    /// Save encrypted config to file
    pub async fn save_to_file(&self, file_path: &str) -> Result<(), CryptoError> {
        let content = serde_json::to_string_pretty(&self.values).map_err(|e| {
            CryptoError::SerializationError(format!("Failed to serialize config: {}", e))
        })?;

        fs::write(file_path, content).map_err(|e| {
            CryptoError::InvalidDataFormat(format!("Failed to write config file: {}", e))
        })?;

        info!(
            "Saved {} encrypted config values to {}",
            self.values.len(),
            file_path
        );
        Ok(())
    }

    /// Set a plain text value
    pub fn set_plain(&mut self, key: &str, value: String) {
        self.values
            .insert(key.to_string(), ConfigValue::plain(value));
    }

    /// Set an encrypted value
    pub fn set_encrypted(
        &mut self,
        key: &str,
        value: String,
        hint: Option<String>,
    ) -> Result<(), CryptoError> {
        let encrypted = self.crypto.encrypt_string(&value)?;
        let encrypted_at = Some(chrono::Utc::now().timestamp());

        self.values.insert(
            key.to_string(),
            ConfigValue::encrypted(encrypted, hint, encrypted_at),
        );
        Ok(())
    }

    /// Get a decrypted value
    pub fn get(&self, key: &str) -> Result<Option<String>, CryptoError> {
        match self.values.get(key) {
            Some(config_value) => Ok(Some(config_value.decrypt(&self.crypto)?)),
            None => Ok(None),
        }
    }

    /// Get a decrypted value or return default
    pub fn get_or_default(&self, key: &str, default: &str) -> Result<String, CryptoError> {
        Ok(self.get(key)?.unwrap_or_else(|| default.to_string()))
    }

    /// Get a decrypted value or get from environment
    pub fn get_or_env(&self, key: &str, env_key: &str) -> Result<Option<String>, CryptoError> {
        if let Some(value) = self.get(key)? {
            Ok(Some(value))
        } else {
            Ok(env::var(env_key).ok())
        }
    }

    /// Check if a key exists
    pub fn contains_key(&self, key: &str) -> bool {
        self.values.contains_key(key)
    }

    /// Get all keys
    pub fn keys(&self) -> Vec<String> {
        self.values.keys().cloned().collect()
    }

    /// Remove a value
    pub fn remove(&mut self, key: &str) -> Option<ConfigValue> {
        self.values.remove(key)
    }

    /// Clear all values
    pub fn clear(&mut self) {
        self.values.clear();
    }

    /// Get encryption status for all values
    pub fn get_encryption_status(&self) -> HashMap<String, bool> {
        self.values
            .iter()
            .map(|(k, v)| (k.clone(), v.is_encrypted()))
            .collect()
    }

    /// Encrypt a plain text value in place
    pub fn encrypt_value(&mut self, key: &str, hint: Option<String>) -> Result<(), CryptoError> {
        if let Some(config_value) = self.values.get(key) {
            if let ConfigValue::Plain(plain_value) = config_value {
                let encrypted = self.crypto.encrypt_string(plain_value)?;
                let encrypted_at = Some(chrono::Utc::now().timestamp());

                self.values.insert(
                    key.to_string(),
                    ConfigValue::encrypted(encrypted, hint, encrypted_at),
                );
            }
        }
        Ok(())
    }

    /// Decrypt an encrypted value in place (convert to plain text)
    pub fn decrypt_value(&mut self, key: &str) -> Result<(), CryptoError> {
        if let Some(config_value) = self.values.get(key) {
            if let ConfigValue::Encrypted { encrypted, .. } = config_value {
                let plain_value = self.crypto.decrypt_string(encrypted)?;
                self.values
                    .insert(key.to_string(), ConfigValue::plain(plain_value));
            }
        }
        Ok(())
    }

    /// Auto-save to file if configured
    pub async fn auto_save(&self) -> Result<(), CryptoError> {
        if let Some(file_path) = &self.config_file {
            self.save_to_file(file_path).await?;
        }
        Ok(())
    }

    /// Load from standard environment variables and encrypt them
    pub async fn load_from_env(
        &mut self,
        env_mappings: &[(&str, &str)],
    ) -> Result<(), CryptoError> {
        for (config_key, env_key) in env_mappings {
            if let Ok(env_value) = env::var(env_key) {
                let hint = Some(format!("From env: {}", env_key));
                self.set_encrypted(config_key, env_value, hint)?;
                info!("Loaded and encrypted {} from environment", config_key);
            }
        }
        Ok(())
    }

    /// Initialize with common sensitive configuration values
    pub async fn init_with_common_secrets(&mut self) -> Result<(), CryptoError> {
        let env_mappings = vec![
            ("database_url", "DATABASE_URL"),
            ("jwt_secret", "JWT_SECRET"),
            ("session_secret", "SESSION_SECRET"),
            ("oauth_google_client_secret", "GOOGLE_CLIENT_SECRET"),
            ("oauth_github_client_secret", "GITHUB_CLIENT_SECRET"),
            ("smtp_password", "SMTP_PASSWORD"),
            ("redis_password", "REDIS_PASSWORD"),
            ("stripe_secret_key", "STRIPE_SECRET_KEY"),
            ("aws_secret_access_key", "AWS_SECRET_ACCESS_KEY"),
            ("openai_api_key", "OPENAI_API_KEY"),
        ];

        self.load_from_env(&env_mappings).await?;
        Ok(())
    }
}

/// Builder for encrypted configuration
pub struct EncryptedConfigBuilder {
    crypto: Arc<CryptoService>,
    config_file: Option<String>,
    auto_load_env: bool,
    env_mappings: Vec<(String, String)>,
}

impl EncryptedConfigBuilder {
    /// Create a new builder
    pub fn new(crypto: Arc<CryptoService>) -> Self {
        Self {
            crypto,
            config_file: None,
            auto_load_env: false,
            env_mappings: Vec::new(),
        }
    }

    /// Set config file path
    pub fn with_file(mut self, file_path: String) -> Self {
        self.config_file = Some(file_path);
        self
    }

    /// Enable auto-loading from environment
    pub fn with_auto_load_env(mut self) -> Self {
        self.auto_load_env = true;
        self
    }

    /// Add environment variable mapping
    pub fn with_env_mapping(mut self, config_key: String, env_key: String) -> Self {
        self.env_mappings.push((config_key, env_key));
        self
    }

    /// Build the encrypted config store
    pub async fn build(self) -> Result<EncryptedConfigStore, CryptoError> {
        let mut store = if let Some(file_path) = self.config_file {
            let mut store = EncryptedConfigStore::with_file(self.crypto, file_path.clone());
            store.load_from_file(&file_path).await?;
            store
        } else {
            EncryptedConfigStore::new(self.crypto)
        };

        if self.auto_load_env {
            store.init_with_common_secrets().await?;
        }

        for (config_key, env_key) in self.env_mappings {
            if let Ok(env_value) = env::var(&env_key) {
                let hint = Some(format!("From env: {}", env_key));
                store.set_encrypted(&config_key, env_value, hint)?;
            }
        }

        Ok(store)
    }
}

/// Utility functions for encrypted configuration
pub mod utils {
    use super::*;

    /// Migrate plain text config to encrypted config
    pub async fn migrate_plain_to_encrypted(
        crypto: &CryptoService,
        plain_config: &HashMap<String, String>,
        sensitive_keys: &[&str],
    ) -> Result<HashMap<String, ConfigValue>, CryptoError> {
        let mut encrypted_config = HashMap::new();

        for (key, value) in plain_config {
            if sensitive_keys.contains(&key.as_str()) {
                // Encrypt sensitive values
                let encrypted = crypto.encrypt_string(value)?;
                let encrypted_at = Some(chrono::Utc::now().timestamp());
                encrypted_config.insert(
                    key.clone(),
                    ConfigValue::encrypted(
                        encrypted,
                        Some("Migrated from plain text".to_string()),
                        encrypted_at,
                    ),
                );
                info!("Encrypted sensitive config key: {}", key);
            } else {
                // Keep non-sensitive values as plain text
                encrypted_config.insert(key.clone(), ConfigValue::plain(value.clone()));
            }
        }

        Ok(encrypted_config)
    }

    /// Validate encrypted config integrity
    pub fn validate_encrypted_config(
        crypto: &CryptoService,
        config: &HashMap<String, ConfigValue>,
    ) -> Result<Vec<String>, CryptoError> {
        let mut errors = Vec::new();

        for (key, value) in config {
            if let ConfigValue::Encrypted { encrypted, .. } = value {
                if let Err(e) = crypto.decrypt_string(encrypted) {
                    errors.push(format!("Failed to decrypt {}: {}", key, e));
                }
            }
        }

        if errors.is_empty() {
            info!("All encrypted config values are valid");
        } else {
            warn!("Found {} invalid encrypted config values", errors.len());
        }

        Ok(errors)
    }

    /// Get decrypted database URL with fallback
    pub fn get_database_url(config_store: &EncryptedConfigStore) -> Result<String, CryptoError> {
        // Try encrypted config first
        if let Some(url) = config_store.get("database_url")? {
            return Ok(url);
        }

        // Fallback to environment variable
        if let Ok(url) = env::var("DATABASE_URL") {
            return Ok(url);
        }

        // Default development database URL
        Ok("postgresql://dev:dev@localhost:5432/rustelo_dev".to_string())
    }

    /// Get decrypted JWT secret with fallback
    pub fn get_jwt_secret(config_store: &EncryptedConfigStore) -> Result<String, CryptoError> {
        // Try encrypted config first
        if let Some(secret) = config_store.get("jwt_secret")? {
            return Ok(secret);
        }

        // Fallback to environment variable
        if let Ok(secret) = env::var("JWT_SECRET") {
            return Ok(secret);
        }

        // Generate a warning for development
        warn!("No JWT secret found, using default development secret");
        Ok("your-super-secret-jwt-key-change-this-in-production".to_string())
    }

    /// Mask sensitive value for logging
    pub fn mask_for_logging(value: &str, show_chars: usize) -> String {
        if value.len() <= show_chars * 2 {
            "*".repeat(value.len())
        } else {
            format!(
                "{}{}{}",
                &value[..show_chars],
                "*".repeat(value.len() - show_chars * 2),
                &value[value.len() - show_chars..]
            )
        }
    }
}

#[cfg(test)]
mod tests {
    use std::env;

    use super::*;

    #[tokio::test]
    async fn test_config_value_creation() {
        let plain = ConfigValue::plain("test_value".to_string());
        let encrypted = ConfigValue::encrypted(
            "encrypted_data".to_string(),
            Some("test hint".to_string()),
            Some(123456789),
        );

        assert!(!plain.is_encrypted());
        assert!(encrypted.is_encrypted());
        assert_eq!(encrypted.hint(), Some("test hint"));
    }

    #[tokio::test]
    async fn test_encrypted_config_store() {
        let crypto = Arc::new(CryptoService::new().unwrap());
        let mut store = EncryptedConfigStore::new(crypto);

        // Test plain value
        store.set_plain("plain_key", "plain_value".to_string());
        assert_eq!(
            store.get("plain_key").unwrap(),
            Some("plain_value".to_string())
        );

        // Test encrypted value
        store
            .set_encrypted(
                "encrypted_key",
                "secret_value".to_string(),
                Some("test secret".to_string()),
            )
            .unwrap();
        assert_eq!(
            store.get("encrypted_key").unwrap(),
            Some("secret_value".to_string())
        );

        // Test encryption status
        let status = store.get_encryption_status();
        assert_eq!(status.get("plain_key"), Some(&false));
        assert_eq!(status.get("encrypted_key"), Some(&true));
    }

    #[tokio::test]
    async fn test_config_builder() {
        let crypto = Arc::new(CryptoService::new().unwrap());

        // Set test environment variable
        unsafe { env::set_var("TEST_SECRET", "test_secret_value") };

        let store = EncryptedConfigBuilder::new(crypto)
            .with_env_mapping("test_key".to_string(), "TEST_SECRET".to_string())
            .build()
            .await
            .unwrap();

        assert_eq!(
            store.get("test_key").unwrap(),
            Some("test_secret_value".to_string())
        );

        // Clean up
        unsafe { env::remove_var("TEST_SECRET") };
    }

    #[tokio::test]
    async fn test_value_encryption_in_place() {
        let crypto = Arc::new(CryptoService::new().unwrap());
        let mut store = EncryptedConfigStore::new(crypto);

        // Add plain value
        store.set_plain("test_key", "test_value".to_string());
        assert!(!store.values.get("test_key").unwrap().is_encrypted());

        // Encrypt in place
        store
            .encrypt_value("test_key", Some("test hint".to_string()))
            .unwrap();
        assert!(store.values.get("test_key").unwrap().is_encrypted());
        assert_eq!(
            store.get("test_key").unwrap(),
            Some("test_value".to_string())
        );

        // Decrypt in place
        store.decrypt_value("test_key").unwrap();
        assert!(!store.values.get("test_key").unwrap().is_encrypted());
        assert_eq!(
            store.get("test_key").unwrap(),
            Some("test_value".to_string())
        );
    }

    #[test]
    fn test_mask_for_logging() {
        assert_eq!(super::utils::mask_for_logging("short", 2), "sh*rt");
        assert_eq!(
            super::utils::mask_for_logging("postgresql://user:password@localhost/db", 4),
            "post*******************************t/db"
        );
        assert_eq!(super::utils::mask_for_logging("secret123", 2), "se*****23");
    }
}
