//! Per-request signing step shared by the HTTP transports (,
//! ).
//!
//! mTLS is applied at client-build time; AWS SigV4 and Web Bot Auth are
//! per-request signers because their signatures are time-bound and
//! commit to the request line and body. The transport builds a request,
//! asks the [`RequestSigner`] for the headers to attach, then sends. The
//! signer composes with bearer tokens and static headers (those ride on
//! the same request unchanged).

use super::{sigv4, AuthError, SigV4Config, WebBotAuthConfig};

/// Holds the optional per-request signers for one transport.
///
/// Both fields are `None` in the common case, in which case
/// [`RequestSigner::sign`] returns no headers and the request is sent
/// as-is.
#[derive(Debug, Clone, Default)]
pub struct RequestSigner {
    /// AWS SigV4 signer config.
    pub sigv4: Option<SigV4Config>,
    /// Web Bot Auth signer config.
    pub web_bot_auth: Option<WebBotAuthConfig>,
}

/// A name/value header pair the transport attaches to the outbound
/// request after signing.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignedHeader {
    /// Header name.
    pub name: String,
    /// Header value.
    pub value: String,
}

impl RequestSigner {
    /// Build a signer from the optional configs. Returns `None` when
    /// neither scheme is set, so the transport can skip the body-capture
    /// work entirely in the common unsigned case.
    pub fn from_configs(
        sigv4: Option<SigV4Config>,
        web_bot_auth: Option<WebBotAuthConfig>,
    ) -> Option<Self> {
        if sigv4.is_none() && web_bot_auth.is_none() {
            return None;
        }
        Some(Self {
            sigv4,
            web_bot_auth,
        })
    }

    /// Sign one request and return the headers to attach.
    ///
    /// `method` is the HTTP method, `url` the absolute request URL, and
    /// `body` the request body the signature commits to (empty for a
    /// GET). Both configured schemes contribute their headers; they
    /// compose.
    pub fn sign(
        &self,
        method: &str,
        url: &str,
        body: &[u8],
    ) -> Result<Vec<SignedHeader>, AuthError> {
        let mut out = Vec::new();
        if let Some(cfg) = &self.sigv4 {
            for h in sigv4::sign_request(method, url, body, cfg)? {
                out.push(SignedHeader {
                    name: h.name,
                    value: h.value,
                });
            }
        }
        if let Some(cfg) = &self.web_bot_auth {
            for h in super::web_bot_auth::sign_request(method, url, body, cfg)? {
                out.push(SignedHeader {
                    name: h.name,
                    value: h.value,
                });
            }
        }
        Ok(out)
    }
}

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

    #[test]
    fn empty_signer_is_skipped() {
        assert!(RequestSigner::from_configs(None, None).is_none());
    }

    #[test]
    fn sigv4_only_signer_is_constructed() {
        let cfg = SigV4Config {
            region: "us-east-1".into(),
            service: "execute-api".into(),
            credentials: super::super::SigV4Credentials::FromEnvironment,
            signature_lifetime: None,
        };
        let signer = RequestSigner::from_configs(Some(cfg), None).expect("signer");
        assert!(signer.sigv4.is_some());
        assert!(signer.web_bot_auth.is_none());
    }
}
