//! Secrets management for the faucet.
//!
//! Provides a pluggable secrets provider abstraction that supports:
//! - Environment variables (development only)
//! - File-based secrets (simple production)
//! - External secrets managers (enterprise production)
//!
//! # Security Best Practices
//!
//! 1. **Never use environment variables in production** - They can be exposed
//!    in process listings, logs, and container orchestration UIs.
//!
//! 2. **Use file-based secrets** as minimum security - Mount a secret file
//!    with restricted permissions (0400) from a secure volume.
//!
//! 3. **Use a secrets manager** for production - AWS Secrets Manager,
//!    HashiCorp Vault, or similar provides rotation and audit logging.

use std::path::Path;
use tracing::{info, warn};

/// Secret provider trait for pluggable secret storage backends.
pub trait SecretProvider: Send + Sync {
    /// Get a secret value by name.
    fn get(&self, name: &str) -> Option<String>;

    /// Provider name for logging.
    fn provider_name(&self) -> &'static str;
}

/// Environment variable secret provider (development only).
///
/// # Warning
///
/// Environment variables are NOT secure for production use:
/// - Visible in `/proc/<pid>/environ` on Linux
/// - Exposed in `ps auxe` output
/// - May be logged by container orchestrators
/// - No access control or audit logging
pub struct EnvSecretProvider;

impl SecretProvider for EnvSecretProvider {
    fn get(&self, name: &str) -> Option<String> {
        std::env::var(name).ok()
    }

    fn provider_name(&self) -> &'static str {
        "environment"
    }
}

/// File-based secret provider.
///
/// Reads secrets from files in a secrets directory. Each secret is stored
/// as a separate file named after the secret key.
///
/// # Example
///
/// ```text
/// /run/secrets/
/// ├── FAUCET_WALLET_KEY
/// └── DATABASE_PASSWORD
/// ```
///
/// # Security Notes
///
/// - Set file permissions to 0400 (owner read only)
/// - Mount from tmpfs or encrypted volume
/// - Use immutable container images
pub struct FileSecretProvider {
    secrets_dir: std::path::PathBuf,
}

impl FileSecretProvider {
    /// Create a new file-based secret provider.
    pub fn new(secrets_dir: impl AsRef<Path>) -> Self {
        Self {
            secrets_dir: secrets_dir.as_ref().to_path_buf(),
        }
    }
}

impl SecretProvider for FileSecretProvider {
    fn get(&self, name: &str) -> Option<String> {
        let path = self.secrets_dir.join(name);
        std::fs::read_to_string(&path)
            .ok()
            .map(|s| s.trim().to_string())
    }

    fn provider_name(&self) -> &'static str {
        "file"
    }
}

/// Chained secret provider that tries multiple providers in order.
///
/// Useful for fallback scenarios, e.g., try file first, then env.
#[allow(dead_code)]
pub struct ChainedSecretProvider {
    providers: Vec<Box<dyn SecretProvider>>,
}

#[allow(dead_code)]
impl ChainedSecretProvider {
    /// Create a new chained provider with the given providers.
    pub fn new(providers: Vec<Box<dyn SecretProvider>>) -> Self {
        Self { providers }
    }
}

impl SecretProvider for ChainedSecretProvider {
    fn get(&self, name: &str) -> Option<String> {
        for provider in &self.providers {
            if let Some(value) = provider.get(name) {
                return Some(value);
            }
        }
        None
    }

    fn provider_name(&self) -> &'static str {
        "chained"
    }
}

/// Creates the appropriate secret provider based on configuration.
///
/// Priority order:
/// 1. If SECRETS_DIR is set, use file-based provider
/// 2. If SECRETS_PROVIDER=file, use file-based provider with /run/secrets
/// 3. Fall back to environment variables with a warning
pub fn create_secret_provider() -> Box<dyn SecretProvider> {
    // Check for explicit secrets directory
    if let Ok(dir) = std::env::var("SECRETS_DIR") {
        info!(dir = %dir, "Using file-based secrets provider");
        return Box::new(FileSecretProvider::new(dir));
    }

    // Check for explicit provider type
    if let Ok(provider) = std::env::var("SECRETS_PROVIDER") {
        match provider.as_str() {
            "file" => {
                let dir =
                    std::env::var("SECRETS_DIR").unwrap_or_else(|_| "/run/secrets".to_string());
                info!(dir = %dir, "Using file-based secrets provider");
                return Box::new(FileSecretProvider::new(dir));
            }
            "env" => {
                warn!(
                    "Using environment variable secrets provider - NOT RECOMMENDED FOR PRODUCTION"
                );
                return Box::new(EnvSecretProvider);
            }
            _ => {
                warn!(provider = %provider, "Unknown secrets provider, falling back to environment");
            }
        }
    }

    // Default: check for /run/secrets directory (common in Docker/Kubernetes)
    if Path::new("/run/secrets").is_dir() {
        info!("Detected /run/secrets directory, using file-based secrets");
        return Box::new(FileSecretProvider::new("/run/secrets"));
    }

    // Fallback to environment variables with warning
    warn!(
        "Using environment variable secrets provider. \
        This is NOT recommended for production. \
        Set SECRETS_DIR or mount secrets to /run/secrets."
    );
    Box::new(EnvSecretProvider)
}

/// Zeroize a string in memory (best effort).
///
/// This attempts to overwrite the string's memory before dropping.
/// Note: Rust's String may have been reallocated, so this isn't perfect.
#[allow(dead_code)]
pub fn zeroize_string(mut s: String) {
    // SAFETY: We're about to drop this string anyway
    unsafe {
        let bytes = s.as_bytes_mut();
        std::ptr::write_bytes(bytes.as_mut_ptr(), 0, bytes.len());
    }
    drop(s);
}

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

    #[test]
    fn test_file_secret_provider() {
        let dir = tempdir().unwrap();
        let secret_path = dir.path().join("TEST_SECRET");
        std::fs::write(&secret_path, "secret_value\n").unwrap();

        let provider = FileSecretProvider::new(dir.path());
        assert_eq!(
            provider.get("TEST_SECRET"),
            Some("secret_value".to_string())
        );
        assert_eq!(provider.get("NONEXISTENT"), None);
    }

    #[test]
    fn test_env_secret_provider() {
        std::env::set_var("TEST_ENV_SECRET", "env_value");
        let provider = EnvSecretProvider;
        assert_eq!(
            provider.get("TEST_ENV_SECRET"),
            Some("env_value".to_string())
        );
        std::env::remove_var("TEST_ENV_SECRET");
    }

    #[test]
    fn test_chained_provider() {
        let dir = tempdir().unwrap();
        let secret_path = dir.path().join("FILE_SECRET");
        std::fs::write(&secret_path, "file_value").unwrap();

        std::env::set_var("ENV_SECRET", "env_value");

        let provider = ChainedSecretProvider::new(vec![
            Box::new(FileSecretProvider::new(dir.path())),
            Box::new(EnvSecretProvider),
        ]);

        // File provider takes precedence
        assert_eq!(provider.get("FILE_SECRET"), Some("file_value".to_string()));
        // Falls through to env provider
        assert_eq!(provider.get("ENV_SECRET"), Some("env_value".to_string()));

        std::env::remove_var("ENV_SECRET");
    }
}
