use crate::{network::network_error::NetworkError, utils::string_error::StringError};

#[cfg_attr(test, derive(Clone))]
pub struct Response {
    pub status_code: u16,
    pub body: Vec<u8>,
    pub headers: Vec<(String, String)>,
}

impl Response {
    pub async fn from_reqwest_response(
        result: Result<reqwest::Response, reqwest::Error>,
        headers_to_read: &Option<Vec<&str>>,
    ) -> Result<Response, NetworkError> {
        match result {
            Err(err) => {
                return Err(if err.is_timeout() {
                    NetworkError::Timeout
                } else if err.is_connect() {
                    NetworkError::Connection
                } else {
                    NetworkError::RequestFailed(Box::new(err))
                });
            }
            Ok(response) => {
                let status_code = response.status().as_u16();
                let req_headers = response.headers();

                let headers: Vec<(String, String)> = headers_to_read
                    .as_deref()
                    .unwrap_or_default()
                    .iter()
                    .filter_map(|&header_name| {
                        req_headers
                            .get(header_name)
                            .map(|value| (header_name.to_string(), value.to_str().unwrap_or("").to_string()))
                    })
                    .collect();

                let body = match response.bytes().await {
                    Ok(bytes) => bytes.to_vec(),
                    Err(err) => {
                        return Err(NetworkError::RequestFailed(Box::new(err)));
                    }
                };

                Ok(Response {
                    status_code,
                    body,
                    headers,
                })
            }
        }
    }

    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, NetworkError> {
        serde_json::from_slice(&self.body).map_err(|err| {
            NetworkError::RequestFailed(Box::new(StringError(format!("JSON Parse error: {}", err.to_string()))))
        })
    }

    pub fn get_header_by_key(&self, key: &str) -> Option<&str> {
        self.headers.iter().find(|(k, _)| *k == key).map(|(_, v)| v.as_str())
    }
}

impl std::fmt::Debug for Response {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", String::from_utf8_lossy(&self.body))
    }
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use super::*;
    use reqwest::header::CONTENT_TYPE;
    use rstest::rstest;
    use serde::Deserialize;
    use std::time::Duration;
    use wiremock::{Mock, MockServer, ResponseTemplate, matchers::method};

    impl Response {
        pub fn new(status_code: u16) -> Self {
            Self {
                status_code,
                body: vec![],
                headers: Vec::new(),
            }
        }
    }

    #[rstest]
    #[case(NetworkError::Timeout, "http://10.255.255.1:81")] // unreachable IP -> connection timeout
    #[case(NetworkError::Connection, "http://127.0.0.1:9")] // port 9 usually closed -> connection refused
    #[case(NetworkError::RequestFailed(Box::new(std::fmt::Error)), "http://[::1")]
    #[tokio::test]
    async fn test_from_reqwest_response_errors(#[case] expected_error: NetworkError, #[case] url: &str) {
        // Very short timeout to force fast failure

        let client = reqwest::Client::builder().timeout(Duration::from_millis(10)).build().unwrap();

        let result = client.get(url).send().await;

        let converted = Response::from_reqwest_response(result, &None).await;

        match (&converted, &expected_error) {
            (Err(NetworkError::Timeout), NetworkError::Timeout) => {}
            (Err(NetworkError::Connection), NetworkError::Connection) => {}
            (Err(NetworkError::RequestFailed(_)), NetworkError::RequestFailed(_)) => {}
            other => panic!("Unexpected result: {:?}", other),
        }
    }

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

        // Use reqwest::Response from a local server
        // Since constructing reqwest::Response directly is hard, we test with a real request
        let url = format!("{}/test", &_server.uri());

        // Wiremock response
        Mock::given(method("GET"))
            .respond_with(
                ResponseTemplate::new(200)
                    .insert_header(CONTENT_TYPE.as_str(), "application/json")
                    .insert_header("Authorization", "Bearer token")
                    .set_body_raw(r#"{"ok":true}"#, "application/json"),
            )
            .mount(&_server)
            .await;

        let client = reqwest::Client::new();
        let resp = client.get(&url).send().await.unwrap();

        let response = Response::from_reqwest_response(Ok(resp), &Some(vec![CONTENT_TYPE.as_str()])).await.unwrap();

        assert_eq!(200, response.status_code);
        assert_eq!(br#"{"ok":true}"#.to_vec(), response.body);

        assert_eq!(1, response.headers.len());
        assert_eq!(Some("application/json"), response.get_header_by_key(CONTENT_TYPE.as_str()));
    }

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

        Mock::given(method("GET"))
            .respond_with(
                ResponseTemplate::new(201).insert_header("X-Test", "123").set_body_raw("Created", "text/plain"),
            )
            .mount(&_server)
            .await;

        let client = reqwest::Client::new();
        let resp = client.get(format!("{}/test2", &_server.uri())).send().await.unwrap();

        let response = Response::from_reqwest_response(Ok(resp), &None).await.unwrap();

        assert_eq!(201, response.status_code);
        assert_eq!(b"Created".to_vec(), response.body);
        assert!(response.headers.is_empty(), "No headers expected without filter");
    }

    #[derive(Debug, Deserialize, PartialEq)]
    struct User {
        id: u32,
        name: String,
    }

    #[rstest]
    #[case(br#"{"id":1,"name":"Alice"}"#, Some(User { id: 1, name: "Alice".into() }), None)]
    #[case(br#"{invalid json}"#, None, Some("JSON Parse error"))]
    fn test_response_json(
        #[case] body: &[u8],
        #[case] expected_user: Option<User>,
        #[case] expected_error_contains: Option<&str>,
    ) {
        let resp = Response {
            status_code: 200,
            body: body.to_vec(),
            headers: Vec::new(),
        };

        let result = resp.json::<User>();

        match (expected_user, expected_error_contains) {
            (Some(expected), None) => {
                // Success case
                assert_eq!(expected, result.unwrap());
            }
            (None, Some(err_contains)) => {
                // Error case
                let err = result.unwrap_err();
                match err {
                    NetworkError::RequestFailed(msg) => {
                        assert!(
                            msg.to_string().contains(err_contains),
                            "Expected error to contain '{}', got '{}'",
                            err_contains,
                            msg
                        );
                    }
                    other => panic!("Expected RequestFailed, got {:?}", other),
                }
            }
            _ => panic!("Invalid test case configuration"),
        }
    }

    #[rstest]
    #[case(
        vec![
            (CONTENT_TYPE.to_string(), "application/json".to_string()),
            ("X-Test".to_string(), "value".to_string())
        ],
        CONTENT_TYPE.as_str(),
        Some("application/json")
    )]
    #[case(
        vec![
            (CONTENT_TYPE.to_string(), "application/json".to_string()),
            ("X-Test".to_string(), "value".to_string())
        ],
        "X-Test",
        Some("value")
    )]
    #[case(
        vec![
            (CONTENT_TYPE.to_string(), "application/json".to_string())
        ],
        "Authorization",
        None
    )]
    #[case(vec![], CONTENT_TYPE.as_str(), None)]
    #[case(
        vec![
            (CONTENT_TYPE.to_string(), "application/json".to_string())
        ],
        "Content-type", // test case-sensitivity
        None
    )]
    fn test_get_header_by_key(
        #[case] headers: Vec<(String, String)>,
        #[case] key: &str,
        #[case] expected: Option<&str>,
    ) {
        let response = Response {
            status_code: 200,
            body: vec![],
            headers,
        };

        assert_eq!(response.get_header_by_key(key), expected);
    }
}
