//! Authentication implementations for various auth schemes.
//!
//! This module provides authentication implementations that conform to the
//! [`Authenticator`] trait.

use secir::plugin::Authenticator;
use std::collections::HashMap;

/// Basic HTTP authenticator using username and password.
///
/// This authenticator implements HTTP Basic Authentication (RFC 7617).
/// It encodes the credentials using base64 and returns an `Authorization`
/// header.
///
/// # Example
///
/// ```
/// use scanclient::auth::BasicAuthenticator;
/// use secir::plugin::Authenticator;
/// use std::collections::HashMap;
///
/// # async fn example() -> secir::Result<()> {
/// let auth = BasicAuthenticator::new("admin", "secret123");
/// let config = HashMap::new();
/// let headers: HashMap<String, String> = auth.authenticate("https://example.com", &config).await?;
/// assert!(headers.contains_key("Authorization"));
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct BasicAuthenticator {
    username: String,
    password: String,
}

impl BasicAuthenticator {
    /// Creates a new `BasicAuthenticator` with the given credentials.
    pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
        Self {
            username: username.into(),
            password: password.into(),
        }
    }

    /// Creates a new `BasicAuthenticator` from a `username:password` string.
    ///
    /// Returns `None` if the input doesn't contain exactly one colon.
    #[must_use]
    pub fn from_credential_string(credentials: &str) -> Option<Self> {
        let parts: Vec<&str> = credentials.splitn(2, ':').collect();
        if parts.len() != 2 {
            return None;
        }
        Some(Self::new(parts[0], parts[1]))
    }

    /// Returns the base64-encoded credentials for the Authorization header.
    fn encoded_credentials(&self) -> String {
        use base64::{Engine as _, engine::general_purpose};
        let credentials = format!("{}:{}", self.username, self.password);
        general_purpose::STANDARD.encode(&credentials)
    }
}

#[async_trait::async_trait]
impl Authenticator for BasicAuthenticator {
    fn name(&self) -> &'static str {
        "basic-auth"
    }

    async fn authenticate(
        &self,
        _target: &str,
        _config: &HashMap<String, String>,
    ) -> secir::Result<HashMap<String, String>> {
        let mut headers = HashMap::new();
        let auth_value = format!("Basic {}", self.encoded_credentials());
        headers.insert("Authorization".to_string(), auth_value);
        Ok(headers)
    }
}

/// Bearer token authenticator.
///
/// This authenticator implements OAuth 2.0 Bearer Token authentication (RFC 6750).
/// It returns an `Authorization` header with the Bearer token.
#[derive(Debug, Clone)]
pub struct BearerAuthenticator {
    token: String,
}

impl BearerAuthenticator {
    /// Creates a new `BearerAuthenticator` with the given token.
    pub fn new(token: impl Into<String>) -> Self {
        Self {
            token: token.into(),
        }
    }
}

#[async_trait::async_trait]
impl Authenticator for BearerAuthenticator {
    fn name(&self) -> &'static str {
        "bearer-auth"
    }

    async fn authenticate(
        &self,
        _target: &str,
        _config: &HashMap<String, String>,
    ) -> secir::Result<HashMap<String, String>> {
        let mut headers = HashMap::new();
        let auth_value = format!("Bearer {}", self.token);
        headers.insert("Authorization".to_string(), auth_value);
        Ok(headers)
    }
}

/// API key authenticator.
///
/// This authenticator adds an API key as a header or query parameter.
#[derive(Debug, Clone)]
pub struct ApiKeyAuthenticator {
    key_name: String,
    key_value: String,
    in_header: bool, // true = header, false = query param (for now, only header is implemented)
}

impl ApiKeyAuthenticator {
    /// Creates a new `ApiKeyAuthenticator` that adds the key as a header.
    pub fn in_header(name: impl Into<String>, value: impl Into<String>) -> Self {
        Self {
            key_name: name.into(),
            key_value: value.into(),
            in_header: true,
        }
    }
}

#[async_trait::async_trait]
impl Authenticator for ApiKeyAuthenticator {
    fn name(&self) -> &'static str {
        "api-key"
    }

    async fn authenticate(
        &self,
        _target: &str,
        _config: &HashMap<String, String>,
    ) -> secir::Result<HashMap<String, String>> {
        let mut headers = HashMap::new();
        if self.in_header {
            headers.insert(self.key_name.clone(), self.key_value.clone());
        }
        Ok(headers)
    }
}

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

    #[tokio::test]
    async fn basic_auth_returns_name() {
        let auth = BasicAuthenticator::new("admin", "password");
        assert_eq!(auth.name(), "basic-auth");
    }

    #[tokio::test]
    async fn basic_auth_generates_authorization_header() {
        let auth = BasicAuthenticator::new("admin", "password");
        let config = HashMap::new();
        let headers = auth
            .authenticate("https://example.com", &config)
            .await
            .expect("operation should succeed");

        assert!(headers.contains_key("Authorization"));
        let auth_header = headers
            .get("Authorization")
            .expect("operation should succeed");
        assert!(auth_header.starts_with("Basic "));
        // Verify it's base64 encoded "admin:password"
        assert_eq!(auth_header, "Basic YWRtaW46cGFzc3dvcmQ=");
    }

    #[tokio::test]
    async fn basic_auth_from_credential_string() {
        let auth = BasicAuthenticator::from_credential_string("admin:secret123")
            .expect("operation should succeed");
        let config = HashMap::new();
        let headers = auth
            .authenticate("https://example.com", &config)
            .await
            .expect("operation should succeed");

        assert!(headers.contains_key("Authorization"));
        let auth_header = headers
            .get("Authorization")
            .expect("operation should succeed");
        assert!(auth_header.starts_with("Basic "));
    }

    #[tokio::test]
    async fn basic_auth_from_invalid_credential_string() {
        // No colon
        assert!(BasicAuthenticator::from_credential_string("admin").is_none());
        // Multiple colons (should still work, password contains colons)
        let auth = BasicAuthenticator::from_credential_string("admin:pass:word")
            .expect("operation should succeed");
        assert_eq!(auth.username, "admin");
        assert_eq!(auth.password, "pass:word");
    }

    #[tokio::test]
    async fn bearer_auth_returns_name() {
        let auth = BearerAuthenticator::new("my-token");
        assert_eq!(auth.name(), "bearer-auth");
    }

    #[tokio::test]
    async fn bearer_auth_generates_authorization_header() {
        let auth = BearerAuthenticator::new("my-secret-token");
        let config = HashMap::new();
        let headers = auth
            .authenticate("https://example.com", &config)
            .await
            .expect("operation should succeed");

        assert!(headers.contains_key("Authorization"));
        let auth_header = headers
            .get("Authorization")
            .expect("operation should succeed");
        assert_eq!(auth_header, "Bearer my-secret-token");
    }

    #[tokio::test]
    async fn api_key_auth_in_header() {
        let auth = ApiKeyAuthenticator::in_header("X-API-Key", "my-api-key");
        let config = HashMap::new();
        let headers = auth
            .authenticate("https://example.com", &config)
            .await
            .expect("operation should succeed");

        assert!(headers.contains_key("X-API-Key"));
        assert_eq!(
            headers.get("X-API-Key").expect("operation should succeed"),
            "my-api-key"
        );
    }

    #[tokio::test]
    async fn basic_auth_different_targets() {
        let auth = BasicAuthenticator::new("user", "pass");
        let config = HashMap::new();

        // Should work for any target
        let headers1 = auth
            .authenticate("https://example.com", &config)
            .await
            .expect("operation should succeed");
        let headers2 = auth
            .authenticate("https://api.example.com", &config)
            .await
            .expect("operation should succeed");

        assert_eq!(headers1.get("Authorization"), headers2.get("Authorization"));
    }
}
