pub mod automation_api;
pub mod data_api;
pub mod datafile;

use reqwest::header::AUTHORIZATION;
use std::{sync::Arc, time::Duration};
use kameleoon_trace_logging::trace_method;

use crate::{
    logging::LogLevel,
    network::{
        access_token_source::AccessTokenSource,
        core_network_service::CoreNetworkService,
        network_error::NetworkError,
        network_manager::{
            automation_api::AutomationApiNetworkManager, data_api::DataApiNetworkManager,
            datafile::DataFileNetworkManager,
        },
        request::RequestBuilder,
        response::Response,
        url_provider::UrlProvider,
    },
    warn_log,
};

// MARK: - Constants

const NETWORK_CALL_RETRIES_NUMBER_CRITICAL: usize = 1;
const NETWORK_CALL_ATTEMPTS_NUMBER_CRITICAL: usize = NETWORK_CALL_RETRIES_NUMBER_CRITICAL + 1;
const NETWORK_CALL_ATTEMPTS_NUMBER_UNCRITICAL: usize = 1;
const CODE_UNAUTHORIZED: u16 = 401;

// MARK: - NetworkManager (Trait)

pub trait NetworkManager: DataFileNetworkManager + DataApiNetworkManager + AutomationApiNetworkManager {
    fn url_provider(&self) -> &impl UrlProvider;
}

// MARK: - NetworkManagerImpl
pub struct NetworkManagerImpl<C, U, A>
where
    C: CoreNetworkService + 'static,
    U: UrlProvider + 'static,
    A: AccessTokenSource + 'static,
{
    core_network: Arc<C>,
    url_provider: Arc<U>,
    access_token_source: A,
    timeout: Duration,
}

impl<C: CoreNetworkService, U: UrlProvider, A: AccessTokenSource> NetworkManager for NetworkManagerImpl<C, U, A> {
    fn url_provider(&self) -> &impl UrlProvider {
        &(*self.url_provider)
    }
}

impl<C: CoreNetworkService, U: UrlProvider, A: AccessTokenSource> NetworkManagerImpl<C, U, A> {
    #[trace_method(LogLevel::Debug, skip(retval, core_network, url_provider, access_token_source))]
    pub fn new(core_network: Arc<C>, url_provider: Arc<U>, access_token_source: A, timeout: Duration) -> Self {
        Self {
            core_network,
            url_provider,
            access_token_source,
            timeout,
        }
    }

    async fn call(
        &self,
        mut req_builder: RequestBuilder,
        headers_to_read: Option<Vec<&'static str>>,
        attempt_count: usize,
        retry_delay: Duration,
        (auth_required, retry_without_token): (bool, bool),
    ) -> Result<Response, NetworkError> {
        if !req_builder.is_timeout_set() {
            req_builder = req_builder.timeout(self.timeout);
        }
        // Copy request builder to additional request without AUTHORIZATION header (if conditions are true)
        let original_req_builder = if auth_required && retry_without_token { Some(req_builder.clone()) } else { None };
        // Original request with AUTHORIZATION header (if required)
        let (req_builder, access_token) = self.try_authorize(auth_required, req_builder);
        let request = req_builder.build().map_err(|err| NetworkError::RequestFailed(Box::new(err)))?;
        let request_url = request.url.clone();

        // Original attempt
        let response =
            self.core_network.call(request, &request_url, &headers_to_read, attempt_count, retry_delay).await?;

        // Check response and handle token issues
        let retry_needed = self.handle_response(&request_url, access_token, response.status_code).await;
        // Send request again if it could be sent without access token
        if retry_needed && retry_without_token {
            let retry_request = original_req_builder
                .ok_or_else(|| NetworkError::RequestFailed("A request without a token cannot be built.".into()))?
                .build()
                .map_err(|err| NetworkError::RequestFailed(Box::new(err)))?;

            self.core_network.call(retry_request, &request_url, &headers_to_read, 1, Duration::ZERO).await
        } else {
            Ok(response)
        }
    }

    async fn handle_response(&self, request_url: &str, access_token: Option<String>, status_code: u16) -> bool {
        let mut retry_needed = false;
        if !self.is_expected_status_code(status_code) {
            warn_log!("Request: '{}' received unexpected status code: '{}'", request_url, status_code);
            if self.is_token_rejection_error(status_code) && access_token.is_some() {
                let access_token = access_token.unwrap_or_default();
                warn_log!("Kameleoon API access token: '{}' is rejected", access_token);
                self.access_token_source.discard_token(access_token).await;
                retry_needed = true;
            }
        }
        retry_needed
    }

    #[inline]
    fn try_authorize(&self, auth_required: bool, req_builder: RequestBuilder) -> (RequestBuilder, Option<String>) {
        if auth_required {
            if let Some(token) = self.access_token_source.get_token_now() {
                let req_builder = req_builder.header((AUTHORIZATION.as_str(), token.clone()));
                return (req_builder, Some(token));
            }
        }
        (req_builder, None)
    }

    #[inline]
    fn is_token_rejection_error(&self, status_code: u16) -> bool {
        status_code == CODE_UNAUTHORIZED
    }

    #[inline]
    fn is_expected_status_code(&self, status_code: u16) -> bool {
        matches!(status_code, 200..=299 | 304 | 403)
    }
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use crate::network::network_test_utils::{
        MockAccessTokenSource, MockCoreNetwork, MockNetProvider, create_network_manager_core_network,
        create_network_manager_net_provider, create_simple_builder, create_status_code_response,
        create_success_response, create_success_response_params,
    };
    use crate::network::request::HttpMethod;

    use super::*;
    use rstest::*;
    use std::time::Duration;

    // Helper functions
    #[tokio::test]
    async fn test_successful_request_without_auth() {
        // Arrange
        let net_provider =
            MockNetProvider::with_responses(vec![Ok(create_success_response_params("test response", Vec::new()))]);
        let network_manager = create_network_manager_net_provider(Some(net_provider.clone()), None, None);

        // Act
        let result = network_manager
            .call(create_simple_builder(), None, 1, Duration::ZERO, (false, false)) // No auth required
            .await;

        // Assert
        assert!(result.is_ok());
        let response = result.unwrap();
        assert_eq!(response.status_code, 200);
        assert_eq!(response.body, b"test response");
        assert_eq!(net_provider.call_count(), 1);

        // Verify no authorization header was added
        let captured_requests = net_provider.captured_requests();
        assert_eq!(captured_requests.len(), 1);
        assert!(captured_requests[0].headers.is_empty());
    }

    #[tokio::test]
    async fn test_successful_request_with_auth() {
        // Arrange
        let net_provider = MockNetProvider::with_responses(vec![Ok(create_success_response())]);
        let access_token_source = MockAccessTokenSource::with_tokens(vec!["test_token".to_string()]);
        let network_manager =
            create_network_manager_net_provider(Some(net_provider.clone()), None, Some(access_token_source.clone()));

        // Act
        let result = network_manager
            .call(create_simple_builder(), None, 1, Duration::ZERO, (true, false)) // Auth required, no retry without token
            .await;

        // Assert
        assert!(result.is_ok());
        let response = result.unwrap();
        assert_eq!(response.status_code, 200);
        assert_eq!(net_provider.call_count(), 1);

        // Verify authorization header was added
        let captured_requests = net_provider.captured_requests();
        assert_eq!(captured_requests.len(), 1);
        assert_eq!(captured_requests[0].headers, vec![(AUTHORIZATION.as_str(), "test_token".to_string())]);
    }

    #[tokio::test]
    async fn test_auth_required_but_token_unavailable() {
        // Arrange
        let net_provider = MockNetProvider::with_responses(vec![Ok(create_success_response())]);
        let access_token_source = MockAccessTokenSource::new();
        access_token_source.set_should_fail(true);
        let network_manager =
            create_network_manager_net_provider(Some(net_provider.clone()), None, Some(access_token_source.clone()));

        // Act
        let result = network_manager
            .call(create_simple_builder(), None, 1, Duration::ZERO, (true, false)) // Auth required but token unavailable
            .await;

        // Assert
        assert!(result.is_ok());
        let captured_requests = net_provider.captured_requests();
        assert_eq!(captured_requests.len(), 1);
        // Should proceed without authorization header
        assert!(captured_requests[0].headers.is_empty());
    }

    #[tokio::test]
    async fn test_token_rejection_with_retry() {
        // Arrange
        let net_provider = MockNetProvider::with_responses(vec![
            Ok(create_status_code_response(401)), // Unauthorized - token rejected
            Ok(create_success_response()),
        ]);
        let access_token_source = MockAccessTokenSource::with_tokens(vec!["invalid_token".to_string()]);
        let network_manager =
            create_network_manager_net_provider(Some(net_provider.clone()), None, Some(access_token_source.clone()));

        // Act
        let result = network_manager
            .call(create_simple_builder(), None, 1, Duration::ZERO, (true, true)) // Auth required, retry without token allowed
            .await;

        // Assert
        assert!(result.is_ok());
        let response = result.unwrap();
        assert_eq!(response.status_code, 200);
        assert_eq!(net_provider.call_count(), 2); // Original + retry

        // Verify token was discarded
        let discarded_tokens = access_token_source.discarded_tokens();
        assert_eq!(discarded_tokens, vec!["invalid_token"]);

        // Verify requests
        let captured_requests = net_provider.captured_requests();
        assert_eq!(captured_requests.len(), 2);
        // First request should have auth header
        assert_eq!(captured_requests[0].headers, vec![(AUTHORIZATION.as_str(), "invalid_token".to_string())]);
        // Second request should not have auth header
        assert!(captured_requests[1].headers.is_empty());
    }

    #[tokio::test]
    async fn test_token_rejection_without_retry() {
        // Arrange
        let net_provider = MockNetProvider::with_responses(vec![
            Ok(create_status_code_response(401)), // Unauthorized - token rejected
        ]);
        let access_token_source = MockAccessTokenSource::with_tokens(vec!["invalid_token".to_string()]);
        let network_manager =
            create_network_manager_net_provider(Some(net_provider.clone()), None, Some(access_token_source.clone()));

        // Act
        let result = network_manager
            .call(create_simple_builder(), None, 1, Duration::ZERO, (true, false)) // Auth required, no retry without token
            .await;

        // Assert
        assert!(result.is_ok());
        let response = result.unwrap();
        assert_eq!(response.status_code, 401);
        assert_eq!(net_provider.call_count(), 1); // No retry

        // Verify token was still discarded
        let discarded_tokens = access_token_source.discarded_tokens();
        assert_eq!(discarded_tokens, vec!["invalid_token"]);
    }

    #[rstest]
    #[case(200, true)] // Success (expected)
    #[case(201, true)] // Created (expected)
    #[case(299, true)] // Last 2xx (expected)
    #[case(300, false)] // Redirection (unexpected)
    #[case(304, true)] // Not Modified (expected)
    #[case(403, true)] // Forbidden (expected)
    #[case(404, false)] // Not Found (unexpected)
    #[case(500, false)] // Server Error (unexpected)
    #[tokio::test]
    async fn test_expected_status_codes(#[case] status_code: u16, #[case] expected: bool) {
        // Arrange
        let network_manager = create_network_manager_net_provider(None, None, None);

        // Act
        let expected_status_code = network_manager.is_expected_status_code(status_code);

        // Assert
        assert_eq!(expected, expected_status_code);
    }

    #[tokio::test]
    async fn test_request_builder_failure() {
        // Arrangef
        let network_manager = create_network_manager_net_provider(None, None, None);

        // Act
        let result = network_manager.call(RequestBuilder::default(), None, 1, Duration::ZERO, (false, false)).await;

        // Assert
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), NetworkError::RequestFailed(_)));
    }

    #[tokio::test]
    async fn test_parameters_passed_to_core() {
        // Arrange & Assert
        let mut mock_core = MockCoreNetwork::new();
        mock_core
            .expect_call()
            .withf(|req, url, headers_to_read, attempt_count, retry_delay| {
                url == "https://api.example.com/test"
                    && *attempt_count == 3
                    && retry_delay.as_millis() == 500
                    && headers_to_read.as_ref().map_or(false, |h| h.contains(&"Authorization"))
                    && req.method == HttpMethod::POST
            })
            .returning(|_, _, _, _, _| Ok(Response::new(200)));
        let network_manager = create_network_manager_core_network(mock_core, None, None);

        // Act
        let result = network_manager
            .call(
                RequestBuilder::default().method(HttpMethod::POST).url("https://api.example.com/test"),
                Some(vec!["Authorization"]),
                3,
                Duration::from_millis(500),
                (false, false),
            )
            .await;

        assert!(result.is_ok())
    }

    #[tokio::test]
    async fn test_core_network_manager_failure_propagation() {
        // Arrange
        let net_provider = MockNetProvider::with_responses(vec![Err(NetworkError::Timeout)]);
        let network_manager = create_network_manager_net_provider(Some(net_provider.clone()), None, None);

        // Act
        let result = network_manager.call(create_simple_builder(), None, 1, Duration::ZERO, (false, false)).await;

        // Assert
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), NetworkError::Timeout));
    }
}
