use std::{any::Any, time::Duration};

use async_trait::async_trait;
use kameleoon_trace_logging::trace_method;
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};

use crate::{
    logging::LogLevel,
    network::{
        access_token_source::AccessTokenSource,
        core_network_service::CoreNetworkService,
        network_error::NetworkError,
        network_manager::{NetworkManagerImpl, NETWORK_CALL_ATTEMPTS_NUMBER_UNCRITICAL},
        query_builder::{QueryBuilder, QueryParam},
        request::{HttpMethod, RequestBuilder},
        response::Response,
        url_provider::UrlProvider,
    },
};

#[async_trait]
#[cfg_attr(test, mockall::automock)]
pub(crate) trait AutomationApiNetworkManager: Send + Sync + Any {
    async fn fetch_access_jwt(&self, basic_authentication_token: &str) -> Result<Response, NetworkError>;
}

const GRANT_TYPE: &str = "client_credentials";
const FORM_CONTENT_TYPE: &str = "application/x-www-form-urlencoded";

#[async_trait]
impl<C, U, A> AutomationApiNetworkManager for NetworkManagerImpl<C, U, A>
where
    C: CoreNetworkService,
    U: UrlProvider,
    A: AccessTokenSource,
{
    #[trace_method(LogLevel::Debug, skip(self, basic_authentication_token))]
    async fn fetch_access_jwt(&self, basic_authentication_token: &str) -> Result<Response, NetworkError> {
        let body = QueryBuilder::with_params(vec![(QueryParam::GrantType, GRANT_TYPE.into())]).build();
        let req_builder = RequestBuilder::default()
            .method(HttpMethod::POST)
            .url(self.url_provider.make_access_token_url())
            .header((CONTENT_TYPE.as_str(), FORM_CONTENT_TYPE.to_owned()))
            .header((AUTHORIZATION.as_str(), format!("Basic {basic_authentication_token}")))
            .body(body.into_bytes());
        let result =
            self.call(req_builder, None, NETWORK_CALL_ATTEMPTS_NUMBER_UNCRITICAL, Duration::ZERO, (false, false)).await;
        result
    }
}

#[cfg(test)]
mod tests {
    use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};

    use super::*;
    use crate::network::{
        network_test_utils::{create_network_manager_net_provider, MockNetProvider, MockUrlProvider},
        request::HttpMethod,
        response::Response,
    };

    const ACCESS_TOKEN_URL: &str =
        "https://api.kameleoon.com/oauth/token?sdkName=rust-sdk&sdkVersion=1.2.3&siteCode=site";
    const BASIC_AUTHENTICATION_TOKEN: &str = "Y2lkOmNz";

    #[tokio::test]
    async fn test_fetch_access_jwt_request() {
        let net_provider = MockNetProvider::with_responses(vec![Ok(Response::new(200))]);
        let mut url_provider = MockUrlProvider::new();
        url_provider.expect_make_access_token_url().once().return_const(ACCESS_TOKEN_URL.to_owned());
        let network_manager = create_network_manager_net_provider(Some(net_provider.clone()), Some(url_provider), None);

        let result = network_manager.fetch_access_jwt(BASIC_AUTHENTICATION_TOKEN).await;

        assert!(result.is_ok());
        assert_eq!(1, net_provider.call_count());
        let captured_requests = net_provider.captured_requests();
        assert_eq!(1, captured_requests.len());
        let request = &captured_requests[0];
        assert_eq!(HttpMethod::POST, request.method);
        assert_eq!(ACCESS_TOKEN_URL, request.url);
        assert_eq!(
            vec![
                (CONTENT_TYPE.as_str(), FORM_CONTENT_TYPE.to_owned()),
                (AUTHORIZATION.as_str(), format!("Basic {BASIC_AUTHENTICATION_TOKEN}")),
            ],
            request.headers
        );
        assert_eq!(Some(b"grant_type=client_credentials".to_vec()), request.body);
        assert!(!request.redact_body);
    }
}
