Documentation
use std::collections::BTreeMap;
use std::{fs};
use std::path::PathBuf;
use br_reqwest::{Client};
use chrono::Utc;
use json::{object, JsonValue};
use crate::{Connection, OssMode};


#[derive(Clone)]
pub struct Tencent {
    pub endpoint: String,
    pub access_key_id: String,
    pub access_key_secret: String,
}
impl Tencent {
    pub fn new(connection: Connection) -> Self {
        Self {
            endpoint: connection.endpoint,
            access_key_id: connection.access_key_id,
            access_key_secret: connection.access_key_secret,
        }
    }
    fn sign(&mut self, http: &mut Client, key: &str, filepath: &str, body: Vec<u8>, content_type: &str) {
        let md5 = br_crypto::md5::encrypt_base64(body.as_slice());
        let keytime = generate_key_time(3600);

        let mut headers: BTreeMap<&str, JsonValue> = BTreeMap::new();
        headers.insert("Host", self.endpoint.to_string().into());
        headers.insert("Content-Type", content_type.into());
        headers.insert("Content-Length", body.len().into());
        headers.insert("Content-MD5", md5.into());

        let paramslist: Vec<String> = vec![];
        let mut headerlist = vec![];
        let mut http_headers = vec![];
        let http_parameters: Vec<String> = vec![];
        for (&item, value) in headers.iter() {
            http.header(item, &value.to_string());
            headerlist.push(item);
        }

        for &item in headerlist.iter() {
            let value = headers.get(item).unwrap();
            let value = br_crypto::encoding::urlencoding_encode(value.to_string().as_str());
            http_headers.push(format!("{}={}", item.to_lowercase(), value));
        }

        let http_parameters = http_parameters.join("&");

        let http_headers = http_headers.join("&");


        let sign_key = br_crypto::sha1::encrypt_hmac(&self.access_key_secret, keytime.as_bytes());
        let httpstring = format!("{}\n{key}\n{http_parameters}\n{http_headers}\n", http.method.to_str().to_lowercase());

        let httpstring = br_crypto::sha1::encrypt_hex(httpstring.as_bytes());
        let string_to_sign = format!("sha1\n{keytime}\n{httpstring}\n");
        let signature = br_crypto::sha1::encrypt_hmac(&sign_key, string_to_sign.as_bytes());
        let mut authorization = vec![];
        authorization.push(format!("q-sign-algorithm={}", "sha1"));
        authorization.push(format!("q-ak={}", self.access_key_id));
        authorization.push(format!("q-sign-time={keytime}"));
        authorization.push(format!("q-key-time={keytime}"));
        authorization.push(format!("q-header-list={}", headerlist.join(";").to_lowercase()));
        authorization.push(format!("q-url-param-list={}", paramslist.join(";").to_lowercase()));
        authorization.push(format!("q-signature={signature}"));
        http.header("Authorization", &authorization.join("&"));

        http.raw_stream(filepath);
    }
    fn sign_get(&mut self, http: &mut Client, key: &str) {
        let keytime = generate_key_time(3600);

        let mut headers: BTreeMap<&str, JsonValue> = BTreeMap::new();
        headers.insert("Host", self.endpoint.to_string().into());

        let paramslist: Vec<String> = vec![];
        let mut headerlist = vec![];
        let mut http_headers = vec![];
        let http_parameters: Vec<String> = vec![];
        for (&item, value) in headers.iter() {
            http.header(item, &value.to_string());
            headerlist.push(item);
        }

        for &item in headerlist.iter() {
            let value = headers.get(item).unwrap();
            let value = br_crypto::encoding::urlencoding_encode(value.to_string().as_str());
            http_headers.push(format!("{}={}", item.to_lowercase(), value));
        }
        let http_parameters = http_parameters.join("&");
        let http_headers = http_headers.join("&");
        let sign_key = br_crypto::sha1::encrypt_hmac(&self.access_key_secret, keytime.as_bytes());
        let httpstring = format!("{}\n{key}\n{http_parameters}\n{http_headers}\n", http.method.to_str().to_lowercase());

        let httpstring = br_crypto::sha1::encrypt_hex(httpstring.as_bytes());
        let string_to_sign = format!("sha1\n{keytime}\n{httpstring}\n");
        let signature = br_crypto::sha1::encrypt_hmac(&sign_key, string_to_sign.as_bytes());
        let mut authorization = vec![];
        authorization.push(format!("q-sign-algorithm={}", "sha1"));
        authorization.push(format!("q-ak={}", self.access_key_id));
        authorization.push(format!("q-sign-time={keytime}"));
        authorization.push(format!("q-key-time={keytime}"));
        authorization.push(format!("q-header-list={}", headerlist.join(";").to_lowercase()));
        authorization.push(format!("q-url-param-list={}", paramslist.join(";").to_lowercase()));
        authorization.push(format!("q-signature={signature}"));
        http.header("Authorization", &authorization.join("&"));
        http.query(object! {});
    }
    fn sign_head(&mut self, http: &mut Client, key: &str) {
        let keytime = generate_key_time(3600);

        let mut headers: BTreeMap<&str, JsonValue> = BTreeMap::new();
        headers.insert("Host", self.endpoint.to_string().into());

        let paramslist: Vec<String> = vec![];
        let mut headerlist = vec![];
        let mut http_headers = vec![];
        let http_parameters: Vec<String> = vec![];
        for (&item, value) in headers.iter() {
            http.header(item, &value.to_string());
            headerlist.push(item);
        }

        for &item in headerlist.iter() {
            let value = headers.get(item).unwrap();
            let value = br_crypto::encoding::urlencoding_encode(value.to_string().as_str());
            http_headers.push(format!("{}={}", item.to_lowercase(), value));
        }
        let http_parameters = http_parameters.join("&");
        let http_headers = http_headers.join("&");
        let sign_key = br_crypto::sha1::encrypt_hmac(&self.access_key_secret, keytime.as_bytes());
        let httpstring = format!("{}\n{key}\n{http_parameters}\n{http_headers}\n", http.method.to_str().to_lowercase());
        let httpstring = br_crypto::sha1::encrypt_hex(httpstring.as_bytes());
        let string_to_sign = format!("sha1\n{keytime}\n{httpstring}\n");
        let signature = br_crypto::sha1::encrypt_hmac(&sign_key, string_to_sign.as_bytes());
        let mut authorization = vec![];
        authorization.push(format!("q-sign-algorithm={}", "sha1"));
        authorization.push(format!("q-ak={}", self.access_key_id));
        authorization.push(format!("q-sign-time={keytime}"));
        authorization.push(format!("q-key-time={keytime}"));
        authorization.push(format!("q-header-list={}", headerlist.join(";").to_lowercase()));
        authorization.push(format!("q-url-param-list={}", paramslist.join(";").to_lowercase()));
        authorization.push(format!("q-signature={signature}"));
        http.header("Authorization", &authorization.join("&"));
    }
}

fn generate_key_time(valid_secs: i64) -> String {
    let start_timestamp = Utc::now().timestamp();
    let end_timestamp = start_timestamp + valid_secs;
    format!("{start_timestamp};{end_timestamp}")
}

impl OssMode for Tencent {
    fn upload(&mut self, dirname: &str, file: PathBuf, content_type: &str) -> Result<(String, String), String> {
        let mut http = Client::new();
        let body = match fs::read(file.clone()) {
            Ok(e) => e,
            Err(e) => return Err(e.to_string()),
        };
        let md5 = br_crypto::md5::encrypt_hex(body.as_slice());
        let uri_pathname = format!("{dirname}{md5}");
        let url = format!("https://{}{uri_pathname}", self.endpoint);
        http.put(url.as_str());
        self.sign(&mut http, &uri_pathname, file.to_str().unwrap(), body, content_type);

        let res = http.send()?;
        if res.status() != 200 {
            return Err(res.xml()?["Message"].to_string());
        }
        let etag = res.headers()["etag"].to_string().trim_start_matches("\"").trim_end_matches("\"").to_lowercase();
        Ok((etag, url))
    }

    fn download(&mut self, dirname: &str, key: &str) -> Result<(String, String, String, Vec<u8>), String> {
        let mut http = Client::new();
        let uri_pathname = format!("{dirname}{key}");
        let url = format!("https://{}{uri_pathname}", self.endpoint);
        http.get(&url);
        self.sign_get(&mut http, &uri_pathname);

        let res = http.send()?;
        if res.status() != 200 {
            return Err(res.xml()?["Message"].to_string());
        }
        let etag = res.headers()["etag"].to_string().trim_start_matches("\"").trim_end_matches("\"").to_lowercase();
        let content_type = res.headers()["content-type"].to_string();
        let body = res.stream();
        Ok((etag, url, content_type, body))
    }

    fn query(&mut self, dirname: &str, key: &str) -> Result<(String, String), String> {
        let mut http = Client::new();
        let uri_pathname = format!("{dirname}{key}");
        let url = format!("https://{}{uri_pathname}", self.endpoint);
        http.head(&url);
        self.sign_head(&mut http, &uri_pathname);

        let res = http.send()?;
        if res.status() != 200 {
            return Err("No such resource".to_string());
        }
        let etag = res.headers()["etag"].to_string().trim_start_matches("\"").trim_end_matches("\"").to_lowercase();
        Ok((etag, url))
    }

    fn del(&mut self, dirname: &str, key: &str) -> Result<bool, String> {
        let mut http = Client::new();
        let uri_pathname = format!("{dirname}{key}");
        let url = format!("https://{}{uri_pathname}", self.endpoint);
        http.delete(&url);
        self.sign_head(&mut http, &uri_pathname);
        let res = http.send()?;
        if res.status() != 204 {
            return Err("No such resource".to_string());
        }
        Ok(true)
    }
}