//! Axum extractor for the per-request `TenantContext`.
//!
//! Handlers consume it as a normal extractor argument:
//!
//! ```ignore
//! async fn list_findings(TenantCtx(ctx): TenantCtx) -> Json<...> {
//!     let filter = compliance_core::db::tenant_filter(&ctx);
//!     ...
//! }
//! ```
//!
//! The middleware ([`crate::auth::require_jwt_auth`]) is responsible for
//! inserting the context into the request extensions. If it's missing on
//! a route that uses this extractor, that's a bug in the wiring — we
//! return 401 so the caller sees an auth failure rather than a 500.

use axum::{
    extract::FromRequestParts,
    http::{request::Parts, StatusCode},
    response::{IntoResponse, Response},
};

use crate::TenantContext;

#[derive(Debug, Clone)]
pub struct TenantCtx(pub TenantContext);

#[derive(Debug)]
pub struct TenantCtxRejection;

impl IntoResponse for TenantCtxRejection {
    fn into_response(self) -> Response {
        (
            StatusCode::UNAUTHORIZED,
            "Missing tenant context — request was not authenticated",
        )
            .into_response()
    }
}

impl<S> FromRequestParts<S> for TenantCtx
where
    S: Send + Sync,
{
    type Rejection = TenantCtxRejection;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        parts
            .extensions
            .get::<TenantContext>()
            .cloned()
            .map(TenantCtx)
            .ok_or(TenantCtxRejection)
    }
}

#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
    use super::*;
    use crate::TenantStatus;
    use axum::http::Request;

    fn ctx() -> TenantContext {
        TenantContext {
            tenant_id: "t-1".to_string(),
            tenant_slug: "acme".to_string(),
            org_roles: vec![],
            products: vec![],
            plan: "starter".to_string(),
            status: TenantStatus::Active,
            user_id: "u-1".to_string(),
            user_name: None,
        }
    }

    #[tokio::test]
    async fn extracts_context_when_present() {
        let mut req = Request::new(());
        req.extensions_mut().insert(ctx());
        let (mut parts, _) = req.into_parts();
        let TenantCtx(found) = TenantCtx::from_request_parts(&mut parts, &())
            .await
            .expect("extractor should succeed");
        assert_eq!(found.tenant_id, "t-1");
    }

    #[tokio::test]
    async fn rejects_when_missing() {
        let req: Request<()> = Request::new(());
        let (mut parts, _) = req.into_parts();
        let err = TenantCtx::from_request_parts(&mut parts, &()).await;
        assert!(err.is_err());
    }
}
