// MARK: - Method

use std::{borrow::Cow, time::Duration};

use derive_builder::Builder;

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum HttpMethod {
    GET,
    POST,
}

impl From<HttpMethod> for reqwest::Method {
    fn from(m: HttpMethod) -> Self {
        match m {
            HttpMethod::GET => reqwest::Method::GET,
            HttpMethod::POST => reqwest::Method::POST,
        }
    }
}

// MARK: - Request

#[derive(Builder, Clone)]
#[builder(pattern = "owned", setter(into), derive(Clone))]
#[cfg_attr(test, derive(PartialEq))]
pub struct Request {
    pub method: HttpMethod,
    pub url: String,
    #[builder(default, setter(each = "header"))]
    pub headers: Vec<(&'static str, String)>,
    #[builder(default)]
    pub body: Option<Vec<u8>>,
    #[builder(default)]
    pub timeout: Option<Duration>,
    #[builder(default = false)]
    pub redact_body: bool,
}

impl std::fmt::Debug for Request {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut dbg = f.debug_struct("Request");
        dbg.field("method", &self.method).field("url", &self.url).field("timeout", &self.timeout);
        if self.method == HttpMethod::POST {
            let body: Cow<'_, str> = if self.redact_body {
                Cow::Borrowed("<Sensitive Data Redacted>")
            } else {
                match &self.body {
                    Some(b) => String::from_utf8_lossy(b),
                    None => Cow::Borrowed(""),
                }
            };
            dbg.field("body", &body);
        }
        dbg.finish()
    }
}

impl RequestBuilder {
    pub fn is_timeout_set(&self) -> bool {
        self.timeout.is_some()
    }
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use reqwest::header::CONTENT_TYPE;

    use super::*;

    #[test]
    fn test_request_builder_min_values() {
        let req = RequestBuilder::default().method(HttpMethod::POST).url("https://example.com/api").build().unwrap();

        assert_eq!(HttpMethod::POST, req.method);
        assert_eq!("https://example.com/api", req.url);
        assert_eq!(0, req.headers.len());
        assert_eq!(None, req.body);
        assert_eq!(None, req.timeout)
    }

    #[test]
    fn test_request_builder_all_values() {
        let req = RequestBuilder::default()
            .method(HttpMethod::POST)
            .url("https://example.com/api")
            .header((CONTENT_TYPE.as_str(), "application/json".to_string()))
            .header(("Authorization", "Bearer 123".to_string()))
            .body(Some(r#"{"key":"value"}"#.as_bytes().to_vec()))
            .timeout(Duration::from_millis(1000))
            .build()
            .unwrap();

        assert_eq!(HttpMethod::POST, req.method);
        assert_eq!("https://example.com/api", req.url);
        assert_eq!(2, req.headers.len());
        assert_eq!((CONTENT_TYPE.as_str(), "application/json".to_string()), req.headers[0]);
        assert_eq!(("Authorization", "Bearer 123".to_string()), req.headers[1]);
        assert_eq!(r#"{"key":"value"}"#, String::from_utf8(req.body.unwrap()).unwrap());
        assert_eq!(Some(Duration::from_millis(1000)), req.timeout)
    }
}
