use reqwest::Client;
use std::time::Duration;
use kameleoon_trace_logging::trace_method;

use crate::{
    logging::LogLevel,
    network::{network_error::NetworkError, request::Request, response::Response},
};

// MARK: - NetProvider

#[async_trait::async_trait]
pub(crate) trait NetProvider: Send + Sync {
    async fn request(&self, req: Request, headers_to_read: &Option<Vec<&str>>) -> Result<Response, NetworkError>;
}

// MARK: - ReqwestNetProvider

pub(crate) struct ReqwestNetProvider {
    client: Client,
}

impl ReqwestNetProvider {
    #[trace_method(LogLevel::Debug, skip(retval))]
    pub fn new() -> Self {
        // ~[43585] Implement proxy like: let proxy = Proxy::all("http://username:password@127.0.0.1:8080")?;
        Self { client: Client::new() }
    }

    #[trace_method(LogLevel::Debug, skip(retval))]
    pub fn with_timeout(timeout: Duration) -> Self {
        Self {
            client: Client::builder().timeout(timeout).build().unwrap_or_default(),
        }
    }
}

#[async_trait::async_trait]
impl NetProvider for ReqwestNetProvider {
    async fn request(&self, req: Request, headers_to_read: &Option<Vec<&str>>) -> Result<Response, NetworkError> {
        let mut builder = self.client.request(req.method.into(), &req.url);
        for (key, value) in req.headers {
            builder = builder.header(key, value);
        }
        if let Some(body) = req.body {
            builder = builder.body(body);
        }
        if let Some(timeout) = req.timeout {
            builder = builder.timeout(timeout);
        }
        Response::from_reqwest_response(builder.send().await, headers_to_read).await
    }
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use crate::network::request::{HttpMethod, RequestBuilder};

    use super::*;
    use reqwest::header::CONTENT_TYPE;
    use rstest::rstest;
    use serde_json::json;
    use wiremock::{
        Mock, MockServer, ResponseTemplate,
        matchers::{body_json, header, method, path},
    };

    #[tokio::test]
    async fn test_reqwest_net_provider_get() {
        // Start a mock HTTP server
        let server = MockServer::start().await;

        // Create GET mock
        Mock::given(method("GET"))
            .and(path("/hello"))
            .respond_with(ResponseTemplate::new(200).set_body_string("world"))
            .up_to_n_times(1)
            .mount(&server)
            .await;

        let provider = ReqwestNetProvider::new();

        let req =
            RequestBuilder::default().method(HttpMethod::GET).url(format!("{}/hello", &server.uri())).build().unwrap();

        let response = provider.request(req, &None).await.unwrap();

        assert_eq!(200, response.status_code);
        assert_eq!(b"world".to_vec(), response.body);
    }

    #[tokio::test]
    async fn test_reqwest_net_provider_post_with_headers_and_body() {
        let server = MockServer::start().await;

        // Create POST mock with header + JSON body matcher
        Mock::given(method("POST"))
            .and(path("/submit"))
            .and(header(CONTENT_TYPE.as_str(), "application/json"))
            .and(body_json(json!({"name": "Alice"})))
            .respond_with(ResponseTemplate::new(201).set_body_json(json!({"success": true})))
            .mount(&server)
            .await;

        let provider = ReqwestNetProvider::new();

        let req = RequestBuilder::default()
            .method(HttpMethod::POST)
            .url(format!("{}/submit", &server.uri()))
            .header((CONTENT_TYPE.as_str(), "application/json".to_string()))
            .body(Some(r#"{"name":"Alice"}"#.as_bytes().to_vec()))
            .build()
            .unwrap();

        let response = provider.request(req, &None).await.unwrap();

        assert_eq!(201, response.status_code);
        assert!(String::from_utf8(response.body).unwrap().contains("success"));
    }

    #[rstest]
    #[case(200, "/success")]
    #[case(400, "/bad_request")]
    #[case(500, "/server_error")]
    #[tokio::test]
    async fn test_reqwest_net_provider_error_status_code(#[case] status_code: u16, #[case] path_: &str) {
        // Setup mock server
        let server = MockServer::start().await;

        // Mount mock for this status code
        Mock::given(method("GET"))
            .and(path(path_))
            .respond_with(ResponseTemplate::new(status_code).set_body_string(format!("Error {}", status_code)))
            .mount(&server)
            .await;

        let provider = ReqwestNetProvider::new();

        let req = RequestBuilder::default()
            .method(HttpMethod::GET)
            .url(format!("{}{}", &server.uri(), path_))
            .build()
            .unwrap();

        let response = provider.request(req, &None).await.unwrap();

        assert_eq!(status_code, response.status_code);
    }

    #[tokio::test]
    async fn test_reqwest_net_provider_request_timeout() {
        use std::time::Duration;
        use tokio::time::Instant;

        let server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/slow"))
            .respond_with(ResponseTemplate::new(200).set_body_string("ok").set_delay(Duration::from_millis(200)))
            .mount(&server)
            .await;

        let provider = ReqwestNetProvider::with_timeout(Duration::from_millis(1000));

        let req = RequestBuilder::default()
            .method(HttpMethod::GET)
            .url(format!("{}/slow", &server.uri()))
            .timeout(Duration::from_millis(100))
            .build()
            .unwrap();

        let start = Instant::now();
        let result = provider.request(req, &None).await;
        let elapsed = start.elapsed();

        assert!(result.is_err(), "Expected timeout error, got {:?}", result);
        assert!(matches!(result, Err(NetworkError::Timeout)));
        assert!(elapsed < Duration::from_millis(200), "Request did not timeout as expected");
    }

    #[tokio::test]
    async fn test_reqwest_net_provider_timeout() {
        use std::time::Duration;
        use tokio::time::Instant;

        let server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/slow"))
            .respond_with(ResponseTemplate::new(200).set_body_string("ok").set_delay(Duration::from_millis(200)))
            .mount(&server)
            .await;

        let provider = ReqwestNetProvider::with_timeout(Duration::from_millis(100));

        let req =
            RequestBuilder::default().method(HttpMethod::GET).url(format!("{}/slow", &server.uri())).build().unwrap();

        let start = Instant::now();
        let result = provider.request(req, &None).await;
        let elapsed = start.elapsed();

        assert!(result.is_err(), "Expected timeout error, got {:?}", result);
        assert!(matches!(result, Err(NetworkError::Timeout)));
        assert!(elapsed < Duration::from_millis(200), "Request did not timeout as expected");
    }

    #[tokio::test]
    async fn test_reqwest_net_provider_connection_error() {
        let provider = ReqwestNetProvider::new();

        // Use an invalid URL or a port where no server is running
        let req = RequestBuilder::default()
            .method(HttpMethod::GET)
            .url("http://127.0.0.1:9/nonexistent") // port 9 is usually closed
            .build()
            .unwrap();

        let result = provider.request(req, &None).await;

        assert!(matches!(result, Err(NetworkError::Connection)));
    }

    #[rstest]
    #[case(Some(vec![CONTENT_TYPE.as_str()]), vec![(CONTENT_TYPE.as_str(), "application/json")])]
    #[case(Some(vec!["X-Custom-Header"]), vec![("X-Custom-Header", "custom-value")])]
    #[case(Some(vec!["Non-Existing"]), vec![])]
    #[case(None, vec![])] // no filter means no headers collected
    #[tokio::test]
    async fn test_response_headers_filter(
        #[case] headers_to_read: Option<Vec<&str>>,
        #[case] expected_headers: Vec<(&str, &str)>,
    ) {
        let server = MockServer::start().await;

        Mock::given(method("GET"))
            .respond_with(
                ResponseTemplate::new(200)
                    .insert_header(CONTENT_TYPE.as_str(), "application/json")
                    .insert_header("X-Custom-Header", "custom-value"),
            )
            .mount(&server)
            .await;

        let provider = ReqwestNetProvider::new();

        let req =
            RequestBuilder::default().method(HttpMethod::GET).url(format!("{}/test", &server.uri())).build().unwrap();

        let response = provider.request(req, &headers_to_read).await.unwrap();

        // Check headers count matches expected
        assert_eq!(expected_headers.len(), response.headers.len());

        for (key, value) in expected_headers {
            assert_eq!(Some(value), response.get_header_by_key(&key));
        }
    }
}
