use crate::serialization;
use crate::serialization::Encoding;
use crate::RPCRequest;
use crate::{config::LotusConfig, types::api::Rs};
use reqwest::{Client, RequestBuilder};
use serde_json::{json, Value};
use std::fmt::Debug;

#[derive(Debug, Clone)]
pub struct LotusClient {
    config: LotusConfig,
    client: Client,
}

impl<'de> LotusClient {
    pub fn init(config: LotusConfig) -> LotusClient {
        LotusClient {
            config,
            client: reqwest::Client::new(),
        }
    }

    pub fn build(&self) -> RequestBuilder {
        let request_builder = self
            .client
            .post(self.config.host_url.to_string())
            .header("Content-Type", self.config.content_type.to_string());

        if !&self.config.token.is_empty() {
            request_builder.bearer_auth(self.config.token.to_string())
        } else {
            request_builder
        }
    }

    pub fn format<T: serde::Serialize>(&self, method: String, val: T) -> RPCRequest {
        RPCRequest {
            jsonrpc: "2.0".to_string(),
            method,
            id: 1,
            params: json!(val),
        }
    }

    pub async fn send<Res: serde::de::DeserializeOwned>(
        &self,
        method: String,
        data: Vec<Value>,
    ) -> anyhow::Result<Res> {
        let response = self
            .build()
            .json(&self.format(format!("Filecoin.{}", method).to_string(), &data))
            .send()
            .await?;

        let encoding = response.encoding();
        let bytes = response.bytes().await?;
        let trace_body = |error| {
            let text_body = serialization::deserialize_text(&bytes, encoding);

            tracing::error!(
                "Invalid body: {}",
                if text_body.is_empty() {
                    "<empty-body>".to_string()
                } else {
                    text_body
                }
            );

            error
        };

        let json_response =
            serialization::deserialize_json::<Rs<Res>>(&bytes).map_err(trace_body)?;
        Ok(json_response.result)
    }
}
