use std::sync::Arc;
use casbin::CoreApi;
use axum::{
    extract::{Request,ConnectInfo},
    http::StatusCode,
    Extension,
    response::{IntoResponse,Response,Redirect},
    middleware::Next, 
};
use tower_cookies::{Cookie, Cookies};

use crate::{
    USER_AGENT,
    SESSION_COOKIE_NAME,
    defs::{
        AppDBs,
        ServPath,
        AuthState,
        SessionStoreDB,
        TraceData,
        TraceContent,
        ReqHeaderMap,
        AppConnectInfo,
    },
};

/* // OLD get_cookie from Request 
pub fn get_cookie(req: &Request) -> Option<u128> {
    req
        .headers()
        .get_all("Cookie")
        .iter()
        .filter_map(|cookie| {
            cookie
                .to_str()
                .ok()
                .and_then(|cookie| cookie.parse::<Cookie>().ok())
        })
        .find_map(|cookie| {
            (cookie.name() == SESSION_COOKIE_NAME).then(move || cookie.value().to_owned())
        })
        .and_then(|cookie_value| cookie_value.parse::<u128>().ok())
}
*/

pub async fn add_session_cookie(make: bool, cookies: &Cookies, session_token: &str, user_data: &str, expire: u64, app_dbs: &AppDBs, cookie_path: &str) -> String {
    if make {
        cookies.remove(Cookie::new(SESSION_COOKIE_NAME, ""));
    }
    let result_store = SessionStoreDB::store_session_data(&session_token,&user_data, expire, &app_dbs).await;
    if result_store.is_empty() {
        eprintln!("Unable to store session {}", &app_dbs.config.session_store_uri);
    } else {
        let cookie = Cookie::build(SESSION_COOKIE_NAME, result_store.to_owned())
        //  .domain(domain)
            .path(format!("{}",cookie_path))
            .secure(true)
            .http_only(true)
            .finish();
        if make {
            cookies.add(cookie);
        }
    }
    result_store
}
pub async fn get_auth_state(update: bool, cookies: &Cookies, app_dbs: &AppDBs) -> AuthState {
    if let Some(s_cookie) = cookies.get(SESSION_COOKIE_NAME) {
        let session_cookie = s_cookie.to_string().replace(&format!("{}=",SESSION_COOKIE_NAME),"");
        let mut auth_state = AuthState::from_cookie(session_cookie.to_string(), app_dbs).await;
        if update {
            let _ = auth_state.expire_in(app_dbs.config.session_expire, &app_dbs).await;
        }
        auth_state
    } else {
        // eprintln!("get_auth_state: No SESSION COOKIE found ");
        AuthState::default()
    }
}
pub fn trace_req(uri_path: &str, user_id: String, sid: String, info: String, context: String, role: String, req_header: ReqHeaderMap, app_dbs: &AppDBs) -> std::io::Result<()> {
    let timestamp = chrono::Utc::now().timestamp().to_string();
    let trace_content = TraceContent{
        when: timestamp.to_owned(),
        sid,
        origin: uri_path.to_owned(),
        trigger: String::from("req_handler"),
        id: user_id.to_owned(),
        info: info.to_owned(),
        context,
        role,
        req: req_header.req_info(),
    };
    let trace_data = TraceData{
        user_id,
        timestamp,
        contents: vec![trace_content]
    };
    trace_data.save(&app_dbs.config,false)
}

pub async fn rewrite_request_uri(
    Extension(app_dbs): Extension<Arc<AppDBs>>,
    Extension(cookies): Extension<Cookies>,
    ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
    req: Request, next: Next,
) -> Result<impl IntoResponse, Response> {
    // TODO Trace acccess to log or user session file !!!
    let auth_state = get_auth_state(true, &cookies, &app_dbs).await;
    let uri_path = req.uri().path().to_owned();
    if uri_path == "/" {
        return Ok(next.run(req).await);
    }
    // For long path is better than:
    // let arr_root_path: Vec<String> = uri_path.split("/").map(|s| s.to_string()).collect();
    // let root_path = arr_root_path[1].to_owned();
    let mut root_path = String::from("/");
    for it in uri_path.split("/") {
        if ! it.is_empty() {  
            root_path = format!("/{}",it.to_owned());
            break;
        }
    }
    let serv_paths: Vec<ServPath> = app_dbs.config.serv_paths.clone().into_iter().filter(
        |it| it.is_restricted && it.url_path == root_path
    ).collect();
    // Only on First one
    if serv_paths.len() > 0 {
        let serv_path = serv_paths[0].to_owned();
        let name = auth_state.user_name();
        if name.is_empty() {
            let uri_path = req.uri().path().to_string();
            if uri_path.ends_with(".html") {
                eprintln!("rewrite_request_uri: No user found in session for {}", &uri_path);
                let new_uri = format!("{}?o={}",&serv_path.not_auth.as_str(),req.uri().path().to_string());
                let _ = trace_req(&uri_path, auth_state.user_id(),auth_state.id(),
                    format!("user no name found in session"), String::from("rewrite_request_uri"),
                    auth_state.user_roles(),
                    ReqHeaderMap::new(req.headers().to_owned(), &uri_path, &app_connect_info),
                    &app_dbs);
                return Err(
                    Redirect::temporary( &new_uri).into_response()
                );
            } else {
                return Ok(next.run(req).await);
            } 
        }
        let arr_roles: Vec<String> = auth_state.user_roles().split(",").map(|s| s.replace(" ", "").to_string()).collect();
        let req_method = req.method().to_string();
        let target_path = serv_path.url_path.to_owned();
        let enforcer = app_dbs.enforcer.clone();
        for role in arr_roles {
            let mut lock = enforcer.write().await;
            let result = lock.enforce_mut(
                vec![role.to_owned(),target_path.to_owned(), req_method.to_owned()]
            ).unwrap_or_else(|e|{
                println!("Error enforce: {}",e);
                false
            });
            drop(lock); 
            if result { 
                if uri_path.ends_with(".html") || app_dbs.config.trace_level > 1  {
                    let _ = trace_req(&uri_path,auth_state.user_id(),auth_state.id(),
                    format!("user in session with role {}",role),
                    String::from("rewrite_request_uri"),
                    auth_state.user_roles(),
                    ReqHeaderMap::new(req.headers().to_owned(), &uri_path, &app_connect_info),
                    &app_dbs);
                }
                return Ok(next.run(req).await); 
            }
        }
        // try with email
        let mut lock = enforcer.write().await;
        let result = lock.enforce_mut(
           vec![
                name,
                target_path.to_owned(),
                req_method.to_owned()
            ]
        ).unwrap_or_else(|e|{
            println!("Error enforce: {}",e);
            false
        });
        drop(lock); 
        if result { return Ok(next.run(req).await); }
        let new_uri = format!("{}",serv_path.not_auth);
        let agent = if let Some(user_agent) = req.headers().get(USER_AGENT) {
           user_agent.to_str().unwrap_or("").to_owned()
        } else {
            String::from("")
        };
        if uri_path.ends_with(".html") || app_dbs.config.trace_level > 1  {
            let _ = trace_req(&uri_path,auth_state.user_id(),auth_state.id(),
                format!("user found in session"),
                String::from("rewrite_request_uri"),
                auth_state.user_roles(),
                ReqHeaderMap::new(req.headers().to_owned(), &uri_path, &app_connect_info),
                &app_dbs);
        }
        if agent.contains("curl") {
            return Ok(
                format!("Got to {}",&new_uri).into_response()
            );
        } else {
            return Err(
                Redirect::temporary(&new_uri).into_response()
            );
        }
    } 
    if uri_path.ends_with(".html") || app_dbs.config.trace_level > 1  {
        let _ = trace_req(&uri_path,auth_state.user_id(),auth_state.id(),
        format!("user in session"),
        String::from("rewrite_request_uri"),
        auth_state.user_roles(),
        ReqHeaderMap::new(req.headers().to_owned(), &uri_path, &app_connect_info),
        &app_dbs);
    }
    Ok(next.run(req).await)
}
pub async fn handle_404(_req: Request) -> (StatusCode, &'static str) {
    (StatusCode::NOT_FOUND, "Not found")
}

