//! Credential resolution for Dirigent accounts.

use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum SecretError {
    #[error("Environment variable '{key}' not set")]
    EnvNotSet { key: String },
    #[error("Failed to read secret file '{path}': {reason}")]
    FileReadFailed { path: String, reason: String },
}

/// Describes how to retrieve a secret value (password, token, etc.).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "source")]
pub enum SecretSource {
    #[serde(rename = "env")]
    Env { key: String },
    #[serde(rename = "inline")]
    Inline { value: String },
    #[serde(rename = "file")]
    File { path: String },
}

impl SecretSource {
    pub fn resolve(&self) -> Result<String, SecretError> {
        match self {
            SecretSource::Env { key } => {
                std::env::var(key).map_err(|_| SecretError::EnvNotSet { key: key.clone() })
            }
            SecretSource::Inline { value } => Ok(value.clone()),
            SecretSource::File { path } => std::fs::read_to_string(path)
                .map(|s| s.trim().to_string())
                .map_err(|e| SecretError::FileReadFailed {
                    path: path.clone(),
                    reason: e.to_string(),
                }),
        }
    }
}

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

    #[test]
    fn test_inline_resolve() {
        let src = SecretSource::Inline { value: "hunter2".to_string() };
        assert_eq!(src.resolve().unwrap(), "hunter2");
    }

    #[test]
    fn test_env_resolve() {
        std::env::set_var("DIRIGENT_TEST_SECRET_7382", "env_value");
        let src = SecretSource::Env { key: "DIRIGENT_TEST_SECRET_7382".to_string() };
        assert_eq!(src.resolve().unwrap(), "env_value");
        std::env::remove_var("DIRIGENT_TEST_SECRET_7382");
    }

    #[test]
    fn test_env_missing() {
        let src = SecretSource::Env { key: "DIRIGENT_NONEXISTENT_VAR_99999".to_string() };
        assert!(src.resolve().is_err());
    }

    #[test]
    fn test_file_missing() {
        let src = SecretSource::File { path: "/tmp/dirigent_nonexistent_secret_file".to_string() };
        assert!(src.resolve().is_err());
    }

    #[test]
    fn test_serde_roundtrip_env() {
        let src = SecretSource::Env { key: "MY_VAR".to_string() };
        let json = serde_json::to_string(&src).unwrap();
        assert!(json.contains(r#""source":"env"#));
        let back: SecretSource = serde_json::from_str(&json).unwrap();
        assert!(matches!(back, SecretSource::Env { key } if key == "MY_VAR"));
    }

    #[test]
    fn test_serde_roundtrip_inline() {
        let src = SecretSource::Inline { value: "secret".to_string() };
        let json = serde_json::to_string(&src).unwrap();
        let back: SecretSource = serde_json::from_str(&json).unwrap();
        assert!(matches!(back, SecretSource::Inline { value } if value == "secret"));
    }

    #[test]
    fn test_serde_roundtrip_file() {
        let src = SecretSource::File { path: "/run/secrets/pw".to_string() };
        let json = serde_json::to_string(&src).unwrap();
        assert!(json.contains(r#""source":"file"#));
        let back: SecretSource = serde_json::from_str(&json).unwrap();
        assert!(matches!(back, SecretSource::File { path } if path == "/run/secrets/pw"));
    }
}
