/// SecretsSource trait and built-in implementations.
///
/// Each source knows how to retrieve a named secret. Sources are tried in
/// order by the SecretsResolver until one succeeds.

use crate::error::SecretsError;
use crate::secret::Secret;

/// A source of secret values.
pub trait SecretsSource: Send + Sync {
    /// The name of this source.
    fn name(&self) -> &str;

    /// Retrieve a secret by key.
    fn get(&self, key: &str) -> Result<Secret<String>, SecretsError>;

    /// List available secret keys (may not be supported by all sources).
    fn list(&self) -> Result<Vec<String>, SecretsError>;
}

/// Reads secrets from environment variables.
///
/// Key mapping: `jwt.key` → `EL_SECRET_JWT_KEY` (uppercased, dots → underscores).
pub struct EnvVarSource {
    prefix: String,
}

impl EnvVarSource {
    pub fn new() -> Self {
        Self { prefix: "EL_SECRET".to_string() }
    }

    pub fn with_prefix(prefix: impl Into<String>) -> Self {
        Self { prefix: prefix.into() }
    }

    fn env_key(&self, key: &str) -> String {
        let normalized = key.replace('.', "_").replace('-', "_").to_uppercase();
        format!("{}_{}", self.prefix, normalized)
    }
}

impl Default for EnvVarSource {
    fn default() -> Self {
        Self::new()
    }
}

impl SecretsSource for EnvVarSource {
    fn name(&self) -> &str {
        "env-var"
    }

    fn get(&self, key: &str) -> Result<Secret<String>, SecretsError> {
        let env_key = self.env_key(key);
        std::env::var(&env_key)
            .map(Secret::new)
            .map_err(|_| SecretsError::NotFound {
                key: key.to_string(),
                source_name: "env-var".to_string(),
            })
    }

    fn list(&self) -> Result<Vec<String>, SecretsError> {
        let prefix = format!("{}_", self.prefix);
        let keys: Vec<String> = std::env::vars()
            .filter(|(k, _)| k.starts_with(&prefix))
            .map(|(k, _)| {
                k.strip_prefix(&prefix)
                    .unwrap_or(&k)
                    .to_lowercase()
                    .replace('_', ".")
            })
            .collect();
        Ok(keys)
    }
}

/// In-memory secrets source for testing.
///
/// NEVER use in production — secrets are in plaintext in memory
/// and this source is not safe for production credentials.
#[derive(Default)]
pub struct InMemorySource {
    secrets: std::collections::HashMap<String, String>,
}

impl InMemorySource {
    pub fn new() -> Self {
        Self::default()
    }

    /// Insert a secret. Only for testing.
    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
        self.secrets.insert(key.into(), value.into());
    }
}

impl SecretsSource for InMemorySource {
    fn name(&self) -> &str {
        "in-memory"
    }

    fn get(&self, key: &str) -> Result<Secret<String>, SecretsError> {
        self.secrets
            .get(key)
            .map(|v| Secret::new(v.clone()))
            .ok_or_else(|| SecretsError::NotFound {
                key: key.to_string(),
                source_name: "in-memory".to_string(),
            })
    }

    fn list(&self) -> Result<Vec<String>, SecretsError> {
        Ok(self.secrets.keys().cloned().collect())
    }
}

/// Vault source stub — HashiCorp Vault integration.
///
/// This is a stub that defines the interface. A real implementation
/// would make HTTP calls to the Vault API. The stub is sufficient
/// for the type system and resolver to work correctly.
pub struct VaultSource {
    /// Vault server URL (e.g. "https://vault.example.com").
    pub address: String,
    /// Mount path for the KV engine (e.g. "secret").
    pub mount: String,
    /// The Vault path prefix for this app's secrets.
    pub path: String,
}

impl VaultSource {
    pub fn new(address: impl Into<String>, mount: impl Into<String>, path: impl Into<String>) -> Self {
        Self {
            address: address.into(),
            mount: mount.into(),
            path: path.into(),
        }
    }
}

impl SecretsSource for VaultSource {
    fn name(&self) -> &str {
        "vault"
    }

    fn get(&self, key: &str) -> Result<Secret<String>, SecretsError> {
        // Stub: in a real impl, make HTTP GET to Vault KV API
        Err(SecretsError::SourceUnavailable {
            source_name: "vault".to_string(),
            reason: format!(
                "Vault HTTP client not implemented in stub — would fetch {}/{}/{}/{}",
                self.address, self.mount, self.path, key
            ),
        })
    }

    fn list(&self) -> Result<Vec<String>, SecretsError> {
        Err(SecretsError::SourceUnavailable {
            source_name: "vault".to_string(),
            reason: "Vault HTTP client not implemented in stub".to_string(),
        })
    }
}

/// AWS Secrets Manager source stub.
pub struct AwsSecretsSource {
    pub region: String,
    pub path_prefix: String,
}

impl AwsSecretsSource {
    pub fn new(region: impl Into<String>, path_prefix: impl Into<String>) -> Self {
        Self {
            region: region.into(),
            path_prefix: path_prefix.into(),
        }
    }
}

impl SecretsSource for AwsSecretsSource {
    fn name(&self) -> &str {
        "aws-secrets-manager"
    }

    fn get(&self, key: &str) -> Result<Secret<String>, SecretsError> {
        Err(SecretsError::SourceUnavailable {
            source_name: "aws-secrets-manager".to_string(),
            reason: format!(
                "AWS SDK not linked — would fetch {}/{} in {}",
                self.path_prefix, key, self.region
            ),
        })
    }

    fn list(&self) -> Result<Vec<String>, SecretsError> {
        Err(SecretsError::SourceUnavailable {
            source_name: "aws-secrets-manager".to_string(),
            reason: "AWS SDK not linked".to_string(),
        })
    }
}

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

    #[test]
    fn in_memory_get() {
        let mut src = InMemorySource::new();
        src.insert("jwt.key", "secret-jwt-key");
        let val = src.get("jwt.key").unwrap();
        assert_eq!(val.expose(), "secret-jwt-key");
    }

    #[test]
    fn in_memory_missing() {
        let src = InMemorySource::new();
        assert!(src.get("missing.key").is_err());
    }

    #[test]
    fn in_memory_list() {
        let mut src = InMemorySource::new();
        src.insert("key.a", "a");
        src.insert("key.b", "b");
        let mut keys = src.list().unwrap();
        keys.sort();
        assert_eq!(keys, vec!["key.a", "key.b"]);
    }

    #[test]
    fn env_key_mapping() {
        let src = EnvVarSource::new();
        assert_eq!(src.env_key("jwt.key"), "EL_SECRET_JWT_KEY");
        assert_eq!(src.env_key("database.password"), "EL_SECRET_DATABASE_PASSWORD");
    }

    #[test]
    fn vault_source_stub_returns_error() {
        let src = VaultSource::new("https://vault.example.com", "secret", "myapp");
        assert!(src.get("jwt.key").is_err());
    }

    #[test]
    fn aws_source_stub_returns_error() {
        let src = AwsSecretsSource::new("us-east-1", "myapp");
        assert!(src.get("jwt.key").is_err());
    }
}
