use std::collections::HashMap;
use headers::{ContentType, HeaderMapExt, HeaderValue};
use http::header::ACCEPT;
use mas_http::RequestBuilderExt;
use mas_jose::claims;
use mime::Mime;
use serde_json::Value;
use url::Url;
use super::jose::JwtVerificationData;
use crate::{
    error::{IdTokenError, ResponseExt, UserInfoError},
    requests::jose::verify_signed_jwt,
    types::IdToken,
};
#[tracing::instrument(skip_all, fields(userinfo_endpoint))]
pub async fn fetch_userinfo(
    http_client: &reqwest::Client,
    userinfo_endpoint: &Url,
    access_token: &str,
    jwt_verification_data: Option<JwtVerificationData<'_>>,
    auth_id_token: &IdToken<'_>,
) -> Result<HashMap<String, Value>, UserInfoError> {
    tracing::debug!("Obtaining user info…");
    let expected_content_type = if jwt_verification_data.is_some() {
        "application/jwt"
    } else {
        mime::APPLICATION_JSON.as_ref()
    };
    let userinfo_request = http_client
        .get(userinfo_endpoint.as_str())
        .bearer_auth(access_token)
        .header(ACCEPT, HeaderValue::from_static(expected_content_type));
    let userinfo_response = userinfo_request
        .send_traced()
        .await?
        .error_from_oauth2_error_response()
        .await?;
    let content_type: Mime = userinfo_response
        .headers()
        .typed_try_get::<ContentType>()
        .map_err(|_| UserInfoError::InvalidResponseContentTypeValue)?
        .ok_or(UserInfoError::MissingResponseContentType)?
        .into();
    if content_type.essence_str() != expected_content_type {
        return Err(UserInfoError::UnexpectedResponseContentType {
            expected: expected_content_type.to_owned(),
            got: content_type.to_string(),
        });
    }
    let mut claims = if let Some(verification_data) = jwt_verification_data {
        let response_body = userinfo_response.text().await?;
        verify_signed_jwt(&response_body, verification_data)
            .map_err(IdTokenError::from)?
            .into_parts()
            .1
    } else {
        userinfo_response.json().await?
    };
    let mut auth_claims = auth_id_token.payload().clone();
    let sub = claims::SUB
        .extract_required(&mut claims)
        .map_err(IdTokenError::from)?;
    let auth_sub = claims::SUB
        .extract_required(&mut auth_claims)
        .map_err(IdTokenError::from)?;
    if sub != auth_sub {
        return Err(IdTokenError::WrongSubjectIdentifier.into());
    }
    Ok(claims)
}