use std::time::Duration;

use async_trait::async_trait;
use tokio::time::sleep;

use crate::{
    debug_log,
    logging::KameleoonLogger,
    network::{net_provider::NetProvider, network_error::NetworkError, request::Request, response::Response},
    warn_log,
};

// MARK: - CoreNetwork

#[async_trait]
pub trait CoreNetworkService: Send + Sync {
    async fn call(
        &self,
        request: Request,
        request_url: &str,
        headers_to_read: &Option<Vec<&'static str>>,
        attempt_count: usize,
        retry_delay: Duration,
    ) -> Result<Response, NetworkError>;
}

// MARK: - CoreNetworkManager

pub struct CoreNetworkServiceImpl<N: NetProvider> {
    pub net_provider: N,
}

#[async_trait]
impl<N: NetProvider> CoreNetworkService for CoreNetworkServiceImpl<N> {
    async fn call(
        &self,
        request: Request,
        request_url: &str,
        headers_to_read: &Option<Vec<&'static str>>,
        attempt_count: usize,
        retry_delay: Duration,
    ) -> Result<Response, NetworkError> {
        let mut request = Some(request);
        for attempt in 1..=attempt_count {
            let is_last_attempt = attempt == attempt_count;
            // not cloning for the last attempt
            let req_to_use = if is_last_attempt { request.take().unwrap() } else { request.as_ref().unwrap().clone() };
            debug_log!(
                "Sending request {:?}, {}/{} attempt, retry delay {:?}",
                req_to_use,
                attempt,
                attempt_count,
                retry_delay
            );
            match self.net_provider.request(req_to_use, headers_to_read).await {
                Ok(response) => {
                    KameleoonLogger::debug_lazy(|| {
                        format!(
                            "Request '{}' succeeded on attempt {}/{}. Body: {}",
                            request_url,
                            attempt,
                            attempt_count,
                            String::from_utf8_lossy(response.body.as_slice())
                        )
                    });
                    return Ok(response);
                }
                Err(err) => {
                    warn_log!(
                        "Request '{}' failed on attempt {}/{}. Error: {:?}",
                        request_url,
                        attempt,
                        attempt_count,
                        err
                    );
                    if is_last_attempt {
                        return Err(err);
                    } else if !retry_delay.is_zero() {
                        sleep(retry_delay).await;
                    }
                }
            }
        }
        unreachable!();
    }
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use reqwest::header::CONTENT_TYPE;
    use rstest::rstest;
    use wiremock::matchers::{method, path};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    use super::*;
    use std::sync::Arc;
    use std::time::{Duration, Instant};

    use crate::network::net_provider::ReqwestNetProvider;
    use crate::network::network_test_utils::MockNetProvider;
    use crate::network::request::HttpMethod;

    const TEST_REQUEST_URL: &str = "https://example.com/test";

    // Helper function to create a sample request
    fn create_sample_request() -> Request {
        Request {
            method: HttpMethod::GET,
            url: TEST_REQUEST_URL.to_string(),
            body: Some(vec![10u8, 10u8, 10u8]),
            headers: vec![("Header", "Value".to_owned())],
            timeout: Some(Duration::from_secs(30)),
            redact_body: false,
        }
    }

    // Helper function to create a sample response
    fn create_sample_response(body: &str) -> Response {
        Response {
            status_code: 200,
            body: body.as_bytes().to_vec(),
            headers: Vec::new(),
        }
    }

    #[tokio::test]
    async fn test_successful_request_first_attempt() {
        // Arrange
        let mock_provider = MockNetProvider::with_responses(vec![Ok(create_sample_response("test response"))]);
        let network_manager = CoreNetworkServiceImpl {
            net_provider: mock_provider.clone(),
        };
        let request = create_sample_request();

        // Act
        let result = network_manager.call(request, TEST_REQUEST_URL, &None, 3, Duration::from_millis(100)).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!(mock_provider.call_count(), 1);
    }

    #[tokio::test]
    async fn test_retry_on_failure_then_success() {
        // Arrange
        let mock_provider = MockNetProvider::with_responses(vec![
            Err(NetworkError::Timeout),
            Err(NetworkError::Connection),
            Ok(create_sample_response("success after retries")),
        ]);
        let network_manager = CoreNetworkServiceImpl {
            net_provider: mock_provider.clone(),
        };
        let request = create_sample_request();

        // Act
        let result = network_manager.call(request, TEST_REQUEST_URL, &None, 3, Duration::from_millis(50)).await;

        // Assert
        assert!(result.is_ok());
        let response = result.unwrap();
        assert_eq!(response.body, b"success after retries");
        assert_eq!(mock_provider.call_count(), 3);
    }

    #[rstest]
    #[case(
        vec![Err(NetworkError::Timeout)],
        1,
        Duration::ZERO,
        NetworkError::Timeout
    )]
    #[case(
        vec![Err(NetworkError::Timeout), Err(NetworkError::Connection)],
        2,
        Duration::from_millis(10),
        NetworkError::Connection
    )]
    #[case(
        vec![
            Err(NetworkError::Timeout), Err(NetworkError::Connection),
            Err(NetworkError::Timeout), Err(NetworkError::Connection)
        ],
        4,
        Duration::from_millis(10),
        NetworkError::Connection
    )]
    #[tokio::test]
    async fn test_attempts_number(
        #[case] responses: Vec<Result<Response, NetworkError>>,
        #[case] max_attempts: usize,
        #[case] retry_delay: Duration,
        #[case] expected_error: NetworkError,
    ) {
        // Arrange
        let mock_provider = MockNetProvider::with_responses(responses);
        let network_manager = CoreNetworkServiceImpl {
            net_provider: mock_provider.clone(),
        };
        let request = create_sample_request();

        // Act
        let result = network_manager.call(request, TEST_REQUEST_URL, &None, max_attempts, retry_delay).await;

        // Assert
        assert!(result.is_err());
        assert_eq!(mock_provider.call_count(), max_attempts);
        assert!(
            matches!(result.unwrap_err(), e if std::mem::discriminant(&e) == std::mem::discriminant(&expected_error))
        );
    }

    #[rstest]
    #[case(Duration::ZERO, Duration::from_millis(1), false)] // zero delay, should be quick
    #[case(Duration::from_millis(5), Duration::from_millis(1), true)] // 5ms delay, should take >= delay
    #[tokio::test]
    async fn test_retry_delay(
        #[case] retry_delay: Duration,
        #[case] max_expected: Duration,
        #[case] expect_delay: bool,
    ) {
        // Arrange
        let mock_provider =
            MockNetProvider::with_responses(vec![Err(NetworkError::Timeout), Ok(create_sample_response("success"))]);
        let network_manager = CoreNetworkServiceImpl {
            net_provider: mock_provider.clone(),
        };
        let request = create_sample_request();

        // Act
        let start = Instant::now();
        let result = network_manager.call(request, TEST_REQUEST_URL, &None, 2, retry_delay).await;
        let elapsed = start.elapsed();

        // Assert
        assert!(result.is_ok());
        assert_eq!(mock_provider.call_count(), 2);

        if expect_delay {
            // Ensure we waited at least the retry delay
            assert!(elapsed >= retry_delay);
        } else {
            // Ensure we completed quickly
            assert!(elapsed < max_expected);
        }
    }

    #[tokio::test]
    async fn test_with_headers_to_read() {
        // Arrange: create a response with headers
        let response = create_sample_response("response with headers");
        let mock_provider = MockNetProvider::with_responses(vec![Ok(response)]);
        let network_manager = CoreNetworkServiceImpl {
            net_provider: mock_provider.clone(),
        };
        let request = create_sample_request();
        let headers_to_read = Some(vec![CONTENT_TYPE.as_str(), "Authorization"]);

        // Act
        _ = network_manager.call(request, TEST_REQUEST_URL, &headers_to_read, 1, Duration::ZERO).await;

        // Assert
        assert_eq!(mock_provider.captured_headers_to_read(), vec![CONTENT_TYPE.as_str(), "Authorization"]);
    }

    #[rstest]
    #[tokio::test]
    async fn test_request_cloning_behavior() {
        // This test verifies that requests are cloned properly for retries
        // and the original is consumed only on the last attempt
        let mock_provider = MockNetProvider::with_responses(vec![
            Err(NetworkError::Timeout),
            Err(NetworkError::Timeout),
            Ok(create_sample_response("success")),
        ]);
        let network_manager = CoreNetworkServiceImpl {
            net_provider: mock_provider.clone(),
        };
        let request = create_sample_request();

        // Act
        let _result = network_manager.call(request.clone(), TEST_REQUEST_URL, &None, 3, Duration::from_millis(1)).await;

        // Assert: all attempts received a proper clone
        let captured = mock_provider.captured_requests();
        assert_eq!(captured.len(), 3, "Expected 3 captured requests");

        // Verify they are independent clones (body, headers, etc.)
        assert!(captured.iter().all(|r| *r == request));
    }

    // MARK: - Integration
    #[tokio::test]
    async fn test_with_real_http_mock_server() {
        // Arrange
        let mock_server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/test"))
            .respond_with(ResponseTemplate::new(200).set_body_string("mock response"))
            .expect(1)
            .mount(&mock_server)
            .await;

        // Construct request to the mock server
        let request = Request {
            method: HttpMethod::GET,
            url: format!("{}/test", &mock_server.uri()), // point to mock server
            body: Some(vec![10u8, 10u8, 10u8]),
            headers: vec![("Authorization", "Bearer authorization".to_owned())],
            timeout: Some(Duration::from_secs(30)),
            redact_body: false,
        };

        let network_manager = CoreNetworkServiceImpl {
            net_provider: ReqwestNetProvider::new(), // or whatever your concrete provider is
        };

        // Act: Perform the HTTP request
        let result = network_manager.call(request, TEST_REQUEST_URL, &None, 1, Duration::ZERO).await;

        // Assert: Successful response
        assert!(result.is_ok(), "Expected successful response");
        let response = result.unwrap();
        assert_eq!(response.status_code, 200);
        assert_eq!(String::from_utf8_lossy(&response.body), "mock response");
    }

    #[tokio::test]
    async fn test_concurrent_requests() {
        // Test multiple concurrent requests to ensure thread safety
        let mock_provider = MockNetProvider::with_responses(vec![
            Ok(create_sample_response("response1")),
            Ok(create_sample_response("response2")),
            Ok(create_sample_response("response3")),
        ]);
        let network_manager = Arc::new(CoreNetworkServiceImpl {
            net_provider: mock_provider.clone(),
        });

        // Act
        let mut handles = Vec::new();
        for i in 0..3 {
            let nm = network_manager.clone();
            let request = create_sample_request();
            let handle = tokio::spawn(async move {
                nm.call(request, &format!("https://example.com/test{}", i), &None, 1, Duration::ZERO).await
            });
            handles.push(handle);
        }

        let results: Vec<_> = futures_util::future::join_all(handles).await;

        // Assert
        for result in results {
            assert!(result.is_ok());
            assert!(result.unwrap().is_ok());
        }
        assert_eq!(mock_provider.call_count(), 3);
    }
}
