// Encrypted secrets management for containers
// Stores secrets encrypted with AES-256-GCM and injects them at runtime

use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Nonce};
use anyhow::{Context, Result};
use rand::RngCore;
use std::path::PathBuf;
use tokio::fs;
use uuid::Uuid;

/// Prefix to identify encrypted files (magic bytes)
const ENCRYPTED_MAGIC: &[u8] = b"ARCHI_ENC1";

pub struct SecretsManager {
    secrets_dir: PathBuf,
    cipher: Aes256Gcm,
}

impl SecretsManager {
    /// Create a new SecretsManager with a 32-byte encryption key.
    /// In production, derive this key from the user's password via Argon2.
    pub fn new(secrets_dir: PathBuf, encryption_key: Vec<u8>) -> Result<Self> {
        anyhow::ensure!(
            encryption_key.len() == 32,
            "Encryption key must be exactly 32 bytes (256 bits), got {}",
            encryption_key.len()
        );
        let cipher = Aes256Gcm::new_from_slice(&encryption_key)
            .map_err(|e| anyhow::anyhow!("Failed to create cipher: {}", e))?;
        Ok(Self {
            secrets_dir,
            cipher,
        })
    }

    /// Encrypt a plaintext value using AES-256-GCM.
    /// Returns: MAGIC (10 bytes) + nonce (12 bytes) + ciphertext (variable)
    fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
        let mut nonce_bytes = [0u8; 12];
        rand::thread_rng().fill_bytes(&mut nonce_bytes);
        let nonce = Nonce::from_slice(&nonce_bytes);

        let ciphertext = self
            .cipher
            .encrypt(nonce, plaintext)
            .map_err(|e| anyhow::anyhow!("Encryption failed: {}", e))?;

        let mut output = Vec::with_capacity(ENCRYPTED_MAGIC.len() + 12 + ciphertext.len());
        output.extend_from_slice(ENCRYPTED_MAGIC);
        output.extend_from_slice(&nonce_bytes);
        output.extend_from_slice(&ciphertext);
        Ok(output)
    }

    /// Decrypt a previously encrypted value.
    fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
        let magic_len = ENCRYPTED_MAGIC.len();
        anyhow::ensure!(
            data.len() > magic_len + 12,
            "Encrypted data too short"
        );
        anyhow::ensure!(
            &data[..magic_len] == ENCRYPTED_MAGIC,
            "Invalid encrypted data (bad magic bytes)"
        );

        let nonce = Nonce::from_slice(&data[magic_len..magic_len + 12]);
        let ciphertext = &data[magic_len + 12..];

        self.cipher
            .decrypt(nonce, ciphertext)
            .map_err(|e| anyhow::anyhow!("Decryption failed (wrong key or corrupted data): {}", e))
    }

    /// Store a secret for an app (encrypted at rest)
    pub async fn store_secret(
        &self,
        app_id: &str,
        _key: &str,
        value: &str,
    ) -> Result<String> {
        let secret_id = Uuid::new_v4().to_string();
        let secret_path = self
            .secrets_dir
            .join(app_id)
            .join(format!("{}.secret", secret_id));

        fs::create_dir_all(secret_path.parent().unwrap()).await?;

        let encrypted = self
            .encrypt(value.as_bytes())
            .context("Failed to encrypt secret")?;
        fs::write(&secret_path, &encrypted)
            .await
            .context("Failed to write secret")?;

        // Set restrictive permissions
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let mut perms = fs::metadata(&secret_path).await?.permissions();
            perms.set_mode(0o600);
            fs::set_permissions(&secret_path, perms).await?;
        }

        Ok(secret_id)
    }

    /// Read and decrypt a secret value
    pub async fn read_secret(&self, app_id: &str, secret_id: &str) -> Result<String> {
        let secret_path = self
            .secrets_dir
            .join(app_id)
            .join(format!("{}.secret", secret_id));

        let data = fs::read(&secret_path)
            .await
            .context("Failed to read secret file")?;

        // Support reading legacy plaintext secrets (no magic prefix)
        if data.len() < ENCRYPTED_MAGIC.len()
            || &data[..ENCRYPTED_MAGIC.len()] != ENCRYPTED_MAGIC
        {
            return String::from_utf8(data)
                .context("Legacy secret is not valid UTF-8");
        }

        let plaintext = self.decrypt(&data)?;
        String::from_utf8(plaintext).context("Decrypted secret is not valid UTF-8")
    }

    /// Retrieve a secret (returns the secret ID path for volume mounting)
    pub fn get_secret_path(&self, app_id: &str, secret_id: &str) -> PathBuf {
        self.secrets_dir
            .join(app_id)
            .join(format!("{}.secret", secret_id))
    }

    /// List secrets for an app
    pub async fn list_secrets(&self, app_id: &str) -> Result<Vec<String>> {
        let app_secrets_dir = self.secrets_dir.join(app_id);

        if !app_secrets_dir.exists() {
            return Ok(vec![]);
        }

        let mut secrets = Vec::new();
        let mut entries = fs::read_dir(&app_secrets_dir).await?;

        while let Some(entry) = entries.next_entry().await? {
            let path = entry.path();
            if path.extension().and_then(|s| s.to_str()) == Some("secret") {
                if let Some(secret_id) = path
                    .file_stem()
                    .and_then(|s| s.to_str())
                    .map(|s| s.to_string())
                {
                    secrets.push(secret_id);
                }
            }
        }

        Ok(secrets)
    }

    /// Delete a secret
    pub async fn delete_secret(&self, app_id: &str, secret_id: &str) -> Result<()> {
        let secret_path = self
            .secrets_dir
            .join(app_id)
            .join(format!("{}.secret", secret_id));

        if secret_path.exists() {
            fs::remove_file(&secret_path).await?;
        }

        Ok(())
    }
}

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

    fn test_key() -> Vec<u8> {
        vec![0x42; 32] // 32-byte test key
    }

    #[tokio::test]
    async fn test_encrypt_decrypt_roundtrip() {
        let dir = tempfile::tempdir().unwrap();
        let mgr = SecretsManager::new(dir.path().to_path_buf(), test_key()).unwrap();

        let secret_id = mgr
            .store_secret("test-app", "db-password", "supersecret123")
            .await
            .unwrap();

        let decrypted = mgr.read_secret("test-app", &secret_id).await.unwrap();
        assert_eq!(decrypted, "supersecret123");
    }

    #[tokio::test]
    async fn test_wrong_key_fails() {
        let dir = tempfile::tempdir().unwrap();
        let mgr = SecretsManager::new(dir.path().to_path_buf(), test_key()).unwrap();

        let secret_id = mgr
            .store_secret("test-app", "key", "secret")
            .await
            .unwrap();

        let wrong_key = vec![0x99; 32];
        let mgr2 = SecretsManager::new(dir.path().to_path_buf(), wrong_key).unwrap();
        assert!(mgr2.read_secret("test-app", &secret_id).await.is_err());
    }

    #[tokio::test]
    async fn test_list_and_delete() {
        let dir = tempfile::tempdir().unwrap();
        let mgr = SecretsManager::new(dir.path().to_path_buf(), test_key()).unwrap();

        let id1 = mgr.store_secret("app1", "k1", "v1").await.unwrap();
        let _id2 = mgr.store_secret("app1", "k2", "v2").await.unwrap();

        let list = mgr.list_secrets("app1").await.unwrap();
        assert_eq!(list.len(), 2);

        mgr.delete_secret("app1", &id1).await.unwrap();
        let list = mgr.list_secrets("app1").await.unwrap();
        assert_eq!(list.len(), 1);
    }

    #[test]
    fn test_invalid_key_length() {
        let dir = tempfile::tempdir().unwrap();
        assert!(SecretsManager::new(dir.path().to_path_buf(), vec![0; 16]).is_err());
    }

    #[tokio::test]
    async fn test_file_is_encrypted_on_disk() {
        let dir = tempfile::tempdir().unwrap();
        let mgr = SecretsManager::new(dir.path().to_path_buf(), test_key()).unwrap();

        let secret_id = mgr
            .store_secret("test-app", "key", "my_secret_value")
            .await
            .unwrap();

        let path = mgr.get_secret_path("test-app", &secret_id);
        let raw = std::fs::read(&path).unwrap();

        // File must NOT contain plaintext
        assert!(!raw.windows(15).any(|w| w == b"my_secret_value"));
        // File must start with our magic prefix
        assert_eq!(&raw[..ENCRYPTED_MAGIC.len()], ENCRYPTED_MAGIC);
    }
}
