Documentation
use crate::config::Config;

use reqwest::{Method, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json as json;

#[derive(Debug, Default)]
pub struct Client {
    config: Config,
    requester: reqwest::Client,
}

impl Client {
    pub fn new(config: Config) -> Result<Self, crate::Error> {
        let requester = reqwest::Client::builder().build()?;
        Ok(Self { config, requester })
    }
    pub async fn get(&self, path: &str) -> Result<json::Value, crate::Error> {
        self.send(Method::GET, path, None).await
    }
    pub async fn post(&self, path: &str, body: json::Value) -> Result<json::Value, crate::Error> {
        self.send(Method::POST, path, Some(body)).await
    }
    pub async fn put(&self, path: &str) -> Result<json::Value, crate::Error> {
        self.send(Method::PUT, path, None).await
    }
    pub async fn put_with_body(
        &self,
        path: &str,
        body: json::Value,
    ) -> Result<json::Value, crate::Error> {
        self.send(Method::PUT, path, Some(body)).await
    }
    pub async fn delete(&self, path: &str) -> Result<json::Value, crate::Error> {
        self.send(Method::DELETE, path, None).await
    }
    async fn send(
        &self,
        method: reqwest::Method,
        path: &str,
        body: Option<json::Value>,
    ) -> Result<json::Value, crate::Error> {
        let url = format!("{}{}", self.config.base_url, path);
        log::debug!("URL: {:?}", url);

        let response = self
            .requester
            .request(method, url)
            .header("x-token", &self.config.token)
            .header("Content-Type", "application/json")
            .json(&body)
            .send()
            .await?;
        let status = response.status();
        let text = response.text().await?;
        log::trace!("Response: {:?}", &text);

        match status {
            StatusCode::OK | StatusCode::CREATED => {
                let response: json::Value = json::from_str(&text)?;
                match response.get("data") {
                    Some(_) => {
                        let response: Response = json::from_str(&text)?;
                        Ok(response.data.to_owned())
                    }
                    None => {
                        log::error!("status: {}, body: {:?}", status, text);
                        Err(crate::Error::InvalidArgument(format!(
                            "Incomplete response. Status: {}. Body: {}",
                            status, text
                        )))
                    }
                }
            }
            StatusCode::NOT_FOUND => Err(crate::Error::NotFound("Resource not found".into())),
            _ => {
                log::error!("status: {}, body: {:?}", status, text);
                Err(crate::Error::InvalidArgument(format!(
                    "Request failed. Status: {}. Body: {}",
                    status, text
                )))
            }
        }
    }
}

#[derive(Debug, Deserialize, Serialize)]
struct Response {
    success: bool,
    code: i32,
    data: json::Value,
}