use std::sync::Arc;
use std::time::Duration;

use async_trait::async_trait;

use super::error::Error;
use super::request::{ApiRequest, ApiResponse, RequestOptions};

#[async_trait]
pub trait HttpClient: Send + Sync + std::fmt::Debug {
    async fn execute(
        &self,
        request: ApiRequest,
        options: &RequestOptions,
        token: Option<String>,
    ) -> Result<ApiResponse, Error>;
}

pub type HttpClientRef = Arc<dyn HttpClient>;

#[derive(Debug)]
pub struct ReqwestHttpClient {
    client: reqwest::Client,
    base_url: String,
}

impl ReqwestHttpClient {
    pub fn new(base_url: &str) -> Self {
        Self {
            client: reqwest::Client::new(),
            base_url: base_url.to_string(),
        }
    }

    pub fn with_timeout(mut self, timeout: Duration) -> Self {
        self.client = reqwest::Client::builder()
            .timeout(timeout)
            .build()
            .unwrap_or_else(|_| reqwest::Client::new());
        self
    }
}

#[async_trait]
impl HttpClient for ReqwestHttpClient {
    async fn execute(
        &self,
        request: ApiRequest,
        options: &RequestOptions,
        token: Option<String>,
    ) -> Result<ApiResponse, Error> {
        let url = super::request::build_url(
            &self.base_url,
            &request.api_path,
            &request.path_params,
            &request.query,
        )?;

        let mut req = self.client.request(request.method, url);

        if let Some(ref t) = token {
            req = req.bearer_auth(t);
        }

        for (key, value) in &options.headers {
            req = req.header(key, value);
        }

        if let Some(ref body) = request.body {
            req = req.json(body);
        }

        let timeout = options.timeout;
        if let Some(t) = timeout {
            req = req.timeout(t);
        }

        let resp = req.send().await?;
        let status = resp.status().as_u16();
        let headers = resp.headers().clone();
        let body = resp.bytes().await?.to_vec();

        Ok(ApiResponse {
            status,
            headers,
            body,
        })
    }
}

pub fn default_http_client(base_url: &str) -> HttpClientRef {
    Arc::new(ReqwestHttpClient::new(base_url))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_reqwest_http_client_builder() {
        let client = ReqwestHttpClient::new("https://open.feishu.cn")
            .with_timeout(std::time::Duration::from_secs(30));
        assert_eq!(client.base_url, "https://open.feishu.cn");
    }
}
