use http::header::{HeaderValue, AUTHORIZATION, CONTENT_TYPE};
use http::{Method, Uri};
use oauth_credentials::Token;
use serde::de;

use crate::response::{RawResponseFuture, ResponseFuture};
use crate::traits::HttpService;

pub trait RawRequest: oauth::Request {
    fn method(&self) -> &Method;

    fn uri(&self) -> &'static str;

    fn send_raw<C, T, S, B>(
        &self,
        token: &Token<C, T>,
        http: &mut S,
    ) -> RawResponseFuture<S::Future>
    where
        C: AsRef<str>,
        T: AsRef<str>,
        S: HttpService<B>,
        B: From<Vec<u8>>,
    {
        let req = prepare_request(self.method(), self.uri(), self, token.as_ref());
        RawResponseFuture::new(http.call(req.map(Into::into)))
    }
}

pub trait Request: RawRequest {
    type Data: de::DeserializeOwned;

    fn send<C, T, S, B>(
        &self,
        token: &Token<C, T>,
        http: &mut S,
    ) -> ResponseFuture<Self::Data, S::Future>
    where
        C: AsRef<str>,
        T: AsRef<str>,
        S: HttpService<B>,
        B: From<Vec<u8>>,
    {
        self.send_raw(token, http).into()
    }
}

fn prepare_request<R>(
    method: &Method,
    uri: &'static str,
    req: &R,
    token: Token<&str, &str>,
) -> http::Request<Vec<u8>>
where
    R: oauth::Request + ?Sized,
{
    let form = method == Method::POST;

    let mut oauth = oauth::Builder::new(token.client(), oauth::HmacSha1);
    oauth.token(token.token());
    let authorization = oauth.build(method.as_str(), uri, req);

    let http = http::Request::builder()
        .method(method)
        .header(AUTHORIZATION, authorization);

    if form {
        let data = oauth::to_form_urlencoded(req).into_bytes();
        http.uri(Uri::from_static(uri))
            .header(
                CONTENT_TYPE,
                HeaderValue::from_static("application/x-www-form-urlencoded"),
            )
            .body(data)
            .unwrap()
    } else {
        let uri = oauth::to_uri_query(uri.to_owned(), req);
        http.uri(uri).body(Vec::default()).unwrap()
    }
}