use std::str::FromStr;
use std::time::Duration;

use reqwest::{Certificate, Method, Proxy, Url};
use reqwest::blocking::{Body, Client, Request};
use reqwest::header::{HeaderName, HeaderValue};
use rust_util::{simple_error, XResult};

const TEST_CERT: &str = r#"-----BEGIN CERTIFICATE-----
MIIDeTCCAmGgAwIBAgIJAKL5ZETgtiFQMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp
c2NvMQ8wDQYDVQQKDAZCYWRTU0wxFTATBgNVBAMMDCouYmFkc3NsLmNvbTAeFw0y
MzA0MjQwMDAxNDVaFw0yNTA0MjMwMDAxNDVaMGIxCzAJBgNVBAYTAlVTMRMwEQYD
VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQK
DAZCYWRTU0wxFTATBgNVBAMMDCouYmFkc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMIE7PiM7gTCs9hQ1XBYzJMY61yoaEmwIrX5lZ6xKyx2
PmzAS2BMTOqytMAPgLaw+XLJhgL5XEFdEyt/ccRLvOmULlA3pmccYYz2QULFRtMW
hyefdOsKnRFSJiFzbIRMeVXk0WvoBj1IFVKtsyjbqv9u/2CVSndrOfEk0TG23U3A
xPxTuW1CrbV8/q71FdIzSOciccfCFHpsKOo3St/qbLVytH5aohbcabFXRNsKEqve
ww9HdFxBIuGa+RuT5q0iBikusbpJHAwnnqP7i/dAcgCskgjZjFeEU4EFy+b+a1SY
QCeFxxC7c3DvaRhBB0VVfPlkPz0sw6l865MaTIbRyoUCAwEAAaMyMDAwCQYDVR0T
BAIwADAjBgNVHREEHDAaggwqLmJhZHNzbC5jb22CCmJhZHNzbC5jb20wDQYJKoZI
hvcNAQELBQADggEBAJqRSkgOf5GHCJzljWQg9D+1LEuByYyQfNzGJb+TZkPpxNEw
6gbt3vbQfWBx9WQ6995XjdjM6N6l5DO8p0Sp70OHHQ9Lt2N7PC7I5YhJFObkMyza
sRuLWTzlYShLvSRGQFC/Ky4hTbpzlZA5TADG1weajSlIBLo6UGkQaGk4xG4zhIKA
PhvsFZsayLexJ1DCql0XAiNnknTfX8FRMI9Ezsj0XeZ8ZD8ouLGYwbTezcYnE/uI
0Y/ayROwdd+Ny4N6McsEE+KOxS8Xe+LU4X3MEHSXcmT8ht/xTyxQ2JjzHtS6eHjO
lJON+7kLWv6kgtYf9jHJDsNMPLis6RbUYdkeP5A=
-----END CERTIFICATE-----"#;

fn main() -> XResult<()> {
    let _ = print_response(&fetch(
        "https://self-signed.badssl.com/",
        &FetchOptions {
            trust_all_certificates: false,
            certificates: vec![TEST_CERT.into()],
            ..Default::default()
        },
    )?);
    Ok(())
}

fn main2() -> XResult<()> {
    println!("TEST1: GET");
    let response = fetch(
        "https://hatter.ink/util/print_request.action",
        &FetchOptions::default())?;
    print_response(&response)?;

    println!("TEST2: PUT");
    let response = fetch(
        "https://hatter.ink/util/print_request.action",
        &FetchOptions {
            method: FetchMethod::Put,
            body: Some("{}".as_bytes().to_vec()),
            ..Default::default()
        })?;
    print_response(&response)?;

    println!("TEST3: POST");
    let response = fetch(
        "https://hatter.ink/util/print_request.action",
        &FetchOptions {
            method: FetchMethod::Post,
            headers: vec![
                FetchHeader {
                    key: "content-type".to_string(),
                    value: "application/json".to_string(),
                }
            ],
            body: Some("{}".as_bytes().to_vec()),
            ..Default::default()
        })?;
    print_response(&response)?;

    Ok(())
}

fn print_response(response: &FetchResponse) -> XResult<()> {
    println!("Status: {}", response.status);
    response.headers.iter().for_each(|_h| {
        // println!("- {}: {}", h.key, h.value);
    });
    println!("----- BEGIN RESPONSE BODY -----\n{}\n----- END RESPONSE BODY -----",
             String::from_utf8(response.body().clone().unwrap().to_vec())?);
    Ok(())
}

#[derive(Clone, Copy, Debug)]
pub enum FetchMethod {
    Get,
    Post,
    Put,
}

impl FetchMethod {
    pub fn require_body(&self) -> bool {
        match self {
            FetchMethod::Get => false,
            FetchMethod::Post | FetchMethod::Put => true,
        }
    }
}

impl Default for FetchMethod {
    fn default() -> Self {
        Self::Get
    }
}

#[derive(Clone, Debug)]
pub struct FetchHeader {
    pub key: String,
    pub value: String,
}

#[derive(Clone, Debug, Default)]
pub struct FetchOptions {
    pub method: FetchMethod,
    pub headers: Vec<FetchHeader>,
    pub body: Option<Vec<u8>>,
    pub connect_timeout: Option<Duration>,
    pub timeout: Option<Duration>,
    pub proxy: Option<String>,
    // TODO hsts supports
    // TODO follow redirects?
    pub certificates: Vec<String>,
    pub trust_all_certificates: bool,
}

impl FetchOptions {}

#[derive(Clone, Debug)]
pub struct FetchResponse {
    status: u16,
    headers: Vec<FetchHeader>,
    body: Option<Vec<u8>>,
}

impl FetchResponse {
    pub fn body(&self) -> &Option<Vec<u8>> {
        &self.body
    }

    pub fn headers(&self) /* TODO */ {
        todo!()
    }

    pub fn ok(&self) -> bool {
        self.status == 200
    }

    pub fn status(&self) -> u16 {
        self.status
    }
}

pub fn fetch(url: &str, option: &FetchOptions) -> XResult<FetchResponse> {
    let mut client_builder = Client::builder();
    if let Some(proxy) = &option.proxy {
        client_builder = client_builder.proxy(Proxy::all(proxy.as_str())?);
    }
    if option.trust_all_certificates {
        client_builder = client_builder.danger_accept_invalid_certs(true);
    }
    if !option.certificates.is_empty() {
        client_builder = client_builder.use_rustls_tls();
        for cert in &option.certificates {
            match Certificate::from_pem(cert.as_bytes()) {
                Ok(cert) => client_builder = client_builder.add_root_certificate(cert),
                Err(e) => return simple_error!("Parse certificate failed: {}", e),
            }
        }
    }
    let connect_timeout = match &option.connect_timeout {
        Some(connect_timeout) => connect_timeout.to_owned(),
        None => Duration::from_secs(5),
    };
    client_builder = client_builder.connect_timeout(connect_timeout);
    // client_builder = client_builder.timeout(Duration::from_secs(50));
    let client = client_builder.build()?;
    let method = match option.method {
        FetchMethod::Get => Method::GET,
        FetchMethod::Post => Method::POST,
        FetchMethod::Put => Method::PUT,
    };
    let mut request = Request::new(method, Url::from_str(url)?);
    if let Some(timeout) = &option.timeout {
        let _ = request.timeout_mut().insert(timeout.to_owned());
    }
    if !option.headers.is_empty() {
        let headers_mut = request.headers_mut();
        for header in &option.headers {
            let header_name = HeaderName::from_str(&header.key)?;
            let header_value = HeaderValue::from_str(&header.value)?;
            headers_mut.insert(header_name, header_value);
        }
    }

    if option.method.require_body() && option.body.is_none() {
        return simple_error!("Require body.");
    }
    if let Some(body) = &option.body {
        let _ = request.body_mut().insert(Body::from(body.to_vec()));
    }
    let response = client.execute(request)?;

    let response_status = response.status();
    let mut response_headers = vec![];
    let response_header_map = response.headers();
    for (response_header_name, response_header_vlaue) in response_header_map {
        let key = response_header_name.to_string();
        let value = response_header_vlaue.to_str()?.to_string();
        response_headers.push(FetchHeader { key, value });
    }
    let bytes = response.bytes()?;

    Ok(FetchResponse {
        status: response_status.as_u16(),
        headers: response_headers,
        body: Some(bytes.to_vec()),
    })
}
