use crate::security::jwt::Claims;
use axum::body::Body;
use axum::http::{HeaderMap, Request, StatusCode};
use axum::Router;
use http_body_util::BodyExt;
use serde::de::DeserializeOwned;
use serde::Serialize;
use tower::ServiceExt;

pub struct Response<T: DeserializeOwned> {
    pub body: Option<T>,
    pub status_code: StatusCode,
    pub headers: HeaderMap,
}

pub async fn request<T: Serialize, R: DeserializeOwned>(
    router: Router,
    uri: &str,
    method: &str,
    body: T,
    claims: Option<Claims>,
) -> Response<R> {
    let request_body = Body::from(serde_json::to_vec(&body).unwrap());

    let claims = claims.unwrap_or(Claims {
        sub: "sub".to_string(),
        aud: "aud".to_string(),
        iss: "iss".to_string(),
        issued_at: chrono::Utc::now().timestamp() as u64,
        exp: (chrono::Utc::now() + chrono::Duration::days(7)).timestamp() as u64,
        scopes: vec![],
        extra: (),
    });
    let request = Request::builder()
        .header("Content-Type", "application/json")
        .header("X-Claims-Subject", claims.sub)
        .header("X-Claims-Audience", claims.aud)
        .header("X-Claims-Issuer", claims.iss)
        .header("X-Claims-Issued-At", claims.issued_at.to_string())
        .header("X-Claims-Expiration", claims.exp.to_string())
        .header(
            "X-Claims-Scopes",
            claims.scopes.iter().fold(String::new(), |acc, s| {
                if acc.is_empty() {
                    s.to_string()
                } else {
                    format!("{},{}", acc, s)
                }
            }),
        )
        .uri(uri)
        .method(method)
        .body(request_body)
        .unwrap();

    let response = router.oneshot(request).await.unwrap();
    let status_code = response.status();
    let headers = response.headers().clone();

    let body = match status_code.as_u16() {
        200..=299 => {
            let body = response.into_body().collect().await.unwrap().to_bytes();
            let r = serde_json::from_slice::<R>(&body).unwrap();
            Some(r)
        }
        _ => None,
    };
    Response {
        body,
        status_code,
        headers,
    }
}
