use std::{fs, thread};
use std::fs::OpenOptions;
use std::io::Write;
use std::time::{Duration, UNIX_EPOCH};
use chrono::{DateTime, Local};
use json::{array, JsonValue, object};
use log::{error, info};
use crate::config::Config;
use crate::content_type::ContentType;

/// 请求
#[derive(Clone, Debug)]
pub struct Request {
    pub config: Config,
    pub header_line: String,
    /// 协议版本
    pub version: String,
    /// 当前请求类型
    pub method: Method,
    pub uri: String,
    /// 应用名称
    pub app: String,

    /// 请求时间戳
    pub timestamp: f64,
    /// 当前时间
    pub datetime: String,

    pub headers: Vec<String>,

    /// 服务器IP
    pub server_ip: String,
    /// 客户端IP
    pub client_ip: String,
    /// 代理IP
    pub agent_ip: String,

    /// 请求源
    pub origin: String,
    /// 当前访问域名或者IP 主机和端口号 头域
    pub host: String,
    /// 请求的目的地即如何使用获取的数据
    pub sec_fetch_dest: String,
    /// 请求的模式
    pub sec_fetch_mode: String,
    /// 表示一个请求发起者的来源与目标资源来源之间的关系
    pub sec_fetch_site: String,
    /// 连接方式
    pub connection: String,
    /// 客户端类型
    pub user_agent: String,
    /// 客户端接受类型
    pub accept: String,
    /// 客户端接受加密
    pub accept_encoding: String,
    /// 客户端接受语言
    pub accept_language: String,
    /// 缓存机制
    pub cache_control: String,
    /// 请求内容的长度
    pub content_length: usize,
    pub content_body_length: usize,
    /// 来源于
    pub referer: String,
    /// 请求类型
    pub content_type: RequestContentType,
    /// 分界线
    pub boundary: String,
    /// 自定义参数
    pub custom: JsonValue,
    /// 查询条件
    pub query: String,
    pub post: JsonValue,
    pub get: JsonValue,

    pub files: JsonValue,
    /// 认证模式
    pub authorization_mode: String,
    pub authorization_value: String,
    /// 是否有源
    pub is_origin: bool,

}

impl Request {
    pub fn new(config: Config, data: Vec<String>) -> Request {
        if config.debug {
            info!("{:#?}",data);
        }
        let header_line = data[0].trim().to_string();
        let timestamp = Local::now().timestamp_millis() as f64 / 1000.0;
        let d = UNIX_EPOCH + Duration::from_secs(timestamp as u64);
        let datetime = DateTime::<Local>::from(d);
        let datetime = datetime.format("%Y-%m-%d %H:%M:%S").to_string();

        let text = header_line.split(" ").collect::<Vec<&str>>();

        let mut method = Method::from(text[0]);
        let uri = match method {
            Method::None => {
                method = Method::GET;
                let mut uri = text[0..text.len() - 1].concat();
                if !uri.starts_with("/") {
                    uri = format!("/{}", uri);
                }
                br_crypto::encoding::urlencoding_decode(uri.as_str())
            }
            _ => {
                let uri = text[1..text.len() - 1].concat();
                br_crypto::encoding::urlencoding_decode(uri.as_str())
            }
        };

        let mut apps = uri.split("/").collect::<Vec<&str>>();
        apps.retain(|&x| {
            return if x == "" {
                false
            } else {
                true
            };
        });
        let app = if apps.len() > 0 {
            apps[0].to_string()
        } else {
            "api".to_string()
        };

        Self {
            config: config.clone(),
            timestamp,
            datetime,
            header_line: header_line.clone(),
            version: text[text.len() - 1].to_string(),
            method,
            uri,
            headers: data[1..].to_vec(),
            server_ip: "".to_string(),
            client_ip: "".to_string(),
            agent_ip: "".to_string(),
            origin: "".to_string(),
            host: "".to_string(),
            sec_fetch_dest: "".to_string(),
            sec_fetch_mode: "".to_string(),
            sec_fetch_site: "".to_string(),
            connection: "".to_string(),
            user_agent: "".to_string(),
            accept: "".to_string(),
            accept_encoding: "".to_string(),
            accept_language: "".to_string(),
            cache_control: "".to_string(),
            content_length: 0,
            content_body_length: 0,
            referer: "".to_string(),
            content_type: RequestContentType::None,
            boundary: "".to_string(),
            custom: object! {},
            query: "".to_string(),
            post: object! {},
            get: object! {},
            files: object! {},
            authorization_mode: "".to_string(),
            authorization_value: "".to_string(),
            is_origin: false,
            app,
        }
    }
    pub fn json(&mut self) -> JsonValue {
        let mut res = object! {};
        res["header_line"] = self.header_line.clone().into();
        res["version"] = self.version.clone().into();
        res["method"] = self.method.clone().str().into();
        res["timestamp"] = self.timestamp.clone().into();
        res["datetime"] = self.datetime.clone().into();

        res["domain"] = self.config.domain.clone().into();

        res["uri"] = self.uri.clone().into();
        res["app"] = self.app.clone().into();

        res["origin"] = self.origin.clone().into();
        res["host"] = self.host.clone().into();
        res["user_agent"] = self.user_agent.clone().into();
        res["content_type"] = self.content_type.clone().str().into();
        res["content_length"] = self.content_length.clone().into();

        res["server_ip"] = self.server_ip.clone().into();
        res["client_ip"] = self.client_ip.clone().into();
        res["agent_ip"] = self.agent_ip.clone().into();

        res["query"] = self.query.clone().into();
        res["custom"] = self.custom.clone().into();
        res["post"] = self.post.clone();
        res["get"] = self.get.clone();
        res["files"] = self.files.clone();
        res["authorization_mode"] = self.authorization_mode.clone().into();
        res["authorization_value"] = self.authorization_value.clone().into();

        res["public"] = self.config.public.clone().into();
        res["runtime"] = self.config.runtime.clone().into();

        res
    }
    pub fn header(&mut self) -> &mut Self {
        self.headers.retain(|x| {
            let keys = x.trim().split(": ").collect::<Vec<&str>>();
            let key = keys[0].to_lowercase().clone();
            let value = keys[1..].join("");
            match key.as_str() {
                "host" => self.host = value,
                "sec-fetch-site" => self.sec_fetch_site = value,
                "sec-fetch-mode" => self.sec_fetch_mode = value,
                "sec-fetch-dest" => self.sec_fetch_dest = value,
                "connection" => self.connection = value,
                "user-agent" => self.user_agent = value,

                "accept" => self.accept = value,
                "accept-language" => {
                    let language = value.split(",").collect::<Vec<&str>>();
                    if language.len() > 1 {
                        self.accept_language = language[0].to_string();
                    } else {
                        self.accept_language = value;
                    }
                }
                "accept-encoding" => self.accept_encoding = value.split(",").collect::<Vec<&str>>().join(";"),

                "cache-control" => self.cache_control = value,
                "content-type" => {
                    match value {
                        _ if value.contains("multipart/form-data") => {
                            let boundary: Vec<&str> = value.split("boundary=").collect();
                            self.boundary = boundary[1..].join("").to_string();
                            self.content_type = RequestContentType::from("multipart/form-data");
                        }
                        _ => {
                            self.content_type = RequestContentType::from(value.as_str());
                        }
                    }
                }
                "content-length" => self.content_length = value.parse::<usize>().unwrap_or(0),
                "origin" => self.origin = value,
                "referer" => self.referer = value,
                "authorization" => {
                    let mode = value.split(" ").collect::<Vec<&str>>();
                    self.authorization_mode = mode[0].to_lowercase();
                    self.authorization_value = mode[1..].concat();
                    match self.authorization_mode.as_str() {
                        "basic" => {
                            self.authorization_value = br_crypto::base64::decode(self.authorization_value.clone());
                        }
                        _ => {}
                    }
                }
                _ => {
                    if !key.starts_with("x-") {
                        return true;
                    }
                    match &key[2..] {
                        "real-ip" => {
                            self.client_ip = value;
                        }
                        // 代理IP
                        "forwarded-for" => {
                            self.agent_ip = value;
                        }
                        _ => {
                            self.custom[&key[2..]] = value.into();
                        }
                    }
                    return false;
                }
            }
            return false;
        });
        self.query();
        self.record_log();
        self.allow_origin();
        self.headers = vec![];
        self
    }
    fn query(&mut self) {
        if !self.uri.contains("?") {
            return;
        }
        let arr = self.uri.split("?").collect::<Vec<&str>>();
        let str = arr[1..].concat();
        let params = if str.contains("&") {
            str.split("&").collect::<Vec<&str>>()
        } else {
            vec![str.as_str()]
        };
        for param in params {
            let res = param.split("=").collect::<Vec<&str>>();
            let key = res[0];
            let value = res[1..].concat();
            let key = br_crypto::encoding::urlencoding_decode(key);
            let value = br_crypto::encoding::urlencoding_decode(value.as_str());
            self.get[key] = value.into();
        }
    }
    /// 请求日志记录
    fn record_log(&mut self) {
        let text = format!("[{}] client-ip: {} content-type: {} content-length: {} {}\r\n", self.datetime.clone(), self.client_ip.clone(), self.content_type.clone().str(), self.content_length.clone(), self.header_line.clone());
        let dir_path = format!("{}/log/{}", self.config.runtime, self.datetime[0..10].replace("-", "/").to_string());
        let path = format!("{}/{}.log", dir_path.clone(), self.datetime[11..13].to_string());
        thread::spawn(move || {
            let _ = fs::create_dir_all(dir_path.clone());
            let mut file = OpenOptions::new()
                .read(true)
                .write(true)
                .create(true)
                .append(true)
                .open(path).unwrap();
            let _ = file.write_all(text.as_bytes());
        });
    }
    pub fn body(&mut self, request: Vec<u8>) {
        self.content_body_length += request.len();
        if self.content_length > self.content_body_length {
            return;
        }
        match self.content_type {
            RequestContentType::FormData => {
                let request = unsafe { String::from_utf8_unchecked(request.clone()) };
                let mut request = request.trim_end_matches(format!("--{}--\r\n", self.boundary).as_str()).split(format!("--{}\r\n", self.boundary).as_str()).collect::<Vec<&str>>();
                request.remove(0);
                for item in request.iter_mut() {
                    if item.contains("filename=") {
                        let text: Vec<&str> = item.split("\r\n\r\n").collect();
                        let temp = if text.len() > 2 {
                            text[1..].join("\r\n\r\n")
                        } else {
                            text[1].to_string()
                        };
                        let body = match temp.strip_suffix("\r\n") {
                            None => temp.as_str(),
                            Some(v) => v
                        };
                        let text: Vec<&str> = text[0].split("\r\n").collect();
                        let name: Vec<&str> = text[0].split("\"").collect();
                        let types: Vec<&str> = text[1].split(": ").collect();
                        let accept = types[1];
                        let field = name[1];
                        let filename = name[3];

                        let mut file_data = object! {};
                        file_data["accept"] = accept.into();
                        file_data["name"] = filename.into();
                        file_data["md5"] = br_crypto::hash::str_to_md5(body).into();
                        file_data["suffix"] = ContentType::from(accept).suffix.into();
                        file_data["types"] = ContentType::from_suffix(file_data["suffix"].as_str().unwrap()).suffix_type_name().into();
                        file_data["size"] = body.len().into();
                        file_data["data"] = br_crypto::base64::encode_file(body.as_bytes().into()).into();
                        if self.files[field].is_empty() {
                            self.files[field] = array![file_data.clone()];
                        } else {
                            self.files[field].push(file_data.clone()).unwrap();
                        }
                    } else {
                        let re = item.split("\r\n\r\n").collect::<Vec<&str>>();
                        let name = re[0].trim_end_matches("\"").split("name=\"").collect::<Vec<&str>>();
                        self.post[name[1]] = re[1].trim_end_matches("\r\n").into();
                    }
                }
            }
            RequestContentType::FOrmUrlencoded => {
                let request = unsafe { String::from_utf8_unchecked(request.clone()) };
                let params = if request.contains("&") {
                    request.split("&").collect::<Vec<&str>>()
                } else {
                    vec![request.as_str()]
                };
                for param in params {
                    let res = param.split("=").collect::<Vec<&str>>();
                    let key = res[0];
                    let value = res[1..].concat();
                    let key = br_crypto::encoding::urlencoding_decode(key);
                    let value = br_crypto::encoding::urlencoding_decode(value.as_str());
                    self.post[key] = value.into();
                }
            }
            RequestContentType::Javascript => {
                let request = unsafe { String::from_utf8_unchecked(request.clone()) };
                if self.config.debug {
                    info!("Javascript: {}", request);
                }
            }
            RequestContentType::Json => {
                let request = unsafe { String::from_utf8_unchecked(request.clone()) };
                match json::parse(request.as_str()) {
                    Ok(e) => {
                        self.post = e;
                    }
                    Err(_) => {
                        error!("解析json失败,请检查格式:{}", request);
                        self.post = object! {}
                    }
                };
            }
            RequestContentType::Xml => {
                let request = unsafe { String::from_utf8_unchecked(request.clone()) };
                let res = br_xml::Xml::xml_json(request);
                self.post = res;
            }
            RequestContentType::Text => {
                let request = unsafe { String::from_utf8_unchecked(request.clone()) };
                if self.config.debug {
                    info!("Text: {}", request);
                }
            }
            RequestContentType::Html => {
                let request = unsafe { String::from_utf8_unchecked(request.clone()) };
                if self.config.debug {
                    info!("Html: {}", request);
                }
            }
            RequestContentType::None => {
                let request = unsafe { String::from_utf8_unchecked(request.clone()) };
                if self.config.debug {
                    info!("RequestContentType 未知: {}", request);
                }
            }
        }
    }

    /// 源判断
    fn allow_origin(&mut self) {
        self.is_origin = false;
        if self.config.cors.allow_origin.len() > 0 {
            if self.origin.is_empty() {
                for origin in &self.config.cors.allow_origin {
                    if self.client_ip.contains(&*origin) {
                        self.is_origin = true;
                    }
                }
            } else {
                for origin in &self.config.cors.allow_origin {
                    if self.origin.contains(&*origin) {
                        self.is_origin = true;
                    }
                }
            }
        } else {
            self.is_origin = true;
        }
    }
}

/// 请求方法
#[derive(Clone, Debug)]
pub enum Method {
    POST,
    GET,
    HEAD,
    PUT,
    DELETE,
    OPTIONS,
    PATCH,
    TRACE,
    None,
}

impl Method {
    pub fn from(name: &str) -> Self {
        match name.to_lowercase().as_str() {
            "post" => Self::POST,
            "get" => Self::GET,
            "head" => Self::HEAD,
            "put" => Self::PUT,
            "delete" => Self::DELETE,
            "options" => Self::OPTIONS,
            "patch" => Self::PATCH,
            "trace" => Self::TRACE,
            _ => Self::None,
        }
    }
    pub fn str(self) -> &'static str {
        match self {
            Method::POST => "post",
            Method::GET => "get",
            Method::HEAD => "head",
            Method::PUT => "put",
            Method::DELETE => "delete",
            Method::OPTIONS => "options",
            Method::PATCH => "patch",
            Method::TRACE => "trace",
            Method::None => ""
        }
    }
}

#[derive(Clone, Debug)]
pub enum RequestContentType {
    FormData,
    FOrmUrlencoded,
    Json,
    Xml,
    Javascript,
    Text,
    Html,
    None,
}

impl RequestContentType {
    pub fn from(name: &str) -> Self {
        match name {
            "multipart/form-data" => Self::FormData,
            "application/x-www-form-urlencoded" => Self::FOrmUrlencoded,
            "application/json" => Self::Json,
            "application/xml" | "text/xml" => Self::Xml,
            "application/javascript" => Self::Javascript,
            "text/html" => Self::Html,
            "text/plain" => Self::Text,
            _ => {
                info!("未知请求类型: {}", name);
                Self::None
            }
        }
    }
    pub fn str(self) -> &'static str {
        match self {
            RequestContentType::FormData => "multipart/form-data",
            RequestContentType::FOrmUrlencoded => "application/x-www-form-urlencoded",
            RequestContentType::Json => "application/json",
            RequestContentType::Xml => "application/xml",
            RequestContentType::Javascript => "application/javascript",
            RequestContentType::Text => "text/plain",
            RequestContentType::Html => "text/html",
            RequestContentType::None => ""
        }
    }
}