use std::time::Duration;

use async_trait::async_trait;

use crate::{
    debug_log,
    network::{
        access_token_source::AccessTokenSource,
        core_network_service::CoreNetworkService,
        network_error::NetworkError,
        network_manager::{NETWORK_CALL_ATTEMPTS_NUMBER_UNCRITICAL, NetworkManagerImpl},
        request::{HttpMethod, RequestBuilder},
        response::Response,
        url_provider::UrlProvider,
    },
    types::RemoteVisitorDataFilter,
};

// MARK: - DataFileNetworkManager (Trait)

#[async_trait]
#[cfg_attr(test, mockall::automock)]
pub(crate) trait DataApiNetworkManager: Send + Sync {
    async fn get_remote_data(&self, key: &str) -> Result<Response, NetworkError>;

    async fn get_remote_visitor_data(
        &self,
        visitor_code: &str,
        filter: &RemoteVisitorDataFilter,
        is_unique_identifier: bool,
    ) -> Result<Response, NetworkError>;

    async fn track_data(&self, tracking_data: Vec<u8>) -> Result<Response, NetworkError>;
}

// MARK: - NetworkManagerImpl

#[async_trait]
impl<C, U, A> DataApiNetworkManager for NetworkManagerImpl<C, U, A>
where
    C: CoreNetworkService,
    U: UrlProvider,
    A: AccessTokenSource,
{
    async fn get_remote_data(&self, key: &str) -> Result<Response, NetworkError> {
        debug_log!("RET NetworkManagerImpl::get_remote_data(key: {key})");
        let req_builder =
            RequestBuilder::default().method(HttpMethod::GET).url(self.url_provider.make_remote_data_url(key));
        let result = self
            .call(
                req_builder,
                None,                                    // headers_to_read - no reading of response headers needed
                NETWORK_CALL_ATTEMPTS_NUMBER_UNCRITICAL, // attempt_count
                Duration::ZERO,                          // retry_delay
                (true, true),                            // (auth_required, retry_without_token)
            )
            .await;
        debug_log!("RET NetworkManagerImpl::get_remote_data(key: {key}) → {:?}", result);
        result
    }

    async fn get_remote_visitor_data(
        &self,
        visitor_code: &str,
        filter: &RemoteVisitorDataFilter,
        is_unique_identifier: bool,
    ) -> Result<Response, NetworkError> {
        debug_log!(
            "CALL NetworkManagerImpl::get_remote_visitor_data(visitor_code: {visitor_code}, \
            filter: {:?}, is_unique_identifier: {is_unique_identifier})",
            filter
        );
        let req_builder = RequestBuilder::default()
            .method(HttpMethod::GET)
            .url(self.url_provider.make_visitor_data_url(visitor_code, &filter, is_unique_identifier));
        let result = self
            .call(
                req_builder,
                None,                                    // no reading of response headers required
                NETWORK_CALL_ATTEMPTS_NUMBER_UNCRITICAL, // attempt_count
                Duration::ZERO,                          // retry_delay
                (true, true),                            // (auth_required, retry_without_token)
            )
            .await;
        debug_log!(
            "RET NetworkManagerImpl::get_remote_visitor_data(visitor_code: {visitor_code}, \
            is_unique_identifier: {is_unique_identifier}) → {:?}",
            result
        );
        result
    }

    async fn track_data(&self, tracking_data: Vec<u8>) -> Result<Response, NetworkError> {
        debug_log!("CALL NetworkManagerImpl::track_data()");
        let req_builder = RequestBuilder::default()
            .method(HttpMethod::POST)
            .url(self.url_provider.make_tracking_url())
            .body(tracking_data);
        let result = self
            .call(
                req_builder,
                None,                                    // no reading of response headers required
                NETWORK_CALL_ATTEMPTS_NUMBER_UNCRITICAL, // attempt_count
                Duration::from_millis(5000),             // retry_delay
                (true, false),                           // (auth_required, retry_without_token)
            )
            .await;
        debug_log!("RET NetworkManagerImpl::track_data() → {:?}", result);
        result
    }
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use crate::{
        network::network_test_utils::{
            MockNetProvider, MockUrlProvider, create_network_manager_core_network, create_network_manager_net_provider,
        },
        types::remote_visitor_data_filter::RemoteVisitorDataFilterBuilder,
    };

    use super::*;
    use crate::network::network_test_utils::MockCoreNetwork;

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

    // Helper function to validate common network call parameters for different method types
    fn validate_get_method_params(
        headers_to_read: &Option<Vec<&str>>,
        attempt_count: &usize,
        retry_delay: &Duration,
    ) -> bool {
        headers_to_read.is_none()
            && *attempt_count == NETWORK_CALL_ATTEMPTS_NUMBER_UNCRITICAL
            && *retry_delay == Duration::ZERO
    }

    #[tokio::test]
    async fn test_get_remote_data_parameters() {
        // Arrange
        let mut mock_core = MockCoreNetwork::new();
        let mut mock_url_provider = MockUrlProvider::new();

        let key = "user_123";
        let key_owned = key.to_string();
        let expected_url = format!("https://api.example.com/remote/{}", key);
        let expected_url_clone = expected_url.clone();

        mock_url_provider
            .expect_make_remote_data_url()
            .withf(move |k| k == key_owned)
            .returning(move |_| expected_url.clone())
            .times(1);

        mock_core
            .expect_call()
            .withf(move |req, url, headers_to_read, attempt_count, retry_delay| {
                url == expected_url_clone
                    && req.method == HttpMethod::GET
                    && req.body.is_none()
                    && validate_get_method_params(headers_to_read, attempt_count, retry_delay)
            })
            .returning(|_, _, _, _, _| Ok(Response::new(200)))
            .times(1);

        let network_manager = create_network_manager_core_network(mock_core, Some(mock_url_provider), None);

        // Act
        let result = network_manager.get_remote_data(key).await;

        // Assert
        assert!(result.is_ok());
        assert_eq!(result.unwrap().status_code, 200);
    }

    #[rstest]
    #[case("visitor_001", RemoteVisitorDataFilter::default(), false)]
    #[case("visitor_002", RemoteVisitorDataFilterBuilder::default().custom_data(true).build().unwrap(), false)]
    #[case("unique_visitor", RemoteVisitorDataFilterBuilder::default().current_visit(true).build().unwrap(), true)]
    #[tokio::test]
    async fn test_get_remote_visitor_data_parameters(
        #[case] visitor_code: &str,
        #[case] filter: RemoteVisitorDataFilter,
        #[case] is_unique_identifier: bool,
    ) {
        // Arrange
        let mut mock_core = MockCoreNetwork::new();
        let mut mock_url_provider = MockUrlProvider::new();

        let visitor_code_owned = visitor_code.to_string();
        let expected_url = format!(
            "https://api.example.com/visitor/{}/data?filter={:?}&unique={}",
            visitor_code, filter, is_unique_identifier
        );
        let expected_url_clone = expected_url.clone();
        let filter_clone = filter.clone();

        mock_url_provider
            .expect_make_visitor_data_url()
            .withf(move |vc, f, unique| vc == visitor_code_owned && f == &filter && unique == &is_unique_identifier)
            .returning(move |_, _, _| expected_url.clone())
            .times(1);

        mock_core
            .expect_call()
            .withf(move |req, url, headers_to_read, attempt_count, retry_delay| {
                url == expected_url_clone
                    && req.method == HttpMethod::GET
                    && req.body.is_none()
                    && validate_get_method_params(headers_to_read, attempt_count, retry_delay)
            })
            .returning(|_, _, _, _, _| Ok(Response::new(200)))
            .times(1);

        let network_manager = create_network_manager_core_network(mock_core, Some(mock_url_provider), None);

        // Act
        let result = network_manager.get_remote_visitor_data(visitor_code, &filter_clone, is_unique_identifier).await;

        // Assert
        assert!(result.is_ok());
        assert_eq!(result.unwrap().status_code, 200);
    }

    #[rstest]
    #[case(vec![1, 2, 3, 4, 5])]
    #[case(vec![0x48, 0x65, 0x6C, 0x6C, 0x6F])] // "Hello" in bytes
    #[case(vec![])] // Empty data
    #[case(vec![255; 1000])] // Large data
    #[tokio::test]
    async fn test_track_data_parameters(#[case] tracking_data: Vec<u8>) {
        // Arrange
        let mut mock_core = MockCoreNetwork::new();
        let mut mock_url_provider = MockUrlProvider::new();

        let expected_url = "https://api.example.com/track".to_string();
        let expected_url_clone = expected_url.clone();
        let tracking_data_clone = tracking_data.clone();

        mock_url_provider.expect_make_tracking_url().returning(move || expected_url.clone()).times(1);

        mock_core
            .expect_call()
            .withf(move |req, url, headers_to_read, attempt_count, retry_delay| {
                url == expected_url_clone
                    && req.method == HttpMethod::POST
                    && req.body.as_ref() == Some(&tracking_data_clone)
                    && headers_to_read.is_none()
                    && *attempt_count == NETWORK_CALL_ATTEMPTS_NUMBER_UNCRITICAL
                    && *retry_delay == Duration::from_millis(5000)
            })
            .returning(|_, _, _, _, _| Ok(Response::new(200)))
            .times(1);

        let network_manager = create_network_manager_core_network(mock_core, Some(mock_url_provider), None);

        // Act
        let result = network_manager.track_data(tracking_data).await;

        // Assert
        assert!(result.is_ok());
        assert_eq!(result.unwrap().status_code, 200);
    }

    #[rstest]
    #[case(200, 1)] // Success, single call
    #[case(401, 2)] // Unauthorized, retried once -> 2 calls
    #[tokio::test]
    async fn test_get_remote_data_auth(#[case] status_code: u16, #[case] expected_calls: usize) {
        let mut mock_core = MockCoreNetwork::new();
        let mut mock_url_provider = MockUrlProvider::new();

        mock_url_provider
            .expect_make_remote_data_url()
            .returning(|_| "https://api.example.com/remote/test".to_string())
            .times(1);

        mock_core.expect_call().returning(move |_, _, _, _, _| Ok(Response::new(status_code))).times(expected_calls);

        let network_manager = create_network_manager_core_network(mock_core, Some(mock_url_provider), None);

        let result = network_manager.get_remote_data("test").await;
        assert!(result.is_ok());
    }

    #[rstest]
    #[case(200, 1)] // Success, single call
    #[case(401, 2)] // Unauthorized, retried once -> 2 calls
    #[tokio::test]
    async fn test_get_remote_visitor_data_auth(#[case] status_code: u16, #[case] expected_calls: usize) {
        let mut mock_core = MockCoreNetwork::new();
        let mut mock_url_provider = MockUrlProvider::new();

        mock_url_provider
            .expect_make_visitor_data_url()
            .returning(|_, _, _| "https://api.example.com/visitor/test".to_string())
            .times(1);

        mock_core.expect_call().returning(move |_, _, _, _, _| Ok(Response::new(status_code))).times(expected_calls);

        let network_manager = create_network_manager_core_network(mock_core, Some(mock_url_provider), None);

        let result = network_manager.get_remote_visitor_data("test", &RemoteVisitorDataFilter::default(), false).await;
        assert!(result.is_ok());
    }

    #[rstest]
    #[case(200, 1)] // Success, single call
    #[case(401, 1)] // Unauthorized, single call
    #[tokio::test]
    async fn test_track_data_auth(#[case] status_code: u16, #[case] expected_calls: usize) {
        let mut mock_core = MockCoreNetwork::new();
        let mut mock_url_provider = MockUrlProvider::new();

        mock_url_provider.expect_make_tracking_url().returning(|| "https://api.example.com/track".to_string()).times(1);

        mock_core.expect_call().returning(move |_, _, _, _, _| Ok(Response::new(status_code))).times(expected_calls);

        let network_manager = create_network_manager_core_network(mock_core, Some(mock_url_provider), None);

        let result = network_manager.track_data(vec![1, 2, 3]).await;

        assert!(result.is_ok());
    }

    #[rstest]
    #[case("get_remote_data")]
    #[case("get_remote_visitor_data")]
    #[case("track_data")]
    #[tokio::test]
    async fn test_network_error_propagation(#[case] method_name: &str) {
        // Arrange
        let net_provider = MockNetProvider::with_responses(vec![Err(NetworkError::Timeout)]);
        let mut mock_url_provider = MockUrlProvider::new();

        // Setup URL provider for all methods
        mock_url_provider
            .expect_make_remote_data_url()
            .returning(|_| "https://api.example.com/remote/test".to_string())
            .times(..);
        mock_url_provider
            .expect_make_visitor_data_url()
            .returning(|_, _, _| "https://api.example.com/visitor/test".to_string())
            .times(..);
        mock_url_provider
            .expect_make_tracking_url()
            .returning(|| "https://api.example.com/track".to_string())
            .times(..);

        let network_manager = create_network_manager_net_provider(Some(net_provider), Some(mock_url_provider), None);

        // Act & Assert
        let result = match method_name {
            "get_remote_data" => network_manager.get_remote_data("test").await,
            "get_remote_visitor_data" => {
                network_manager.get_remote_visitor_data("test", &RemoteVisitorDataFilter::default(), false).await
            }
            "track_data" => network_manager.track_data(vec![1, 2, 3]).await,
            _ => panic!("Unknown method name"),
        };

        assert!(result.is_err());
        assert!(matches!(result, Err(NetworkError::Timeout)));
    }
}
