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_CRITICAL, NetworkManagerImpl},
        request::{HttpMethod, RequestBuilder},
        response::Response,
        url_provider::UrlProvider,
    },
};

// MARK: - Constant

const HEADER_SDK_TYPE: &str = "X-Kameleoon-SDK-Type";
const HEADER_SDK_VERSION: &str = "X-Kameleoon-SDK-Version";
const HEADER_IF_MODIFIED_SINCE: &str = "If-Modified-Since";
pub const HEADER_LAST_MODIFIED: &str = "Last-Modified";

// MARK: - DataFileNetworkManager (Trait)

#[async_trait]
pub(crate) trait DataFileNetworkManager: Send + Sync {
    async fn fetch_datafile(
        &self,
        environment: Option<&str>,
        ts: Option<u64>,
        if_modified_since: Option<&str>,
    ) -> Result<Response, NetworkError>;
}

// MARK: - NetworkManagerImpl

#[async_trait]
impl<C, U, A> DataFileNetworkManager for NetworkManagerImpl<C, U, A>
where
    C: CoreNetworkService,
    U: UrlProvider,
    A: AccessTokenSource,
{
    async fn fetch_datafile(
        &self,
        environment: Option<&str>,
        ts: Option<u64>,
        if_modified_since: Option<&str>,
    ) -> Result<Response, NetworkError> {
        debug_log!(
            "CALL NetworkManagerImpl::fetch_datafile(environment: {:?}, ts: {:?}, if_modified_since: {:?})",
            environment,
            ts,
            if_modified_since
        );
        let mut headers = vec![(HEADER_SDK_TYPE, "SDK_NAME".to_owned()), (HEADER_SDK_VERSION, "10.0.0".to_owned())];
        if let Some(if_modified_since) = if_modified_since {
            headers.push((HEADER_IF_MODIFIED_SINCE, if_modified_since.to_string()));
        }
        let req_builder = RequestBuilder::default()
            .method(HttpMethod::GET)
            .url(self.url_provider.make_datafile_url(environment.as_deref(), ts))
            .headers(headers);
        let result = self
            .call(
                req_builder,
                Some(vec![HEADER_LAST_MODIFIED]),      // read Last-Modified header
                NETWORK_CALL_ATTEMPTS_NUMBER_CRITICAL, // attempt_count
                Duration::ZERO,                        // retry_delay
                (false, false),                        // (auth_required, retry_without_token)
            )
            .await;
        debug_log!(
            "RET NetworkManagerImpl::fetch_datafile(environment: {:?}, ts: {:?}, if_modified_since: {:?}) -> {:?}",
            environment,
            ts,
            if_modified_since,
            result
        );
        result
    }
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use crate::network::{
        network_test_utils::{MockNetProvider, MockUrlProvider, create_network_manager_net_provider},
        request::Request,
    };

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

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

    // Helper constants
    const EXPECTED_SDK_TYPE: &str = "SDK_NAME";
    const EXPECTED_SDK_VERSION: &str = "10.0.0";

    // Helper function to validate request headers
    fn has_required_headers(req: &Request) -> bool {
        let headers = &req.headers;
        headers.iter().any(|(key, value)| *key == HEADER_SDK_TYPE && value == &EXPECTED_SDK_TYPE)
            && headers.iter().any(|(key, value)| *key == HEADER_SDK_VERSION && value == &EXPECTED_SDK_VERSION)
    }

    fn has_if_modified_since_header(req: &Request, expected_value: &str) -> bool {
        req.headers.iter().any(|(key, value)| *key == HEADER_IF_MODIFIED_SINCE && value == &expected_value)
    }

    #[rstest]
    #[case(None, None, None, "https://api.example.com/data", 2)] // Basic case
    #[case(Some("production"), None, None, "https://api.example.com/data/production", 2)] // With environment
    #[case(None, Some(1234567890u64), None, "https://api.example.com/data?ts=1234567890", 2)] // With timestamp
    #[case(None, None, Some("Wed, 21 Oct 2015 07:28:00 GMT"), "https://api.example.com/data", 3)] // With if-modified-since
    #[case(
        Some("staging"),
        Some(9876543210u64),
        Some("Thu, 22 Oct 2015 08:30:00 GMT"),
        "https://api.example.com/data/staging?ts=9876543210",
        3
    )] // All parameters
    #[tokio::test]
    async fn test_fetch_datafile_parameters(
        #[case] environment: Option<&str>,
        #[case] timestamp: Option<u64>,
        #[case] if_modified_since: Option<&str>,
        #[case] expected_url: &str,
        #[case] expected_header_count: usize,
    ) {
        // Arrange

        use crate::network::network_test_utils::create_network_manager_core_network;
        let mut mock_core = MockCoreNetwork::new();
        let mut mock_url_provider = MockUrlProvider::new();

        // Convert to owned strings to avoid lifetime issues
        let environment_owned = environment.map(|s| s.to_owned());
        let if_modified_since_owned = if_modified_since.map(|s| s.to_owned());
        let expected_url_owned = expected_url.to_owned();

        // Setup URL provider mock based on parameters
        mock_url_provider
            .expect_make_datafile_url()
            .withf(move |env, ts| {
                let env_matches = match (&environment_owned, env) {
                    (None, None) => true,
                    (Some(expected), Some(actual)) => expected == actual,
                    _ => false,
                };
                let ts_matches = ts == &timestamp;
                env_matches && ts_matches
            })
            .returning(move |_, _| expected_url_owned.clone())
            .times(1);

        // Setup core network mock with parameter validation
        let if_modified_clone = if_modified_since_owned.clone();
        let expected_url_clone = expected_url.to_string();
        mock_core
            .expect_call()
            .withf(move |req, url, headers_to_read, attempt_count, retry_delay| {
                let url_matches = url == expected_url_clone;
                let method_matches = req.method == HttpMethod::GET;
                let has_basic_headers = has_required_headers(req);
                let header_count_matches = req.headers.len() == expected_header_count;
                let has_header = req.headers.iter().any(|(key, _)| *key == HEADER_IF_MODIFIED_SINCE);
                let if_modified_matches = if_modified_clone
                    .as_ref()
                    .map_or(!has_header, |expected| has_if_modified_since_header(req, expected));
                let attempt_count_matches = *attempt_count == NETWORK_CALL_ATTEMPTS_NUMBER_CRITICAL;
                let retry_delay_matches = *retry_delay == Duration::ZERO;
                let headers_to_read_matches =
                    headers_to_read.as_ref().is_some_and(|h| h.contains(&HEADER_LAST_MODIFIED));

                url_matches
                    && method_matches
                    && has_basic_headers
                    && header_count_matches
                    && if_modified_matches
                    && attempt_count_matches
                    && retry_delay_matches
                    && headers_to_read_matches
            })
            .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.fetch_datafile(environment, timestamp, if_modified_since).await;

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

    #[tokio::test]
    async fn test_fetch_datafile_failure_propagation() {
        // Arrange
        let net_provider =
            MockNetProvider::with_responses(vec![Err(NetworkError::Timeout), Err(NetworkError::Connection)]);
        let mut mock_url_provider = MockUrlProvider::new();
        mock_url_provider
            .expect_make_datafile_url()
            .returning(move |_, _| "https://api.example.com/".to_string())
            .times(1);
        let network_manager = create_network_manager_net_provider(Some(net_provider), Some(mock_url_provider), None);

        // Act
        let result = network_manager.fetch_datafile(None, None, None).await;

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