
use std::{
    net::IpAddr,
    collections::HashMap,
};
use axum::http::{header::FORWARDED, HeaderMap};
use forwarded_header_value::{ForwardedHeaderValue, Identifier};

use crate::defs::{
    Config,
    AppConnectInfo,
    AUTHORIZATION,
    REFERER,
    BEARER,
    USER_AGENT,
    ACCEPT_LANGUAGE,
    X_FORWARDED_FOR,
    X_REAL_IP,
    X_INTERNAL,
};


pub struct ReqHeaderMap {
    pub header: HeaderMap,
    pub req_path: Vec<String>,    
    pub app_connect_info: AppConnectInfo,
}

impl ReqHeaderMap {
    pub fn new(header: HeaderMap, request_path: &str, app_connect_info: &AppConnectInfo) -> Self {
        let req_path = 
            Self::req_end_path(request_path)
            .split(",").map(|s| s.to_string())
            .collect();
        ReqHeaderMap {
            header,
            req_path,
            app_connect_info: app_connect_info.to_owned(),
        }
    }
    pub fn req_end_path(req_path: &str) -> String {
        let arr_req_path: Vec<String> = req_path.split("/").map(|s| s.to_string()).collect();
        format!("{}",arr_req_path[arr_req_path.len()-1])
    } 
    
    pub fn auth(&self) -> String {
        if let Some(auth) = self.header.get(AUTHORIZATION) {
            format!("{}",auth.to_str().unwrap_or("").replace(&format!("{} ", BEARER),""))
        } else { 
            String::from("")
        }
    }
    pub fn referer(&self) -> String {
        if let Some(referer) = self.header.get(REFERER) {
            format!("{}",referer.to_str().unwrap_or(""))
        } else { 
            String::from("")
        }
    }
    pub fn internal(&self) -> String {
        if let Some(internal) = self.header.get(X_INTERNAL) {
            format!("{}",internal.to_str().unwrap_or(""))
        } else { 
            String::from("")
        }
    }
    pub fn is_browser(&self) -> bool {
        if let Some(user_agent) = self.header.get(USER_AGENT) {
            let agent = user_agent.to_str().unwrap_or(""); 
            if agent.contains("Mozilla") { 
                true
            } else if agent.contains("WebKit") {
                true
            } else if agent.contains("Chrome") {
                true
            } else {
                false
            }
        } else { 
            false
        } 
    }
    #[allow(dead_code)]
    pub fn is_curl(&self) -> bool {
        if let Some(user_agent) = self.header.get(USER_AGENT) {
            let agent = user_agent.to_str().unwrap_or(""); 
            if agent.contains("curl") { 
                true
            } else {
                false
            }
        } else { 
            false
        } 
    }
    pub fn is_wget(&self) -> bool {
        if let Some(user_agent) = self.header.get(USER_AGENT) {
            let agent = user_agent.to_str().unwrap_or("").to_lowercase(); 
            if agent.contains("wget") { 
                true
            } else {
                false
            }
        } else { 
            false
        } 
    }
    #[allow(dead_code)]
    pub fn response_user_agent_html(&self) -> bool {
        if let Some(user_agent) = self.header.get(USER_AGENT) {
            let agent = user_agent.to_str().unwrap_or("").to_lowercase(); 
            agent.contains("curl") || agent.contains("wget") 
        } else {
            false
        }
    }
    pub fn agent(&self) -> String {
        if let Some(user_agent) = self.header.get(USER_AGENT) {
            user_agent.to_str().unwrap_or("").to_owned()
        } else {
            String::from("")
        }
    }
    pub fn lang(&self, config: &Config) -> String {
        if let Some(langs) = self.header.get(ACCEPT_LANGUAGE) {
            let langs_data = langs.to_str().unwrap_or("");
            if langs_data.is_empty() {
                format!("{}",config.dflt_lang)
            } else {
                let arr_langs: Vec<String> = langs_data.split(",").map(|s| s.to_string()).collect();
                let arr_lang: Vec<String> = arr_langs[0].split("-").map(|s| s.to_string()).collect();
                format!("{}",arr_lang[0])
            }
        } else {
            format!("{}",config.dflt_lang)
        }
    }
    /// Tries to parse the `x-real-ip` header
    fn maybe_x_forwarded_for(&self) -> Option<IpAddr> {
        self.header
            .get(X_FORWARDED_FOR)
            .and_then(|hv| hv.to_str().ok())
            .and_then(|s| s.split(',').find_map(|s| s.trim().parse::<IpAddr>().ok()))
    }

    /// Tries to parse the `x-real-ip` header
    fn maybe_x_real_ip(&self) -> Option<IpAddr> {
        self.header
            .get(X_REAL_IP)
            .and_then(|hv| hv.to_str().ok())
            .and_then(|s| s.parse::<IpAddr>().ok())
    }

    /// Tries to parse `forwarded` headers
    fn maybe_forwarded(&self) -> Option<IpAddr> {
        self.header
            .get_all(FORWARDED).iter().find_map(|hv| {
                hv.to_str()
                    .ok()
                    .and_then(|s| ForwardedHeaderValue::from_forwarded(s).ok())
                    .and_then(|f| {
                        f.iter()
                            .filter_map(|fs| fs.forwarded_for.as_ref())
                            .find_map(|ff| match ff {
                                Identifier::SocketAddr(a) => Some(a.ip()),
                                Identifier::IpAddr(ip) => Some(*ip),
                                _ => None,
                            })
                    })
        })
    }
    pub fn ip(&self) -> String {
        if let Some(ip) = self.maybe_x_real_ip()
            .or_else(||self.maybe_x_forwarded_for())
            .or_else(|| self.maybe_forwarded())
            .or_else(|| None )
        {
            format!("{}",ip)
        } else {
            format!("{}", self.app_connect_info.remote_addr)
        }
    }
    pub fn req_info(&self) -> HashMap<String,String> {
        let mut req_data: HashMap<String,String> = HashMap::new();
        req_data.insert(String::from("agent"), self.agent());
        req_data.insert(String::from("ip"), self.ip());
        req_data.insert(String::from("auth"), self.auth());
        req_data.insert(String::from("ref"), self.referer());
        req_data.insert(String::from("int"), self.internal());
        req_data
    }
}    