Documentation
use std::fs;
use std::path::PathBuf;
use br_reqwest::Client;
use chrono::{Utc};
use crate::{Connection, OssMode};


#[derive(Clone)]
pub struct Aliyun {
    pub endpoint: String,
    pub access_key_id: String,
    pub access_key_secret: String,
    pub bucket_name: String,
}

impl Aliyun {
    pub fn new(connection: Connection) -> Self {
        Self {
            endpoint: connection.endpoint,
            access_key_id: connection.access_key_id,
            access_key_secret: connection.access_key_secret,
            bucket_name: connection.bucket_name,
        }
    }
    fn http_put(&mut self, http: &mut Client, filepath: &str, uri_pathname: &str, content_type: &str) -> Result<(), String> {
        let datetime_gmt = Utc::now().format("%a, %d %b %Y %H:%M:%S GMT").to_string();

        let method = http.method.to_str();
        let data = format!("{}\n\n{}\n{}\n/{}{}", method, content_type, datetime_gmt.clone(), self.bucket_name, uri_pathname);

        let signature = br_crypto::sha1::encrypt_hmac(self.access_key_secret.as_str(), data.as_bytes());
        let base64_signature = br_crypto::base64::encode_file(signature.as_bytes().to_vec());
        let auth_token = format!("OSS {}:{}", self.access_key_id, base64_signature);

        http.header("authorization", auth_token.leak());
        http.header("date", datetime_gmt.leak());
        http.header("host", format!("{}.{}", self.bucket_name, self.endpoint).leak());
        http.header("Content-Type", content_type);
        http.raw_stream(filepath);
        Ok(())
    }
    //fn _api_header(&mut self, method: Method, headers: JsonValue, params: JsonValue, object_name: &str, object_meta: &str) -> Result<(u16, String, HashMap<String, String>, HashMap<String, String>), String> {
    //    let datetime_gmt = Utc::now().format("%a, %d %b %Y %H:%M:%S GMT").to_string();
    //    let data = match method {
    //        Method::Delete => {
    //            format!("{}\n\n{}\n{}\n/{}/{}", method.clone().str(), "", datetime_gmt.clone(), self.bucket_name, object_name)
    //        }
    //        Method::Put => {
    //            if object_meta.is_empty() {
    //                format!("{}\n\n{}\n{}\n/{}/{}", method.clone().str(), "application/octet-stream", datetime_gmt.clone(), self.bucket_name, object_name)
    //            } else {
    //                format!("{}\n\n{}\n{}\n/{}/{}/", method.clone().str(), "application/octet-stream", datetime_gmt.clone(), self.bucket_name, object_meta)
    //            }
    //        }
    //        Method::Head => {
    //            if object_meta.is_empty() {
    //                format!("{}\n\n{}\n{}\n/{}/{}", method.clone().str(), "", datetime_gmt.clone(), self.bucket_name, object_name)
    //            } else {
    //                format!("{}\n\n{}\n{}\n/{}/{}?{}", method.clone().str(), "", datetime_gmt.clone(), self.bucket_name, object_name, object_meta)
    //            }
    //        }
    //        _ => {
    //            format!("{}\n\n{}\n{}\n/{}/{}", method.clone().str(), "", datetime_gmt.clone(), self.bucket_name, object_name)
    //        }
    //    };
    //    let signature = br_crypto::hmac::sha1_code(self.access_key_secret.as_str(), data.as_str());
    //    let base64_signature = br_crypto::base64::encode_file(signature.clone());
    //    let auth_token = format!("OSS {}:{}", self.access_key_id, base64_signature);
    //    let url = {
    //        if object_meta.is_empty() {
    //            format!("https://{}.{}/{}", self.bucket_name, self.endpoint, object_name)
    //        } else {
    //            format!("https://{}.{}/{}?{}", self.bucket_name, self.endpoint, object_name, object_meta)
    //        }
    //    };
    //    let mut http = br_reqwest::Client::new();
    //
    //    http.header("authorization", auth_token.leak());
    //    http.header("date", datetime_gmt.leak());
    //    http.header("host", format!("{}.{}", self.bucket_name, self.endpoint).leak());
    //
    //    for (key, value) in headers.clone().entries() {
    //        http.header(key.to_string().leak(), value.to_string().leak());
    //    }
    //    let _ = match method {
    //        Method::Put => {
    //            http.header("Content-Type", "application/octet-stream");
    //            http.put(&url.clone())
    //        }
    //        Method::Get => {
    //            http.get(&url.clone()).raw_json(params)
    //        }
    //        // Method::POST => {
    //        //     http.post(&*url.clone()).raw_json(params)
    //        // }
    //        Method::Head => {
    //            http.head(&url.clone())
    //        }
    //        Method::Delete => {
    //            http.delete(&url.clone()).raw_json(object! {})
    //        }
    //    };
    //    Err("".to_owned())
    //}
    fn http_get(&mut self, http: &mut Client, uri_pathname: &str) -> Result<(), String> {
        let datetime_gmt = Utc::now().format("%a, %d %b %Y %H:%M:%S GMT").to_string();
        let data = format!("{}\n\n{}\n{}\n/{}{}", http.method.to_str(), "", datetime_gmt.clone(), self.bucket_name, uri_pathname);
        let signature = br_crypto::sha1::encrypt_hmac(self.access_key_secret.as_str(), data.as_bytes());
        let base64_signature = br_crypto::base64::encode_file(signature.as_bytes().to_vec());
        let auth_token = format!("OSS {}:{}", self.access_key_id, base64_signature);
        http.header("authorization", auth_token.leak());
        http.header("date", datetime_gmt.leak());
        http.header("host", format!("{}.{}", self.bucket_name, self.endpoint).leak());
        Ok(())
    }
}
impl OssMode for Aliyun {
    fn upload(&mut self, dirname: &str, file: PathBuf, content_type: &str) -> Result<(String, String), String> {
        let body = match fs::read(file.clone()) {
            Ok(e) => e,
            Err(e) => return Err(e.to_string()),
        };
        let mut http = Client::new();

        let md5 = br_crypto::md5::encrypt_hex(body.as_slice());
        let uri_pathname = format!("{dirname}{md5}");
        let url = format!("https://{}.{}{}", self.bucket_name, self.endpoint, uri_pathname);
        http.put(url.as_str());
        self.http_put(&mut http, file.to_str().unwrap(), &uri_pathname, 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://{}.{}{}", self.bucket_name, self.endpoint, uri_pathname);
        http.get(url.as_str());
        self.http_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://{}.{}{}", self.bucket_name, self.endpoint, uri_pathname);
        http.head(&url);
        self.http_get(&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://{}.{}{}", self.bucket_name, self.endpoint, uri_pathname);
        http.delete(&url);
        self.http_get(&mut http, &uri_pathname)?;
        let res = http.send()?;
        if res.status() != 204 {
            return Err("No such resource".to_string());
        }
        Ok(true)
    }
}