//! Configuration encryption module
//!
//! This module provides encryption and decryption functionality for configuration values.
//! It uses AES-256-GCM encryption with a key stored in a `.k` file in the root path.
//! Configuration values starting with `@` are automatically decrypted when loaded.

use crate::config::ConfigError;
use aes_gcm::{
    Aes256Gcm, Key, Nonce,
    aead::{Aead, AeadCore, KeyInit, OsRng},
};
use base64::{Engine, engine::general_purpose::STANDARD};
use std::fs;
use std::path::PathBuf;
use tracing::{debug, error, warn};

/// Configuration encryption manager
#[allow(dead_code)]
pub struct ConfigEncryption {
    cipher: Aes256Gcm,
    key_path: PathBuf,
}

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

impl Clone for ConfigEncryption {
    fn clone(&self) -> Self {
        Self {
            cipher: self.cipher.clone(),
            key_path: self.key_path.clone(),
        }
    }
}

impl ConfigEncryption {
    /// Create a new configuration encryption manager
    #[allow(dead_code)]
    pub fn new(root_path: &str) -> Result<Self, ConfigError> {
        let key_path = PathBuf::from(root_path).join(".k");
        let key = Self::load_or_create_key(&key_path)?;
        let cipher = Aes256Gcm::new(&key);

        Ok(Self { cipher, key_path })
    }

    /// Load encryption key from file or create a new one
    fn load_or_create_key(key_path: &PathBuf) -> Result<Key<Aes256Gcm>, ConfigError> {
        if key_path.exists() {
            Self::load_key_from_file(key_path)
        } else {
            warn!(
                "Encryption key file not found at {:?}, creating new key",
                key_path
            );
            Self::create_new_key(key_path)
        }
    }

    /// Load encryption key from file
    fn load_key_from_file(key_path: &PathBuf) -> Result<Key<Aes256Gcm>, ConfigError> {
        let key_data = fs::read_to_string(key_path).map_err(|e| {
            ConfigError::ReadError(format!("Failed to read encryption key file: {}", e))
        })?;

        let key_bytes = STANDARD.decode(key_data.trim()).map_err(|e| {
            ConfigError::ParseError(format!("Failed to decode encryption key: {}", e))
        })?;

        if key_bytes.len() != 32 {
            return Err(ConfigError::ValidationError(
                "Encryption key must be 32 bytes (256 bits)".to_string(),
            ));
        }

        let key = Key::<Aes256Gcm>::from_slice(&key_bytes);
        debug!("Loaded encryption key from {:?}", key_path);
        Ok(*key)
    }

    /// Create a new encryption key and save it to file
    fn create_new_key(key_path: &PathBuf) -> Result<Key<Aes256Gcm>, ConfigError> {
        let key = Aes256Gcm::generate_key(&mut OsRng);
        let key_b64 = STANDARD.encode(key.as_slice());

        // Create parent directory if it doesn't exist
        if let Some(parent) = key_path.parent() {
            fs::create_dir_all(parent).map_err(|e| {
                ConfigError::DirectoryCreationError(format!(
                    "Failed to create key directory: {}",
                    e
                ))
            })?;
        }

        fs::write(key_path, key_b64).map_err(|e| {
            ConfigError::ReadError(format!("Failed to write encryption key file: {}", e))
        })?;

        // Set restrictive permissions on the key file (Unix only)
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let mut perms = fs::metadata(key_path)
                .map_err(|e| {
                    ConfigError::ReadError(format!("Failed to get key file metadata: {}", e))
                })?
                .permissions();
            perms.set_mode(0o600); // Read/write for owner only
            fs::set_permissions(key_path, perms).map_err(|e| {
                ConfigError::ReadError(format!("Failed to set key file permissions: {}", e))
            })?;
        }

        debug!("Created new encryption key at {:?}", key_path);
        Ok(key)
    }

    /// Encrypt a plaintext value
    #[allow(dead_code)]
    pub fn encrypt(&self, plaintext: &str) -> Result<String, ConfigError> {
        let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
        let ciphertext = self
            .cipher
            .encrypt(&nonce, plaintext.as_bytes())
            .map_err(|e| ConfigError::ValidationError(format!("Failed to encrypt value: {}", e)))?;

        // Combine nonce and ciphertext, then base64 encode
        let mut combined = nonce.to_vec();
        combined.extend_from_slice(&ciphertext);
        let encrypted_b64 = STANDARD.encode(combined);

        Ok(format!("@{}", encrypted_b64))
    }

    /// Decrypt an encrypted value
    pub fn decrypt(&self, encrypted: &str) -> Result<String, ConfigError> {
        if !encrypted.starts_with('@') {
            return Err(ConfigError::ValidationError(
                "Encrypted values must start with '@'".to_string(),
            ));
        }

        let encrypted_data = &encrypted[1..]; // Remove '@' prefix
        let combined = STANDARD.decode(encrypted_data).map_err(|e| {
            ConfigError::ParseError(format!("Failed to decode encrypted value: {}", e))
        })?;

        if combined.len() < 12 {
            return Err(ConfigError::ValidationError(
                "Encrypted value too short".to_string(),
            ));
        }

        // Split nonce and ciphertext
        let (nonce_bytes, ciphertext) = combined.split_at(12);
        let nonce = Nonce::from_slice(nonce_bytes);

        let plaintext = self
            .cipher
            .decrypt(nonce, ciphertext)
            .map_err(|e| ConfigError::ValidationError(format!("Failed to decrypt value: {}", e)))?;

        String::from_utf8(plaintext).map_err(|e| {
            ConfigError::ParseError(format!("Decrypted value is not valid UTF-8: {}", e))
        })
    }

    /// Check if a value is encrypted (starts with '@')
    pub fn is_encrypted(value: &str) -> bool {
        value.starts_with('@')
    }

    /// Decrypt a value if it's encrypted, otherwise return as-is
    pub fn decrypt_if_encrypted(&self, value: &str) -> Result<String, ConfigError> {
        if Self::is_encrypted(value) {
            self.decrypt(value)
        } else {
            Ok(value.to_string())
        }
    }

    /// Get the path to the encryption key file
    #[allow(dead_code)]
    pub fn key_file_path(&self) -> &PathBuf {
        &self.key_path
    }

    /// Rotate the encryption key (create new key, re-encrypt all values)
    #[allow(dead_code)]
    pub fn rotate_key(&mut self) -> Result<(), ConfigError> {
        warn!("Rotating encryption key at {:?}", self.key_path);

        // Create backup of old key
        let backup_path = self.key_path.with_extension("k.backup");
        if self.key_path.exists() {
            fs::copy(&self.key_path, &backup_path)
                .map_err(|e| ConfigError::ReadError(format!("Failed to backup old key: {}", e)))?;
        }

        // Generate new key
        let new_key = Self::create_new_key(&self.key_path)?;
        self.cipher = Aes256Gcm::new(&new_key);

        debug!("Encryption key rotated successfully");
        Ok(())
    }

    /// Verify that the encryption key is valid and accessible
    #[allow(dead_code)]
    pub fn verify_key(&self) -> Result<(), ConfigError> {
        // Test encryption/decryption with a known value
        let test_value = "test_encryption_key";
        let encrypted = self.encrypt(test_value)?;
        let decrypted = self.decrypt(&encrypted)?;

        if decrypted != test_value {
            return Err(ConfigError::ValidationError(
                "Encryption key verification failed".to_string(),
            ));
        }

        debug!("Encryption key verification successful");
        Ok(())
    }
}

/// Encrypt a single configuration value
#[allow(dead_code)]
pub fn encrypt_value(root_path: &str, value: &str) -> Result<String, ConfigError> {
    let encryption = ConfigEncryption::new(root_path)?;
    encryption.encrypt(value)
}

/// Decrypt a single configuration value
#[allow(dead_code)]
pub fn decrypt_value(root_path: &str, value: &str) -> Result<String, ConfigError> {
    let encryption = ConfigEncryption::new(root_path)?;
    encryption.decrypt_if_encrypted(value)
}

/// CLI tool for managing encrypted configuration values
#[allow(dead_code)]
pub struct EncryptionTool {
    encryption: ConfigEncryption,
}

impl EncryptionTool {
    /// Create a new encryption tool
    #[allow(dead_code)]
    pub fn new(root_path: &str) -> Result<Self, ConfigError> {
        let encryption = ConfigEncryption::new(root_path)?;
        Ok(Self { encryption })
    }

    /// Encrypt a value for use in configuration
    #[allow(dead_code)]
    pub fn encrypt_value(&self, value: &str) -> Result<String, ConfigError> {
        self.encryption.encrypt(value)
    }

    /// Decrypt a value from configuration
    #[allow(dead_code)]
    pub fn decrypt_value(&self, value: &str) -> Result<String, ConfigError> {
        self.encryption.decrypt_if_encrypted(value)
    }

    /// Generate a new encryption key
    #[allow(dead_code)]
    pub fn generate_key(&mut self) -> Result<(), ConfigError> {
        self.encryption.rotate_key()
    }

    /// Verify the encryption key
    #[allow(dead_code)]
    pub fn verify_key(&self) -> Result<(), ConfigError> {
        self.encryption.verify_key()
    }

    /// Show key information
    #[allow(dead_code)]
    pub fn show_key_info(&self) -> Result<String, ConfigError> {
        let key_path = self.encryption.key_file_path();
        let exists = key_path.exists();
        let size = if exists {
            fs::metadata(key_path).map(|m| m.len()).unwrap_or(0)
        } else {
            0
        };

        let info = format!(
            "Key file: {:?}\nExists: {}\nSize: {} bytes\n",
            key_path, exists, size
        );

        Ok(info)
    }

    /// Find all encrypted values in configuration content
    #[allow(dead_code)]
    pub fn find_encrypted_values(&self, config_content: &str) -> Vec<String> {
        let mut encrypted_values = Vec::new();

        for line in config_content.lines() {
            if let Some(_value_start) = line.find('@') {
                // Extract the value part after '='
                if let Some(equals_pos) = line.find('=') {
                    let value_part = &line[equals_pos + 1..].trim();
                    if value_part.starts_with('"') && value_part.ends_with('"') {
                        // Remove quotes
                        let quoted_value = &value_part[1..value_part.len() - 1];
                        if ConfigEncryption::is_encrypted(quoted_value) {
                            encrypted_values.push(quoted_value.to_string());
                        }
                    } else if ConfigEncryption::is_encrypted(value_part) {
                        encrypted_values.push(value_part.to_string());
                    }
                }
            }
        }

        encrypted_values
    }

    /// Decrypt all encrypted values in a configuration string for display
    #[allow(dead_code)]
    pub fn decrypt_config_display(&self, config_content: &str) -> Result<String, ConfigError> {
        let mut result = config_content.to_string();
        let encrypted_values = self.find_encrypted_values(config_content);

        for encrypted_value in encrypted_values {
            match self.encryption.decrypt(&encrypted_value) {
                Ok(decrypted) => {
                    result = result.replace(&encrypted_value, &format!("{}[DECRYPTED]", decrypted));
                }
                Err(e) => {
                    error!("Failed to decrypt value {}: {}", encrypted_value, e);
                    result = result.replace(&encrypted_value, "[DECRYPTION_FAILED]");
                }
            }
        }

        Ok(result)
    }
}

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

    #[test]
    fn test_encryption_roundtrip() {
        let temp_dir = TempDir::new().unwrap();
        let encryption = ConfigEncryption::new(temp_dir.path().to_str().unwrap()).unwrap();

        let original = "secret_password_123";
        let encrypted = encryption.encrypt(original).unwrap();
        let decrypted = encryption.decrypt(&encrypted).unwrap();

        assert_eq!(original, decrypted);
        assert!(encrypted.starts_with('@'));
    }

    #[test]
    fn test_decrypt_if_encrypted() {
        let temp_dir = TempDir::new().unwrap();
        let encryption = ConfigEncryption::new(temp_dir.path().to_str().unwrap()).unwrap();

        let plain_value = "not_encrypted";
        let result = encryption.decrypt_if_encrypted(plain_value).unwrap();
        assert_eq!(plain_value, result);

        let encrypted_value = encryption.encrypt("encrypted_value").unwrap();
        let result = encryption.decrypt_if_encrypted(&encrypted_value).unwrap();
        assert_eq!("encrypted_value", result);
    }

    #[test]
    fn test_key_file_creation() {
        let temp_dir = TempDir::new().unwrap();
        let key_path = temp_dir.path().join(".k");

        assert!(!key_path.exists());

        let _encryption = ConfigEncryption::new(temp_dir.path().to_str().unwrap()).unwrap();

        assert!(key_path.exists());
    }

    #[test]
    fn test_key_verification() {
        let temp_dir = TempDir::new().unwrap();
        let encryption = ConfigEncryption::new(temp_dir.path().to_str().unwrap()).unwrap();

        encryption.verify_key().unwrap();
    }

    #[test]
    fn test_encryption_tool() {
        let temp_dir = TempDir::new().unwrap();
        let tool = EncryptionTool::new(temp_dir.path().to_str().unwrap()).unwrap();

        let original = "test_value";
        let encrypted = tool.encrypt_value(original).unwrap();
        let decrypted = tool.decrypt_value(&encrypted).unwrap();

        assert_eq!(original, decrypted);
    }

    #[test]
    fn test_find_encrypted_values() {
        let temp_dir = TempDir::new().unwrap();
        let tool = EncryptionTool::new(temp_dir.path().to_str().unwrap()).unwrap();

        let config_content = r#"
            database_url = "postgresql://user:pass@localhost/db"
            secret_key = "@aGVsbG8gd29ybGQ="
            api_key = "@c2VjcmV0X2FwaV9rZXk="
            plain_value = "not_encrypted"
        "#;

        let encrypted_values = tool.find_encrypted_values(config_content);
        assert_eq!(encrypted_values.len(), 2);
        assert!(encrypted_values.contains(&"@aGVsbG8gd29ybGQ=".to_string()));
        assert!(encrypted_values.contains(&"@c2VjcmV0X2FwaV9rZXk=".to_string()));
    }
}
