use std::collections::HashMap;
use std::fs;
use chrono::{Utc};
use json::{JsonValue, object};
use crate::Connection;
use crate::mode::OssMode;

#[derive(Clone)]
enum Method {
    Put,
    Get,
    // POST,
    Head,
    Delete,
    // OPTIONS,
}

impl Method {
    fn str(self) -> String {
        match self {
            Method::Put => "PUT",
            Method::Get => "GET",
            // Method::POST => "POST",
            Method::Head => "HEAD",
            Method::Delete => "DELETE",
            // Method::OPTIONS => "OPTIONS",
        }.to_string()
    }
}

#[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 _api(&mut self, method: Method, headers: JsonValue, params: JsonValue, object_name: &str, object_meta: &str) -> Result<(u16, String, String, HashMap<String, String>), String> {
        let datetime_gmt = Utc::now().format("%a, %d %b %Y %H:%M:%S GMT").to_string();

        let data = if object_name.is_empty() {
            format!("{}\n\n{}\n{}\n/{}/{}/", method.clone().str(), "application/octet-stream", datetime_gmt.clone(), self.bucket_name, object_meta)
        } else {
            format!("{}\n\n{}\n{}\n/{}/{}/{}", method.clone().str(), "application/octet-stream", datetime_gmt.clone(), self.bucket_name, object_meta, 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_name.is_empty() {
                format!("https://{}.{}/{}/", self.bucket_name, self.endpoint, object_meta)
            } else {
                format!("https://{}.{}/{}/{}", self.bucket_name, self.endpoint, object_meta, object_name)
            }
        };


        let mut http = br_http::Http::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());
        }
        match method {
            Method::Put => {
                http.put(&url.clone()).raw_stream(fs::read(params.as_str().unwrap()).unwrap_or_default())
            }
            _ => {
                http.get(&url.clone()).raw_json(params.clone())
            }
        };
        http.info()
    }
    fn _api_header(&mut self, method: Method, headers: JsonValue, params: JsonValue, object_name: &str, object_meta: &str) -> Result<(u16, 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_http::Http::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 http = 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! {})
            }
        };
        http.info()
    }
    fn _api_stream(&mut self, object_name: &str, object_meta: &str) -> Result<Vec<u8>, String> {
        let datetime_gmt = Utc::now().format("%a, %d %b %Y %H:%M:%S GMT").to_string();
        let data = format!("{}\n\n{}\n{}\n/{}/{}", Method::Get.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_http::Http::new();
        http.header("authorization", auth_token.leak());
        http.header("date", datetime_gmt.leak());
        http.header("host", format!("{}.{}", self.bucket_name, self.endpoint).leak());
        http.get(url.as_str()).stream()
    }
}
impl OssMode for Aliyun {
    fn put(&mut self, filename: &str, path: &str) -> Result<String, String> {
        let data = fs::read(filename).unwrap_or_default();
        let md5 = if data.is_empty() {
            ""
        } else {
            &*br_crypto::hash::u8_to_md5(data.clone())
        };
        let (state, _, _, _) = self._api(Method::Put, object! {}, filename.into(), md5, path)?;
        if state == 200 {
            Ok(format!("https://{}.{}/{}/{}", self.bucket_name, self.endpoint, path, md5))
        } else {
            Err("失败".to_string())
        }
    }

    fn get(&mut self, name: &str) -> Result<Vec<u8>, String> {
        let data = self._api_stream(name, "")?;
        Ok(data)
    }

    fn del(&mut self, name: &str) -> Result<bool, String> {
        let (state, _, _, _) = self._api_header(Method::Delete, object! {}, object! {}, name, "")?;
        if state == 204 {
            Ok(true)
        } else {
            Ok(false)
        }
    }

    fn get_head(&mut self, name: &str) -> Result<String, String> {
        let (state, _, _, _) = self._api_header(Method::Head, object! {}, object! {}, name, "")?;
        if state == 200 {
            Ok(format!("https://{}.{}/{}", self.bucket_name, self.endpoint, name))
        } else {
            Err("不存在".to_string())
        }
    }
    fn get_head_meta(&mut self, name: &str) -> Result<JsonValue, String> {
        let params = object! {};
        let (data, _, _, _) = self._api(Method::Head, object! {}, params, name, "objectMeta")?;

        Ok(JsonValue::from(data))
        // if data["x-oss-err"].is_empty() {
        //     Ok(JsonValue::from(data["etag"].clone()))
        // } else {
        //     let res = br_crypto::base64::decode(data["x-oss-err"].as_str().unwrap());
        //     info!("{:#}", res);
        //     Ok(JsonValue::from(""))
        // }
    }
}