use crate::{Requester, RequesterBuilder};
use async_trait::async_trait;
use std::collections::HashMap;

#[derive(Clone)]
pub struct ReqwestRequester(reqwest::Client);

impl ReqwestRequester {
    pub fn inner(&self) -> &reqwest::Client {
        &self.0
    }
}

#[cfg_attr(feature = "rt-multi-thread", async_trait)]
#[cfg_attr(not(feature = "rt-multi-thread"), async_trait(?Send))]
impl Requester for ReqwestRequester {
    type Error = ReqwestRequesterError;

    async fn get(
        &self,
        url: &url::Url,
        body: &[u8],
        headers: HashMap<&str, &str>,
    ) -> Result<Vec<u8>, Self::Error> {
        self.request(reqwest::Method::GET, url, body, headers).await
    }

    async fn post(
        &self,
        url: &url::Url,
        body: &[u8],
        headers: HashMap<&str, &str>,
    ) -> Result<Vec<u8>, Self::Error> {
        self.request(reqwest::Method::POST, url, body, headers)
            .await
    }
}

pub struct ReqwestRequesterBuilder(reqwest::ClientBuilder);

impl RequesterBuilder for ReqwestRequesterBuilder {
    type Requester = ReqwestRequester;

    fn with_pem_bundle(mut self, bytes: &[u8]) -> Self {
        // TODO: Error when certificate parsing failed?
        let certificates = reqwest::tls::Certificate::from_pem_bundle(bytes).unwrap_or_default();
        for c in certificates {
            self.0 = self.0.add_root_certificate(c);
        }
        self
    }

    fn build(self) -> Self::Requester {
        // TODO: Fail when not able to build?
        ReqwestRequester(self.0.build().unwrap_or_default())
    }
}

impl Default for ReqwestRequesterBuilder {
    fn default() -> Self {
        Self(reqwest::Client::builder())
    }
}

impl ReqwestRequester {
    pub fn new() -> Self {
        Self(reqwest::Client::new())
    }

    async fn request(
        &self,
        method: reqwest::Method,
        url: &url::Url,
        body: &[u8],
        headers: HashMap<&str, &str>,
    ) -> Result<Vec<u8>, ReqwestRequesterError> {
        log::trace!(
            "{}: URL: {}, Body: {}, Headers: {:?}",
            method,
            url,
            String::from_utf8_lossy(body),
            headers
        );
        let body = body.to_vec();
        // TODO: Fail on parsing header.
        let mut req = self.0.request(method, url.clone()).body(body);

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

        let response = req.send().await?;

        let status = response.status();
        let bytes = response.bytes().await?;

        if status.is_success() {
            Ok(bytes.to_vec())
        } else {
            log::error!(
                "Request was not successfull; returned body: {}",
                String::from_utf8_lossy(&bytes)
            );
            Err(ReqwestRequesterError::NoSuccessStatusCode(
                status.as_u16(),
                status.canonical_reason(),
                bytes.to_vec(),
            ))
        }
    }
}

impl Default for ReqwestRequester {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug)]
pub enum ReqwestRequesterError {
    /// reqwest failed.
    Reqwest(reqwest::Error),
    /// Got a status code which is no success.
    /// Contains the status code, the "canonical reason" and the body bytes.
    NoSuccessStatusCode(u16, Option<&'static str>, Vec<u8>),
}

impl std::fmt::Display for ReqwestRequesterError {
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        match self {
            Self::Reqwest(e) => write!(fmt, "reqwest error: {}", e),
            Self::NoSuccessStatusCode(code, Some(reason), _) => {
                write!(fmt, "unsuccessful status code {} ({})", code, reason)
            }
            Self::NoSuccessStatusCode(code, None, _) => {
                write!(fmt, "unsuccessful status code {}", code)
            }
        }
    }
}

impl std::error::Error for ReqwestRequesterError {}

impl From<reqwest::Error> for ReqwestRequesterError {
    fn from(e: reqwest::Error) -> ReqwestRequesterError {
        Self::Reqwest(e)
    }
}
