//! API-key wrapper and the `tonic` interceptor that attaches it as a Bearer
//! token on every outbound gRPC call. The wrapper redacts its inner value in
//! `Debug`/`Display` so logs never leak the secret.

use std::fmt;

use tonic::metadata::MetadataValue;
use tonic::service::Interceptor;
use tonic::{Request, Status};

/// API key wrapper that avoids exposing raw credentials in formatted output.
///
/// Use [`ApiKey::expose_secret`] when the underlying string is genuinely
/// needed (for example, building the `authorization` header).
#[derive(Clone, Eq, PartialEq)]
pub struct ApiKey(String);

impl ApiKey {
    /// Construct an [`ApiKey`] from the raw `mxgw_<key-id>_<secret>` string
    /// returned by the gateway's `apikey` admin command.
    pub fn new(value: impl Into<String>) -> Self {
        Self(value.into())
    }

    /// Return the raw key value. Callers must not log or otherwise persist
    /// the result.
    pub fn expose_secret(&self) -> &str {
        &self.0
    }
}

impl fmt::Debug for ApiKey {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_tuple("ApiKey")
            .field(&"<redacted>")
            .finish()
    }
}

impl fmt::Display for ApiKey {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("<redacted>")
    }
}

/// `tonic` interceptor that attaches gateway API key metadata.
#[derive(Clone, Debug, Default)]
pub struct AuthInterceptor {
    api_key: Option<ApiKey>,
}

impl AuthInterceptor {
    /// Build an interceptor that injects the supplied API key on every
    /// request. Pass `None` to disable authentication (useful for local
    /// development against a gateway with `Authentication:Required = false`).
    pub fn new(api_key: Option<ApiKey>) -> Self {
        Self { api_key }
    }
}

impl Interceptor for AuthInterceptor {
    fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
        if let Some(api_key) = &self.api_key {
            let header_value = format!("Bearer {}", api_key.expose_secret())
                .parse::<MetadataValue<_>>()
                .map_err(|_| Status::unauthenticated("invalid API key metadata"))?;
            request.metadata_mut().insert("authorization", header_value);
        }

        Ok(request)
    }
}

#[cfg(test)]
mod tests {
    use tonic::service::Interceptor;
    use tonic::Request;

    use super::{ApiKey, AuthInterceptor};

    #[test]
    fn api_key_debug_is_redacted() {
        let key = ApiKey::new("mxgw_visible_secret");

        assert_eq!(format!("{key:?}"), "ApiKey(\"<redacted>\")");
        assert!(!format!("{key:?}").contains("visible_secret"));
        assert_eq!(key.to_string(), "<redacted>");
    }

    #[test]
    fn interceptor_attaches_bearer_metadata() {
        let mut interceptor = AuthInterceptor::new(Some(ApiKey::new("mxgw_fixture_secret")));
        let request = interceptor.call(Request::new(())).unwrap();

        assert_eq!(
            request.metadata().get("authorization").unwrap(),
            "Bearer mxgw_fixture_secret"
        );
    }
}
