//! Age key management for SOPS integration
//!
//! Manages Age encryption key pairs used by SOPS for GitOps secrets management.
//! Keys are stored encrypted in SecretumVault's Transit Engine using
//! post-quantum algorithms.
//!
//! # Architecture
//!
//! Each environment (dev, staging, prod) has its own Age keypair stored in
//! SecretumVault:
//! - `transit/age-master-{env}/private` → Age private key (encrypted at rest
//!   via PQ)
//! - `transit/age-master-{env}/public` → Age public key (for .sops.yaml)
//!
//! # Security Properties
//!
//! - Private keys never leave SecretumVault unencrypted
//! - All access is audited via SecretumVault logs
//! - Keys can be rotated without re-encrypting all SOPS files (age supports key
//!   versions)
//! - Post-quantum encryption from SecretumVault's Transit Engine

use std::fmt::Display;

use base64::engine::general_purpose::STANDARD as BASE64;
use base64::Engine;
use serde::{Deserialize, Serialize};
use tracing::{debug, error, info};

use crate::types::{KmsError, Result};

/// Age keypair: public and private keys
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgeKeypair {
    /// Age public key (x25519 format: age1...)
    pub public_key: String,
    /// Age private key (x25519 format: AGE-SECRET-KEY-1...)
    /// Stored encrypted in SecretumVault, never exposed in plaintext
    #[serde(skip_serializing)]
    pub private_key: String,
    /// Timestamp of keypair generation
    pub created_at: String,
    /// Environment this keypair is for
    pub environment: String,
    /// Version for key rotation tracking
    pub version: u32,
}

impl Display for AgeKeypair {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "AgeKeypair(env={}, version={}, public={}...)",
            self.environment,
            self.version,
            &self.public_key[0..20.min(self.public_key.len())]
        )
    }
}

/// Age key manager using SecretumVault backend
pub struct AgeKeyManager {
    vault_url: String,
    vault_token: String,
    http_client: reqwest::Client,
}

impl AgeKeyManager {
    /// Create a new Age key manager
    pub fn new(vault_url: impl Into<String>, vault_token: impl Into<String>) -> Result<Self> {
        let url = vault_url.into();
        let token = vault_token.into();

        if url.is_empty() {
            return Err(KmsError::ConfigurationError(
                "Vault URL is required".to_string(),
            ));
        }
        if token.is_empty() {
            return Err(KmsError::ConfigurationError(
                "Vault token is required".to_string(),
            ));
        }

        let client = reqwest::Client::builder()
            .danger_accept_invalid_certs(true) // For dev/self-signed
            .build()
            .map_err(KmsError::HttpError)?;

        Ok(Self {
            vault_url: url,
            vault_token: token,
            http_client: client,
        })
    }

    /// Generate a new Age keypair and store in SecretumVault
    pub async fn generate_keypair(&self, environment: &str) -> Result<AgeKeypair> {
        info!(
            "Generating new Age keypair for environment: {}",
            environment
        );

        // Call external command to generate Age keypair
        let age_keypair = self.generate_age_keypair()?;

        let now = chrono::Utc::now().to_rfc3339();

        // Store in SecretumVault (encrypted)
        self.store_keypair(&age_keypair, environment, 1).await?;

        let keypair = AgeKeypair {
            public_key: age_keypair.public,
            private_key: age_keypair.private,
            created_at: now,
            environment: environment.to_string(),
            version: 1,
        };

        info!("✓ Age keypair generated for {}: {}", environment, keypair);
        Ok(keypair)
    }

    /// Get the public key for an environment (usable in .sops.yaml)
    pub async fn get_public_key(&self, environment: &str) -> Result<String> {
        debug!("Retrieving age public key for environment: {}", environment);

        let secret_path = format!("age-master-{}/public", environment);
        self.retrieve_secret_from_vault(&secret_path).await
    }

    /// Get the private key for an environment (for SOPS decryption)
    /// This operation is audited by SecretumVault
    /// Returns (private_key, version)
    pub async fn get_private_key_with_version(&self, environment: &str) -> Result<(String, u32)> {
        info!(
            "Retrieving age private key for environment: {} (AUDIT)",
            environment
        );

        let secret_path = format!("age-master-{}/private", environment);
        let private_key = self.retrieve_secret_from_vault(&secret_path).await?;
        let version = self.get_keypair_version(environment).await?;

        info!(
            "✓ Age private key retrieved for {} (version {}, check audit logs)",
            environment, version
        );
        Ok((private_key, version))
    }

    /// Get the private key for an environment (for SOPS decryption)
    /// This operation is audited by SecretumVault
    pub async fn get_private_key(&self, environment: &str) -> Result<String> {
        self.get_private_key_with_version(environment)
            .await
            .map(|(key, _)| key)
    }

    /// Rotate the Age keypair for an environment
    /// Generates a new keypair, increments version
    pub async fn rotate_keypair(&self, environment: &str) -> Result<AgeKeypair> {
        info!("Rotating Age keypair for environment: {}", environment);

        // Get current version
        let current_version = self.get_keypair_version(environment).await?;
        let new_version = current_version + 1;

        // Generate new keypair
        let age_keypair = self.generate_age_keypair()?;
        let now = chrono::Utc::now().to_rfc3339();

        // Store with new version
        self.store_keypair(&age_keypair, environment, new_version)
            .await?;

        let keypair = AgeKeypair {
            public_key: age_keypair.public,
            private_key: age_keypair.private,
            created_at: now,
            environment: environment.to_string(),
            version: new_version,
        };

        info!(
            "✓ Age keypair rotated for {} (version {}→{})",
            environment, current_version, new_version
        );
        Ok(keypair)
    }

    /// Get current keypair version for an environment
    async fn get_keypair_version(&self, environment: &str) -> Result<u32> {
        let metadata_path = format!("age-master-{}/version", environment);
        let version_str = self.retrieve_secret_from_vault(&metadata_path).await?;

        version_str.parse::<u32>().map_err(|e| {
            KmsError::ConfigurationError(format!("Invalid version for {}: {}", environment, e))
        })
    }

    /// Generate Age keypair using external command
    /// Uses age-keygen or similar utility
    fn generate_age_keypair(&self) -> Result<GeneratedAgeKeypair> {
        use std::process::Command;

        debug!("Invoking age-keygen to generate keypair");

        // Try to use age-keygen command
        let output = Command::new("age-keygen")
            .arg("-o")
            .output()
            .map_err(|e| {
                KmsError::ConfigurationError(format!(
                    "age-keygen not found or failed: {}. Install age: https://github.com/FiloSottile/age",
                    e
                ))
            })?;

        if !output.status.success() {
            return Err(KmsError::ConfigurationError(format!(
                "age-keygen failed: {}",
                String::from_utf8_lossy(&output.stderr)
            )));
        }

        let keygen_output = String::from_utf8(output.stdout).map_err(|e| {
            KmsError::ConfigurationError(format!("Invalid UTF-8 from age-keygen: {}", e))
        })?;

        // Parse age-keygen output format:
        // # created: 2025-02-12T10:00:00Z
        // # public key: age1xxxxx...
        // AGE-SECRET-KEY-1yyyyyy...

        let mut public_key = String::new();
        let mut private_key = String::new();

        for line in keygen_output.lines() {
            if line.starts_with("# public key:") {
                public_key = line
                    .strip_prefix("# public key:")
                    .unwrap_or("")
                    .trim()
                    .to_string();
            } else if line.starts_with("AGE-SECRET-KEY-") {
                private_key = line.trim().to_string();
            }
        }

        if public_key.is_empty() || private_key.is_empty() {
            return Err(KmsError::ConfigurationError(
                "Failed to parse age-keygen output: missing public or private key".to_string(),
            ));
        }

        debug!(
            "✓ Age keypair generated: {}",
            &public_key[0..20.min(public_key.len())]
        );

        Ok(GeneratedAgeKeypair {
            public: public_key,
            private: private_key,
        })
    }

    /// Store keypair in SecretumVault Transit Engine
    async fn store_keypair(
        &self,
        keypair: &GeneratedAgeKeypair,
        environment: &str,
        version: u32,
    ) -> Result<()> {
        debug!("Storing age keypair in SecretumVault for {}", environment);

        let base_path = format!("age-master-{}", environment);

        // Store public key (in plaintext, it's public)
        let public_secret_path = format!("{}/public", base_path);
        self.store_secret(&public_secret_path, &keypair.public)
            .await?;

        // Store private key (encrypted in transit)
        let private_secret_path = format!("{}/private", base_path);
        let encrypted_private = self.encrypt_secret(&keypair.private).await?;
        self.store_secret(&private_secret_path, &encrypted_private)
            .await?;

        // Store version metadata
        let version_path = format!("{}/version", base_path);
        self.store_secret(&version_path, &version.to_string())
            .await?;

        info!(
            "✓ Age keypair stored in SecretumVault at {} (version {})",
            base_path, version
        );
        Ok(())
    }

    /// Store a secret value in SecretumVault's KV engine
    async fn store_secret(&self, secret_path: &str, secret_value: &str) -> Result<()> {
        let url = format!(
            "{}/v1/secret/data/{}",
            self.vault_url.trim_end_matches('/'),
            secret_path
        );

        let payload = serde_json::json!({
            "data": {
                "value": secret_value
            }
        });

        let response = self
            .http_client
            .post(&url)
            .header("X-Vault-Token", &self.vault_token)
            .json(&payload)
            .send()
            .await
            .map_err(KmsError::HttpError)?;

        if !response.status().is_success() {
            let status = response.status();
            let body = response
                .text()
                .await
                .unwrap_or_else(|_| "No response body".to_string());
            return Err(KmsError::SecretumVaultError(format!(
                "Failed to store secret ({}): {}",
                status, body
            )));
        }

        debug!("✓ Secret stored at {}", secret_path);
        Ok(())
    }

    /// Retrieve a secret value from SecretumVault
    async fn retrieve_secret_from_vault(&self, secret_path: &str) -> Result<String> {
        let url = format!(
            "{}/v1/secret/data/{}",
            self.vault_url.trim_end_matches('/'),
            secret_path
        );

        let response = self
            .http_client
            .get(&url)
            .header("X-Vault-Token", &self.vault_token)
            .send()
            .await
            .map_err(KmsError::HttpError)?;

        if !response.status().is_success() {
            let status = response.status();
            if status.as_u16() == 404 {
                return Err(KmsError::KeyNotFound(format!(
                    "Secret not found: {}",
                    secret_path
                )));
            }
            let body = response
                .text()
                .await
                .unwrap_or_else(|_| "No response body".to_string());
            return Err(KmsError::SecretumVaultError(format!(
                "Failed to retrieve secret ({}): {}",
                status, body
            )));
        }

        let body: serde_json::Value = response.json().await.map_err(|e| {
            KmsError::SecretumVaultError(format!("Failed to parse response JSON: {}", e))
        })?;

        body.pointer("/data/data/value")
            .and_then(|v| v.as_str())
            .map(|s: &str| s.to_string())
            .ok_or_else(|| KmsError::SecretumVaultError("No value in secret response".to_string()))
    }

    /// Encrypt secret using SecretumVault Transit Engine before storage
    async fn encrypt_secret(&self, plaintext: &str) -> Result<String> {
        let mount = "transit";
        let key_name = "age-encryption";
        let url = format!(
            "{}/v1/{}/encrypt/{}",
            self.vault_url.trim_end_matches('/'),
            mount,
            key_name
        );

        let plaintext_b64 = BASE64.encode(plaintext);

        let payload = serde_json::json!({
            "plaintext": plaintext_b64
        });

        let response = self
            .http_client
            .post(&url)
            .header("X-Vault-Token", &self.vault_token)
            .json(&payload)
            .send()
            .await
            .map_err(KmsError::HttpError)?;

        if !response.status().is_success() {
            let status = response.status();
            let body = response
                .text()
                .await
                .unwrap_or_else(|_| "No response body".to_string());
            return Err(KmsError::SecretumVaultError(format!(
                "Failed to encrypt secret ({}): {}",
                status, body
            )));
        }

        let body: serde_json::Value = response.json().await.map_err(|e| {
            KmsError::SecretumVaultError(format!("Failed to parse response JSON: {}", e))
        })?;

        body.pointer("/data/ciphertext")
            .and_then(|v| v.as_str())
            .map(|s: &str| s.to_string())
            .ok_or_else(|| KmsError::SecretumVaultError("No ciphertext in response".to_string()))
    }
}

/// Temporary holder for generated Age keypair data
struct GeneratedAgeKeypair {
    public: String,
    private: String,
}

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

    #[test]
    fn test_age_keypair_display() {
        let kp = AgeKeypair {
            public_key: "age1x123456789abcdefghij".to_string(),
            private_key: "AGE-SECRET-KEY-1yyyyyy".to_string(),
            created_at: "2025-02-12T10:00:00Z".to_string(),
            environment: "dev".to_string(),
            version: 1,
        };

        let display = format!("{}", kp);
        assert!(display.contains("env=dev"));
        assert!(display.contains("version=1"));
        assert!(display.contains("age1x12345"));
    }

    #[test]
    fn test_age_key_manager_new_missing_url() {
        let result = AgeKeyManager::new("", "token");
        assert!(result.is_err());
    }

    #[test]
    fn test_age_key_manager_new_missing_token() {
        let result = AgeKeyManager::new("http://vault:8200", "");
        assert!(result.is_err());
    }

    #[test]
    fn test_age_key_manager_new_success() {
        let result = AgeKeyManager::new("http://vault:8200", "token123");
        assert!(result.is_ok());
    }
}
