// Auth extraction and request logging helpers.
//
// These are plain functions called by route handlers, not axum middleware
// layers. This avoids the indirection of inserting AuthSession into request
// extensions and keeps auth logic visible at each call site.

use axum::http::{HeaderMap, StatusCode, header};

use crate::auth::{AuthSession, Authenticator};
use crate::codec::frame::ErrorCode;
use crate::error::Error;
use crate::types::{ApiKey, NamespaceId};

use super::error::HttpError;

/// Extract the Bearer token from the Authorization header.
pub fn extract_bearer_token(headers: &HeaderMap) -> Result<ApiKey, HttpError> {
    let value = headers
        .get(header::AUTHORIZATION)
        .ok_or_else(|| {
            HttpError::new(
                StatusCode::UNAUTHORIZED,
                ErrorCode::NotAuthenticated,
                "Missing Authorization header",
            )
        })?
        .to_str()
        .map_err(|_| {
            HttpError::new(
                StatusCode::UNAUTHORIZED,
                ErrorCode::NotAuthenticated,
                "Invalid Authorization header encoding",
            )
        })?;

    let key = value.strip_prefix("Bearer ").ok_or_else(|| {
        HttpError::new(
            StatusCode::UNAUTHORIZED,
            ErrorCode::NotAuthenticated,
            "Authorization header must use Bearer scheme",
        )
    })?;

    Ok(ApiKey(key.to_owned()))
}

/// Extract the Bearer token, validate it, and return the auth session.
///
/// Logs the request outcome via tracing.
pub fn authenticate(
    auth: &dyn Authenticator,
    headers: &HeaderMap,
) -> Result<AuthSession, HttpError> {
    let key = extract_bearer_token(headers)?;
    auth.authenticate(&key).map_err(|_| {
        tracing::warn!("HTTP auth failed");
        HttpError::new(
            StatusCode::UNAUTHORIZED,
            ErrorCode::AuthFailed,
            "Invalid or missing API key",
        )
    })
}

/// Verify the authenticated session's namespace matches the URL path namespace.
pub fn verify_namespace(session: &AuthSession, ns: &NamespaceId) -> Result<(), HttpError> {
    if session.namespace != *ns {
        return Err(Error::AuthFailed.into());
    }
    Ok(())
}
