use http::Method;
use std::collections::HashMap;
use url::ParseError;

use crate::into_url::IntoUrl;
use crate::response;

// The DEFAULT_USER_AGENT is setted in the build.rs script
// and can be overwritten in compiled time
static DEFAULT_USER_AGENT: &str = env!("DEFAULT_USER_AGENT");

#[derive(Clone)]
pub enum ParameterType {
    Number(isize),
    Decimal(f64),
    Boolean(bool),
    Text(String),
}

pub trait RequestBase {
    fn new<T: IntoUrl>(url: T) -> Result<Self, ParseError>
    where
        Self: Sized;
    fn launch(self) -> response::Response;
}

#[derive(Debug)]
pub struct Request {
    pub url: url::Url,
    pub user_agent: String,
    method: Method,
    params: Vec<(String, String)>,
    headers: HashMap<String, String>,
}

impl RequestBase for Request {
    fn new<T: IntoUrl>(url: T) -> Result<Request, ParseError> {
        url.into_url().map(|url_parsed| Request {
            url: url_parsed,
            user_agent: DEFAULT_USER_AGENT.to_string(),
            method: Method::GET,
            params: Vec::new(),
            headers: HashMap::new(),
        })
    }

    fn launch(mut self) -> response::Response {
        let client = reqwest::blocking::Client::builder()
            .user_agent(self.user_agent)
            .build()
            .unwrap();

        // Set GET params
        for (key, value) in &self.params {
            self.url.query_pairs_mut().append_pair(key, value);
        }

        let resp = (match self.method {
            Method::GET => client.get(self.url.as_str()),
            Method::POST => client.post(self.url.as_str()),
            Method::PUT => client.put(self.url.as_str()),
            Method::PATCH => client.patch(self.url.as_str()),
            Method::DELETE => client.delete(self.url.as_str()),
            _ => unimplemented!(),
        })
        .send()
        .unwrap();

        let status = resp.status();
        let text = resp.text().unwrap();

        let url = self.url;

        response::Response {
            text: text,
            status_code: status,
            url: url,
        }
    }
}

impl std::string::ToString for ParameterType {
    fn to_string(&self) -> String {
        match self {
            ParameterType::Number(n) => n.to_string(),
            ParameterType::Decimal(n) => n.to_string(),
            ParameterType::Boolean(n) => n.to_string(),
            ParameterType::Text(t) => t.clone(),
        }
    }
}

impl Request {
    pub fn method(mut self, method: Method) -> Self {
        self.method = method;
        self
    }

    pub fn set_user_agent<S: AsRef<str>>(mut self, user_agent: S) -> Self {
        self.user_agent = user_agent.as_ref().to_string();
        self
    }

    pub fn add_params<S: AsRef<str>, T: ToString>(mut self, params: Vec<(S, T)>) -> Self {
        self.params = params
            .iter()
            .map(|item| (item.0.as_ref().to_string(), item.1.to_string()))
            .collect();
        self
    }

    pub fn add_param<S: AsRef<str>, T: ToString>(&mut self, key: S, value: T) {
        self.params
            .push((key.as_ref().to_string(), value.to_string()));
    }
}