use std::collections::{BTreeSet, HashMap};
use std::fmt::{Debug, Formatter};
use http::{HeaderMap, HeaderName, HeaderValue};
use reqwest::IntoUrl;
use url::Url;
use uuid::Uuid;
use graph_core::crypto::{secure_random_32, ProofKeyCodeExchange};
use graph_error::{IdentityResult, AF};
use crate::identity::{
    AppConfig, AsQuery, AuthorizationCodeAssertionCredentialBuilder,
    AuthorizationCodeCredentialBuilder, AuthorizationUrl, AzureCloudInstance, Prompt, ResponseMode,
    ResponseType,
};
use crate::oauth_serializer::{AuthParameter, AuthSerializer};
#[cfg(feature = "openssl")]
use crate::identity::X509Certificate;
#[cfg(feature = "interactive-auth")]
use {
    crate::identity::{
        tracing_targets::INTERACTIVE_AUTH, AuthorizationCodeCertificateCredentialBuilder,
        AuthorizationResponse, Token,
    },
    crate::interactive::{
        HostOptions, InteractiveAuthEvent, UserEvents, WebViewAuth, WebViewAuthorizationEvent,
        WebViewHostValidator, WebViewOptions, WithInteractiveAuth,
    },
    crate::{Assertion, Secret},
    graph_error::{AuthExecutionError, WebViewError, WebViewResult},
    tao::{event_loop::EventLoopProxy, window::Window},
    wry::{WebView, WebViewBuilder},
};
credential_builder_base!(AuthCodeAuthorizationUrlParameterBuilder);
#[derive(Clone)]
pub struct AuthCodeAuthorizationUrlParameters {
    pub(crate) app_config: AppConfig,
    pub(crate) response_type: BTreeSet<ResponseType>,
    pub(crate) response_mode: Option<ResponseMode>,
    pub(crate) nonce: Option<String>,
    pub(crate) state: Option<String>,
    pub(crate) prompt: BTreeSet<Prompt>,
    pub(crate) domain_hint: Option<String>,
    pub(crate) login_hint: Option<String>,
    pub(crate) code_challenge: Option<String>,
    pub(crate) code_challenge_method: Option<String>,
}
impl Debug for AuthCodeAuthorizationUrlParameters {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AuthCodeAuthorizationUrlParameters")
            .field("app_config", &self.app_config)
            .field("response_type", &self.response_type)
            .field("response_mode", &self.response_mode)
            .field("prompt", &self.prompt)
            .finish()
    }
}
impl AuthCodeAuthorizationUrlParameters {
    pub fn new(
        client_id: impl AsRef<str>,
        redirect_uri: impl IntoUrl,
    ) -> IdentityResult<AuthCodeAuthorizationUrlParameters> {
        let mut response_type = BTreeSet::new();
        response_type.insert(ResponseType::Code);
        let redirect_uri_result = Url::parse(redirect_uri.as_str());
        Ok(AuthCodeAuthorizationUrlParameters {
            app_config: AppConfig::builder(client_id.as_ref())
                .redirect_uri(redirect_uri.into_url().or(redirect_uri_result)?)
                .build(),
            response_type,
            response_mode: None,
            nonce: None,
            state: None,
            prompt: Default::default(),
            domain_hint: None,
            login_hint: None,
            code_challenge: None,
            code_challenge_method: None,
        })
    }
    pub fn builder(client_id: impl TryInto<Uuid>) -> AuthCodeAuthorizationUrlParameterBuilder {
        AuthCodeAuthorizationUrlParameterBuilder::new(client_id)
    }
    pub fn url(&self) -> IdentityResult<Url> {
        self.url_with_host(&AzureCloudInstance::default())
    }
    pub fn url_with_host(&self, azure_cloud_instance: &AzureCloudInstance) -> IdentityResult<Url> {
        self.authorization_url_with_host(azure_cloud_instance)
    }
    pub fn into_credential(
        self,
        authorization_code: impl AsRef<str>,
    ) -> AuthorizationCodeCredentialBuilder {
        AuthorizationCodeCredentialBuilder::new_with_auth_code(authorization_code, self.app_config)
    }
    pub fn into_assertion_credential(
        self,
        authorization_code: impl AsRef<str>,
    ) -> AuthorizationCodeAssertionCredentialBuilder {
        AuthorizationCodeAssertionCredentialBuilder::new_with_auth_code(
            self.app_config,
            authorization_code,
        )
    }
    #[cfg(feature = "openssl")]
    pub fn into_certificate_credential(
        self,
        authorization_code: impl AsRef<str>,
        x509: &X509Certificate,
    ) -> IdentityResult<AuthorizationCodeCertificateCredentialBuilder> {
        AuthorizationCodeCertificateCredentialBuilder::new_with_auth_code_and_x509(
            authorization_code,
            x509,
            self.app_config,
        )
    }
    pub fn nonce(&mut self) -> Option<&String> {
        self.nonce.as_ref()
    }
    #[cfg(feature = "interactive-auth")]
    pub(crate) fn interactive_webview_authentication(
        &self,
        options: WebViewOptions,
    ) -> WebViewResult<AuthorizationResponse> {
        let uri = self
            .url()
            .map_err(|err| Box::new(AuthExecutionError::from(err)))?;
        let redirect_uri = self.redirect_uri().cloned().unwrap();
        let (sender, receiver) = std::sync::mpsc::channel();
        std::thread::spawn(move || {
            AuthCodeAuthorizationUrlParameters::run(uri, vec![redirect_uri], options, sender)
                .unwrap();
        });
        let mut iter = receiver.try_iter();
        let mut next = iter.next();
        while next.is_none() {
            next = iter.next();
        }
        match next {
            None => unreachable!(),
            Some(auth_event) => match auth_event {
                InteractiveAuthEvent::InvalidRedirectUri(reason) => {
                    Err(WebViewError::InvalidUri(reason))
                }
                InteractiveAuthEvent::ReachedRedirectUri(uri) => {
                    let query = uri
                        .query()
                        .or(uri.fragment())
                        .ok_or(WebViewError::InvalidUri(format!(
                            "uri missing query or fragment: {}",
                            uri
                        )))?;
                    let response_query: AuthorizationResponse =
                        serde_urlencoded::from_str(query)
                            .map_err(|err| WebViewError::InvalidUri(err.to_string()))?;
                    if response_query.is_err() {
                        tracing::debug!(target: INTERACTIVE_AUTH, "error in authorization query or fragment from redirect uri");
                        return Err(WebViewError::Authorization {
                            error: response_query
                                .error
                                .map(|query_error| query_error.to_string())
                                .unwrap_or_default(),
                            error_description: response_query.error_description.unwrap_or_default(),
                            error_uri: response_query.error_uri.map(|uri| uri.to_string()),
                        });
                    }
                    tracing::debug!(target: INTERACTIVE_AUTH, "parsed authorization query or fragment from redirect uri");
                    Ok(response_query)
                }
                InteractiveAuthEvent::WindowClosed(window_close_reason) => {
                    Err(WebViewError::WindowClosed(window_close_reason.to_string()))
                }
            },
        }
    }
    #[allow(dead_code)]
    #[cfg(feature = "interactive-auth")]
    pub(crate) fn interactive_authentication_builder(
        &self,
        options: WebViewOptions,
    ) -> WebViewResult<AuthorizationResponse> {
        let uri = self
            .url()
            .map_err(|err| Box::new(AuthExecutionError::from(err)))?;
        let redirect_uri = self.redirect_uri().cloned().unwrap();
        let (sender, receiver) = std::sync::mpsc::channel();
        std::thread::spawn(move || {
            AuthCodeAuthorizationUrlParameters::run(uri, vec![redirect_uri], options, sender)
                .unwrap();
        });
        let mut iter = receiver.try_iter();
        let mut next = iter.next();
        while next.is_none() {
            next = iter.next();
        }
        match next {
            None => unreachable!(),
            Some(auth_event) => match auth_event {
                InteractiveAuthEvent::InvalidRedirectUri(reason) => {
                    Err(WebViewError::InvalidUri(reason))
                }
                InteractiveAuthEvent::ReachedRedirectUri(uri) => {
                    let query = uri
                        .query()
                        .or(uri.fragment())
                        .ok_or(WebViewError::InvalidUri(format!(
                            "uri missing query or fragment: {}",
                            uri
                        )))?;
                    let response_query: AuthorizationResponse =
                        serde_urlencoded::from_str(query)
                            .map_err(|err| WebViewError::InvalidUri(err.to_string()))?;
                    Ok(response_query)
                }
                InteractiveAuthEvent::WindowClosed(window_close_reason) => {
                    Err(WebViewError::WindowClosed(window_close_reason.to_string()))
                }
            },
        }
    }
}
#[cfg(feature = "interactive-auth")]
mod internal {
    use super::*;
    impl WebViewAuth for AuthCodeAuthorizationUrlParameters {
        fn webview(
            host_options: HostOptions,
            window: &Window,
            proxy: EventLoopProxy<UserEvents>,
        ) -> anyhow::Result<WebView> {
            let start_uri = host_options.start_uri.clone();
            let validator = WebViewHostValidator::try_from(host_options)?;
            Ok(WebViewBuilder::new(window)
                .with_url(start_uri.as_ref())
                .with_file_drop_handler(|_| true)
                .with_navigation_handler(move |uri| {
                    if let Ok(url) = Url::parse(uri.as_str()) {
                        let is_valid_host = validator.is_valid_uri(&url);
                        let is_redirect = validator.is_redirect_host(&url);
                        if is_redirect {
                            proxy.send_event(UserEvents::ReachedRedirectUri(url))
                                .unwrap();
                            proxy.send_event(UserEvents::InternalCloseWindow)
                                .unwrap();
                            return true;
                        }
                        is_valid_host
                    } else {
                        tracing::debug!(target: INTERACTIVE_AUTH, "unable to navigate webview - url is none");
                        proxy.send_event(UserEvents::CloseWindow).unwrap();
                        false
                    }
                })
                .build()?)
        }
    }
}
impl AuthorizationUrl for AuthCodeAuthorizationUrlParameters {
    fn redirect_uri(&self) -> Option<&Url> {
        self.app_config.redirect_uri.as_ref()
    }
    fn authorization_url(&self) -> IdentityResult<Url> {
        self.authorization_url_with_host(&AzureCloudInstance::default())
    }
    fn authorization_url_with_host(
        &self,
        azure_cloud_instance: &AzureCloudInstance,
    ) -> IdentityResult<Url> {
        let mut serializer = AuthSerializer::new();
        if let Some(redirect_uri) = self.app_config.redirect_uri.as_ref() {
            if redirect_uri.as_str().trim().is_empty() {
                return AF::result("redirect_uri");
            } else {
                serializer.redirect_uri(redirect_uri.as_str());
            }
        }
        let client_id = self.app_config.client_id.to_string();
        if client_id.is_empty() || self.app_config.client_id.is_nil() {
            return AF::result("client_id");
        }
        if self.app_config.scope.is_empty() {
            return AF::result("scope");
        }
        serializer
            .client_id(client_id.as_str())
            .set_scope(self.app_config.scope.clone());
        let response_types: Vec<String> =
            self.response_type.iter().map(|s| s.to_string()).collect();
        if response_types.is_empty() {
            serializer.response_type("code");
            if let Some(response_mode) = self.response_mode.as_ref() {
                serializer.response_mode(response_mode.as_ref());
            }
        } else {
            let response_type = response_types.join(" ").trim().to_owned();
            if response_type.is_empty() {
                serializer.response_type("code");
            } else {
                serializer.response_type(response_type);
            }
            if self.response_type.contains(&ResponseType::IdToken) {
                if self.response_mode.eq(&Some(ResponseMode::Query)) {
                    return Err(AF::msg_err(
                        "response_mode",
                        "ResponseType::IdToken requires ResponseMode::Fragment or ResponseMode::FormPost")
                    );
                } else if let Some(response_mode) = self.response_mode.as_ref() {
                    serializer.response_mode(response_mode.as_ref());
                }
            } else if let Some(response_mode) = self.response_mode.as_ref() {
                serializer.response_mode(response_mode.as_ref());
            }
        }
        if let Some(state) = self.state.as_ref() {
            serializer.state(state.as_str());
        }
        if !self.prompt.is_empty() {
            serializer.prompt(&self.prompt.as_query());
        }
        if let Some(domain_hint) = self.domain_hint.as_ref() {
            serializer.domain_hint(domain_hint.as_str());
        }
        if let Some(login_hint) = self.login_hint.as_ref() {
            serializer.login_hint(login_hint.as_str());
        }
        if let Some(nonce) = self.nonce.as_ref() {
            serializer.nonce(nonce);
        }
        if let Some(code_challenge) = self.code_challenge.as_ref() {
            serializer.code_challenge(code_challenge.as_str());
        }
        if let Some(code_challenge_method) = self.code_challenge_method.as_ref() {
            serializer.code_challenge_method(code_challenge_method.as_str());
        }
        let query = serializer.encode_query(
            vec![
                AuthParameter::ResponseMode,
                AuthParameter::State,
                AuthParameter::Prompt,
                AuthParameter::LoginHint,
                AuthParameter::DomainHint,
                AuthParameter::Nonce,
                AuthParameter::CodeChallenge,
                AuthParameter::CodeChallengeMethod,
            ],
            vec![
                AuthParameter::ClientId,
                AuthParameter::ResponseType,
                AuthParameter::RedirectUri,
                AuthParameter::Scope,
            ],
        )?;
        let mut uri = azure_cloud_instance.auth_uri(&self.app_config.authority)?;
        uri.set_query(Some(query.as_str()));
        Ok(uri)
    }
}
#[derive(Clone)]
pub struct AuthCodeAuthorizationUrlParameterBuilder {
    credential: AuthCodeAuthorizationUrlParameters,
}
impl AuthCodeAuthorizationUrlParameterBuilder {
    pub fn new(client_id: impl TryInto<Uuid>) -> AuthCodeAuthorizationUrlParameterBuilder {
        let mut response_type = BTreeSet::new();
        response_type.insert(ResponseType::Code);
        AuthCodeAuthorizationUrlParameterBuilder {
            credential: AuthCodeAuthorizationUrlParameters {
                app_config: AppConfig::new(client_id),
                response_mode: None,
                response_type,
                nonce: None,
                state: None,
                prompt: Default::default(),
                domain_hint: None,
                login_hint: None,
                code_challenge: None,
                code_challenge_method: None,
            },
        }
    }
    pub(crate) fn new_with_app_config(
        app_config: AppConfig,
    ) -> AuthCodeAuthorizationUrlParameterBuilder {
        let mut response_type = BTreeSet::new();
        response_type.insert(ResponseType::Code);
        AuthCodeAuthorizationUrlParameterBuilder {
            credential: AuthCodeAuthorizationUrlParameters {
                app_config,
                response_mode: None,
                response_type,
                nonce: None,
                state: None,
                prompt: Default::default(),
                domain_hint: None,
                login_hint: None,
                code_challenge: None,
                code_challenge_method: None,
            },
        }
    }
    pub fn with_redirect_uri(&mut self, redirect_uri: Url) -> &mut Self {
        self.credential.app_config.redirect_uri = Some(redirect_uri);
        self
    }
    pub fn with_response_type<I: IntoIterator<Item = ResponseType>>(
        &mut self,
        response_type: I,
    ) -> &mut Self {
        self.credential.response_type = response_type.into_iter().collect();
        self
    }
    pub fn with_response_mode(&mut self, response_mode: ResponseMode) -> &mut Self {
        self.credential.response_mode = Some(response_mode);
        self
    }
    pub fn with_nonce<T: AsRef<str>>(&mut self, nonce: T) -> &mut Self {
        self.credential.nonce = Some(nonce.as_ref().to_owned());
        self
    }
    pub fn with_generated_nonce(&mut self) -> &mut Self {
        self.credential.nonce = Some(secure_random_32());
        self
    }
    pub fn with_state<T: AsRef<str>>(&mut self, state: T) -> &mut Self {
        self.credential.state = Some(state.as_ref().to_owned());
        self
    }
    pub fn with_prompt<I: IntoIterator<Item = Prompt>>(&mut self, prompt: I) -> &mut Self {
        self.credential.prompt.extend(prompt.into_iter());
        self
    }
    pub fn with_domain_hint<T: AsRef<str>>(&mut self, domain_hint: T) -> &mut Self {
        self.credential.domain_hint = Some(domain_hint.as_ref().to_owned());
        self
    }
    pub fn with_login_hint<T: AsRef<str>>(&mut self, login_hint: T) -> &mut Self {
        self.credential.login_hint = Some(login_hint.as_ref().to_owned());
        self
    }
    pub fn with_code_challenge<T: AsRef<str>>(&mut self, code_challenge: T) -> &mut Self {
        self.credential.code_challenge = Some(code_challenge.as_ref().to_owned());
        self
    }
    pub fn with_code_challenge_method<T: AsRef<str>>(
        &mut self,
        code_challenge_method: T,
    ) -> &mut Self {
        self.credential.code_challenge_method = Some(code_challenge_method.as_ref().to_owned());
        self
    }
    pub fn with_pkce(&mut self, proof_key_for_code_exchange: &ProofKeyCodeExchange) -> &mut Self {
        self.with_code_challenge(proof_key_for_code_exchange.code_challenge.as_str());
        self.with_code_challenge_method(proof_key_for_code_exchange.code_challenge_method.as_str());
        self
    }
    pub fn build(&self) -> AuthCodeAuthorizationUrlParameters {
        self.credential.clone()
    }
    pub fn url_with_host(&self, azure_cloud_instance: &AzureCloudInstance) -> IdentityResult<Url> {
        self.credential.url_with_host(azure_cloud_instance)
    }
    pub fn url(&self) -> IdentityResult<Url> {
        self.credential.url()
    }
    pub fn with_auth_code(
        self,
        authorization_code: impl AsRef<str>,
    ) -> AuthorizationCodeCredentialBuilder {
        AuthorizationCodeCredentialBuilder::new_with_auth_code(
            authorization_code,
            self.credential.app_config,
        )
    }
    pub fn with_auth_code_assertion(
        self,
        authorization_code: impl AsRef<str>,
    ) -> AuthorizationCodeAssertionCredentialBuilder {
        AuthorizationCodeAssertionCredentialBuilder::new_with_auth_code(
            self.credential.app_config,
            authorization_code,
        )
    }
    #[cfg(feature = "openssl")]
    pub fn with_auth_code_x509_certificate(
        self,
        authorization_code: impl AsRef<str>,
        x509: &X509Certificate,
    ) -> IdentityResult<AuthorizationCodeCertificateCredentialBuilder> {
        AuthorizationCodeCertificateCredentialBuilder::new_with_auth_code_and_x509(
            authorization_code,
            x509,
            self.credential.app_config,
        )
    }
}
#[cfg(feature = "interactive-auth")]
impl WithInteractiveAuth<Secret> for AuthCodeAuthorizationUrlParameterBuilder {
    type CredentialBuilder = AuthorizationCodeCredentialBuilder;
    fn with_interactive_auth(
        &self,
        auth_type: Secret,
        options: WebViewOptions,
    ) -> WebViewResult<WebViewAuthorizationEvent<Self::CredentialBuilder>> {
        let authorization_response = self
            .credential
            .interactive_webview_authentication(options)?;
        if authorization_response.is_err() {
            tracing::debug!(target: INTERACTIVE_AUTH, "error in authorization query or fragment from redirect uri");
            return Ok(WebViewAuthorizationEvent::Unauthorized(
                authorization_response,
            ));
        }
        tracing::debug!(target: INTERACTIVE_AUTH, "parsed authorization query or fragment from redirect uri");
        let mut credential_builder = {
            if let Some(authorization_code) = authorization_response.code.as_ref() {
                AuthorizationCodeCredentialBuilder::new_with_auth_code(
                    authorization_code,
                    self.credential.app_config.clone(),
                )
            } else {
                AuthorizationCodeCredentialBuilder::new_with_token(
                    self.credential.app_config.clone(),
                    Token::try_from(authorization_response.clone())?,
                )
            }
        };
        credential_builder.with_client_secret(auth_type.0);
        Ok(WebViewAuthorizationEvent::Authorized {
            authorization_response,
            credential_builder,
        })
    }
}
#[cfg(feature = "interactive-auth")]
impl WithInteractiveAuth<Assertion> for AuthCodeAuthorizationUrlParameterBuilder {
    type CredentialBuilder = AuthorizationCodeAssertionCredentialBuilder;
    fn with_interactive_auth(
        &self,
        auth_type: Assertion,
        options: WebViewOptions,
    ) -> WebViewResult<WebViewAuthorizationEvent<Self::CredentialBuilder>> {
        let authorization_response = self
            .credential
            .interactive_webview_authentication(options)?;
        if authorization_response.is_err() {
            tracing::debug!(target: INTERACTIVE_AUTH, "error in authorization query or fragment from redirect uri");
            return Ok(WebViewAuthorizationEvent::Unauthorized(
                authorization_response,
            ));
        }
        tracing::debug!(target: INTERACTIVE_AUTH, "parsed authorization query or fragment from redirect uri");
        let mut credential_builder = {
            if let Some(authorization_code) = authorization_response.code.as_ref() {
                AuthorizationCodeAssertionCredentialBuilder::new_with_auth_code(
                    self.credential.app_config.clone(),
                    authorization_code,
                )
            } else {
                AuthorizationCodeAssertionCredentialBuilder::new_with_token(
                    self.credential.app_config.clone(),
                    Token::try_from(authorization_response.clone())?,
                )
            }
        };
        credential_builder.with_client_assertion(auth_type.0);
        Ok(WebViewAuthorizationEvent::Authorized {
            authorization_response,
            credential_builder,
        })
    }
}
#[cfg(feature = "openssl")]
#[cfg(feature = "interactive-auth")]
impl WithInteractiveAuth<&X509Certificate> for AuthCodeAuthorizationUrlParameterBuilder {
    type CredentialBuilder = AuthorizationCodeCertificateCredentialBuilder;
    fn with_interactive_auth(
        &self,
        auth_type: &X509Certificate,
        options: WebViewOptions,
    ) -> WebViewResult<WebViewAuthorizationEvent<Self::CredentialBuilder>> {
        let authorization_response = self
            .credential
            .interactive_webview_authentication(options)?;
        if authorization_response.is_err() {
            tracing::debug!(target: INTERACTIVE_AUTH, "error in authorization query or fragment from redirect uri");
            return Ok(WebViewAuthorizationEvent::Unauthorized(
                authorization_response,
            ));
        }
        tracing::debug!(target: INTERACTIVE_AUTH, "parsed authorization query or fragment from redirect uri");
        let mut credential_builder = {
            if let Some(authorization_code) = authorization_response.code.as_ref() {
                AuthorizationCodeCertificateCredentialBuilder::new_with_auth_code_and_x509(
                    authorization_code,
                    auth_type,
                    self.credential.app_config.clone(),
                )?
            } else {
                AuthorizationCodeCertificateCredentialBuilder::new_with_token(
                    Token::try_from(authorization_response.clone())?,
                    auth_type,
                    self.credential.app_config.clone(),
                )?
            }
        };
        credential_builder.with_x509(auth_type)?;
        Ok(WebViewAuthorizationEvent::Authorized {
            authorization_response,
            credential_builder,
        })
    }
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn serialize_uri() {
        let authorizer = AuthCodeAuthorizationUrlParameters::builder(Uuid::new_v4())
            .with_redirect_uri(Url::parse("https://localhost:8080").unwrap())
            .with_scope(["read", "write"])
            .build();
        let url_result = authorizer.url();
        assert!(url_result.is_ok());
    }
    #[test]
    fn url_with_host() {
        let url_result = AuthCodeAuthorizationUrlParameters::builder(Uuid::new_v4())
            .with_redirect_uri(Url::parse("https://localhost:8080").unwrap())
            .with_scope(["read", "write"])
            .url_with_host(&AzureCloudInstance::AzureGermany);
        assert!(url_result.is_ok());
    }
    #[test]
    #[should_panic]
    fn response_type_id_token_panics_when_response_mode_query() {
        let url = AuthCodeAuthorizationUrlParameters::builder(Uuid::new_v4())
            .with_redirect_uri(Url::parse("https://localhost:8080").unwrap())
            .with_scope(["read", "write"])
            .with_response_mode(ResponseMode::Query)
            .with_response_type(vec![ResponseType::IdToken])
            .url()
            .unwrap();
        let _query = url.query().unwrap();
    }
    #[test]
    fn response_mode_not_set() {
        let url = AuthCodeAuthorizationUrlParameters::builder(Uuid::new_v4())
            .with_redirect_uri(Url::parse("https://localhost:8080").unwrap())
            .with_scope(["read", "write"])
            .url()
            .unwrap();
        let query = url.query().unwrap();
        assert!(!query.contains("response_mode"));
        assert!(query.contains("response_type=code"));
    }
    #[test]
    fn multi_response_type_set() {
        let url = AuthCodeAuthorizationUrlParameters::builder(Uuid::new_v4())
            .with_redirect_uri(Url::parse("https://localhost:8080").unwrap())
            .with_scope(["read", "write"])
            .with_response_mode(ResponseMode::FormPost)
            .with_response_type(vec![ResponseType::IdToken, ResponseType::Code])
            .url()
            .unwrap();
        let query = url.query().unwrap();
        assert!(query.contains("response_mode=form_post"));
        assert!(query.contains("response_type=code+id_token"));
    }
    #[test]
    fn generate_nonce() {
        let url = AuthCodeAuthorizationUrlParameters::builder(Uuid::new_v4())
            .with_redirect_uri(Url::parse("https://localhost:8080").unwrap())
            .with_scope(["read", "write"])
            .with_generated_nonce()
            .url()
            .unwrap();
        let query = url.query().unwrap();
        assert!(query.contains("nonce"));
    }
}