use std::collections::HashMap;
use std::sync::Arc;
use axum::{
    http::{
        StatusCode,
        Uri,
        header::HeaderMap,
    },
    Json,
    routing::{get,post},
    Extension,
    extract::ConnectInfo,
    response::{IntoResponse,Response,Redirect},
    Router,
};
use tower_cookies::{Cookie, Cookies};

use crate::{
    SESSION_COOKIE_NAME,
    DEFAULT_ROLES,
    route,
    defs::{
        AppDBs,
        AuthState,
        SessionStoreDB,
        ReqHandler,
        ReqHeaderMap,
        TotpMode,
        Random,
        AppConnectInfo,
        OpenidData,
        OpenidCli,
    },
    users::{
        User,
        UserData,
        UserLogin,
        UserItem,
        UserStatus,
    },
    login_password::generate_hash,
    handlers::{
        add_session_cookie,
        get_auth_state,
        login_user,
    },
};
pub fn users_login_router_handlers() -> Router {
    async fn signup_handler(
        header: HeaderMap,
        uri: Uri,
        Extension(app_dbs): Extension<Arc<AppDBs>>,
        Extension(cookies): Extension<Cookies>,
        Extension(random): Extension<Random>,
        ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
    ) -> Response {
        let auth_state = get_auth_state(true, &cookies, &app_dbs).await;
        let mut req_handler = ReqHandler::new(
            ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info),
            &app_dbs,
            &uri,
            &auth_state,
            &random,
            "signup_handler"
        );
        if !auth_state.is_admin() {
           let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{
                println!("Count error: {}",e);
                0
            });
            if ! app_dbs.config.signup_mode.contains("open") && total_users != 0 {
                let msg = format!("Config signup mode not open: {}", app_dbs.config.signup_mode);
                println!("{}",&msg);
                let _ = req_handler.trace_req(msg);
                return Redirect::temporary( &format!("/")).into_response();
            }
            let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{
                println!("Count error: {}",e);
                -1
            });
            // Fake to insert firt time admin user ...
            if total_users < 1  {
                let isadmin= String::from("TRUE");
                req_handler.context.insert("isadmin", &isadmin);
            }
        }
        let mut res_headers = HeaderMap::new();
        if req_handler.req_header.is_browser() {
            res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap());
        }
        req_handler.context.insert("password_score", &app_dbs.config.password_score);
        req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode));
        // req_handler.context.insert("with_menu", "1");
        if app_dbs.config.totp_mode != TotpMode::No {
            match req_handler.otp_generate() {
                Ok(totp) => {
                    req_handler.context.insert("otp_code", &totp.get_secret_base32());
                    req_handler.context.insert("otp_url", &totp.get_url());
                    req_handler.context.insert("otp_qr", &totp.get_qr_base64().unwrap_or_default());
                    req_handler.context.insert("totp_digits", &app_dbs.config.totp_digits);
                    req_handler.context.insert("totp_algorithm",&format!("{}",&app_dbs.config.totp_algorithm));
                },
                Err(e) => {
                    println!("Error TOTP generartor: {}",e);
                }
            }
        }
        req_handler.context.insert("admin_fields", &app_dbs.config.admin_fields);
        let result = if let Some(tpl) = app_dbs.config.tpls.get("signup") {
            req_handler.render_template(&tpl,"signup")
        } else {
            String::from("signup")
        };
        let _ = req_handler.trace_req(format!("Signup request"));
        (
            res_headers,
            result.to_owned()
        ).into_response()
    }
    
    async fn post_signup_handler(
        header: HeaderMap,
        uri: Uri,
        Extension(app_dbs): Extension<Arc<AppDBs>>,
        Extension(random): Extension<Random>,
        Extension(cookies): Extension<Cookies>,
        ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
        Json(user_data): Json<UserData>,
    ) -> Response {
        let auth_state = get_auth_state(true, &cookies, &app_dbs).await;
        let req_handler = ReqHandler::new(
            ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info),
            &app_dbs,
            &uri,
            &auth_state,
            &random,
            "post_signup_handler"
        );
        if user_data.name.is_empty() {
            let _ = req_handler.trace_req(format!("No name found"));
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error"
            ).into_response();
        }
        if user_data.password.is_empty() {
            let _ = req_handler.trace_req(format!("user '{}' no password found",&user_data.name));
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error"
            ).into_response();
        }
        let passwd_score = User::password_score(&user_data.password);
        if passwd_score < app_dbs.config.password_score {
            let _ = req_handler.trace_req(format!("User '{}' password '{}' score: {} under {}"
                ,&user_data.name,&user_data.password, passwd_score,app_dbs.config.password_score)
            );
            return (
                StatusCode::UNAUTHORIZED,
                req_handler.req_header.header,
                "Error"
            ).into_response();
        }
        let new_hash = generate_hash(&user_data.password);
        // println!("password: {}", &new_hash);
        // // if  user_data.password.is_empty() {
        // //     return Err(error_page(&SignupError::MissingDetails));
        // // }
        // match verify_password(&user_data.password, &new_hash) {
        //     Ok(_) => { 
        //         println!("Is valid!");
        //     },
        //     Err(e) => {
        //         println!("NOT valid {}",e);
        //         //return Err(error_page(&SignupError::PasswordsDoNotMatch))
        //     }
        // }
        let otp_enabled: bool;
        let otp_verified: bool;
        let otp_base32: String;
        let otp_auth_url: String;
        let otp_defs: String;
        if app_dbs.config.totp_mode != TotpMode::No && !user_data.otp_auth.is_empty() {
            match req_handler.otp_check(&user_data.otp_code,&user_data.otp_auth, "") {
                Ok(val) => {
                    if val {
                        otp_enabled = true;
                    } else {
                        let _ = req_handler.trace_req(format!("User '{}' not valid TOTP code",&user_data.name));
                        // otp_enabled = false;
                        return (
                            StatusCode::UNAUTHORIZED,
                            req_handler.req_header.header,
                            "Error"
                        ).into_response();
                    }
                },
                Err(e) => {
                    println!("TOTP check: {}", e);
                    let _ = req_handler.trace_req(format!("User '{}' TOTP check error: {}",&user_data.name,e));
                    return (
                        StatusCode::UNAUTHORIZED,
                        req_handler.req_header.header,
                        "Error"
                    ).into_response();
                }
            }
        } else {
            otp_enabled = false;
        }
        if otp_enabled  {
            otp_verified= true;
            otp_base32 = user_data.otp_code.to_owned();
            otp_auth_url = user_data.otp_url.to_owned();
            otp_defs = format!("{},{}",
                &app_dbs.config.totp_digits,
                format!("{}",&app_dbs.config.totp_algorithm),
            );
        } else {
            otp_verified = false;
            otp_base32 = String::from("");
            otp_auth_url = String::from("");
            otp_defs = String::from("");
        }
        let roles = if ! user_data.roles.is_empty() {
            user_data.roles.to_owned()
        } else {
            DEFAULT_ROLES.to_owned()
        };
        let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{
           let _ = req_handler.trace_req( format!("Users count error: {}",e));
            println!("Count error: {}",e);
            -1
        });
        let isadmin = if user_data.id == "A" || total_users < 1 {
            true
        } else {
            false
        };
        let mut new_items = user_data.items.to_owned();
        new_items.remove("invite_key");
        new_items.remove("invite_id");
        let user = User{
            id: 0,
            name: user_data.name.to_owned(),
            fullname: user_data.name.to_owned(),
            email:   user_data.email.to_owned(),
            description: user_data.description.to_owned(),
            password: new_hash,
            otp_enabled,
            otp_verified,
            otp_base32,
            otp_auth_url,
            otp_defs,
            created: chrono::Utc::now().timestamp().to_string(),
            lastaccess: String::from(""),
            status: UserStatus::Created,
            items: User::json_items(new_items),
            isadmin,
            roles,
        };
        let usr_sel = User::select("name", &user_data.name, false, &app_dbs.user_store).await.unwrap_or_default();
        if usr_sel.name == user_data.name {
            // User already exists
            let _ = req_handler.trace_req(format!("User 'name' = '{}' already exists",&user_data.name));
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error"
            ).into_response();
        }
        let str_user_data =  match user.add(&app_dbs.user_store).await {
            Ok(id) => {
                println!("user {} created -> {}", &user_data.name, id);
                format!("{}|{}|{}",id, &user_data.name, &user_data.roles)
            },
            Err(e) => {
                let _ = req_handler.trace_req(format!("User '{}' create error: {}",&user_data.name,e));
                println!("user {} error -> {:#}", &user_data.name, e);
                return (
                    StatusCode::NOT_FOUND,
                    req_handler.req_header.header,
                    "Error"
                ).into_response();
            }
        };
        let session_token = req_handler.new_token();
        let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await;
        let _ = req_handler.trace_req(format!("User '{}' created",&user_data.name));
        if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie); }
        (
            req_handler.req_header.header,
            "Ok"
         ).into_response()
    }
    async fn login_handler(
        header: HeaderMap,
        uri: Uri,
        Extension(app_dbs): Extension<Arc<AppDBs>>,
        Extension(cookies): Extension<Cookies>,
        Extension(random): Extension<Random>,
        ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
    ) -> Response {
        SessionStoreDB::cleanup_data(&app_dbs).await;
        let auth_state = get_auth_state(true, &cookies, &app_dbs).await;
        dbg!(&header);
        dbg!(&auth_state);
        let mut req_handler = ReqHandler::new(
            ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info),
            &app_dbs,
            &uri,
            &auth_state,
            &random,
            "login_handler"
        );
        let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{
           let _ = req_handler.trace_req( format!("Users count error: {}",e));
            println!("Count error: {}",e);
            -1
        });
        if total_users < 1 {
           let _ = req_handler.trace_req(String::from("No users found"));
          return Redirect::temporary( &format!("/signup")).into_response();
        }
        let user_data = auth_state.user_data();
        if user_data.len() > 4  && user_data[0] == "openid" {
            req_handler.context.insert("login_text", &format!("Login {} / {}", &user_data[1], &user_data[5]));
            req_handler.context.insert("signin_target", &user_data[1]);
        }
        let mut res_headers = HeaderMap::new();
        if req_handler.req_header.is_browser() {
            res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap());
        }
        req_handler.context.insert("openid_auths", &app_dbs.config.openid_auths.clone().into_keys().collect::<Vec<String>>());
        // req_handler.context.insert("with_menu", "1");
        req_handler.context.insert("use_mail", &app_dbs.config.use_mail);
        req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode));
        if app_dbs.config.totp_mode != TotpMode::No {
            req_handler.context.insert("with_totp", "1");
            req_handler.context.insert("totp_digits", &app_dbs.config.totp_digits);
        }
        let _ = req_handler.trace_req(String::from("login request"));
        let result = if let Some(tpl) = app_dbs.config.tpls.get("login") {
            req_handler.render_template(&tpl,"login")
        } else {
            String::from("login")
        };
        (
            res_headers,
            result.to_owned()
        ).into_response()
    }
    async fn openid_login_handler(
        header: HeaderMap,
        uri: Uri,
        Extension(app_dbs): Extension<Arc<AppDBs>>,
        Extension(cookies): Extension<Cookies>,
        Extension(random): Extension<Random>,
        Extension(openid_clients): Extension<Arc<HashMap<String,OpenidCli>>>,
        ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
        axum::extract::Path(data): axum::extract::Path<String>,
    ) -> Response {
        SessionStoreDB::cleanup_data(&app_dbs).await;
        let auth_state = get_auth_state(true, &cookies, &app_dbs).await;
        let req_handler = ReqHandler::new(
            ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info),
            &app_dbs,
            &uri,
            &auth_state,
            &random,
            "login_handler"
        );
        let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{
            let _ = req_handler.trace_req( format!("Users count error: {}",e));
            println!("Count error: {}",e);
            -1
        });
        if total_users < 1 {
            let _ = req_handler.trace_req(String::from("No users found"));
            return Redirect::temporary( &format!("/signup")).into_response();
        }
        if data.is_empty() {
            return Redirect::temporary( &format!("/login")).into_response();
        }
        let (str_user_data,url) = if let Some(openid_conf) = app_dbs.config.openid_auths.get(&data) {
           if let Some(openid_cli) = openid_clients.get(&data) {
               match openid_conf.get_auth(openid_cli).await {
                   Ok(openid) => {
                       //let _ = req_handler.trace_req(format!("User '{}' created",&user_data.name));
                       let str_user_data = format!("{}|{}|{}", &data, openid.token.secret(), openid.nonce.secret());
                       //let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await;
                       //if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) };
                       //let _new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await;
                       /*req_handler = ReqHandler::new(
                           req_handler.req_header,
                           &app_dbs,
                           &uri,
                           &new_auth_state,
                           &random,
                           "post_login_handler"
                       );*/
                       //let _ = req_handler.trace_req(format!("user '{}', new token: '{}', cookie: '{}' ",&user_login.name, &session_token, &session_cookie));
                       (str_user_data, format!("{}", openid.url.to_string()))
                   },
                   Err(e) => {
                       println!("Error openid login handler: {}", e);
                       (String::from(""), String::from("/login"))
                   }
               }
           } else {
               (String::from(""), String::from("/login"))
           }
        } else {
            (String::from(""), String::from("/login"))
        };
        let session_token = req_handler.new_token();
        let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await;
        if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) };
        let _new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await;
        return Redirect::temporary(&format!("{}",url)).into_response()
    }
    async fn post_login_handler(
        header: HeaderMap,
        uri: Uri,
        Extension(app_dbs): Extension<Arc<AppDBs>>,
        Extension(cookies): Extension<Cookies>,
        Extension(random): Extension<Random>,
        ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
        Json(user_login): Json<UserLogin>,
    ) -> Response {
       login_user ( header, uri,
           app_dbs, cookies, random,
           app_connect_info,
           user_login,
           "post_login"
       ).await
    }
    async fn openid_from_login_handler(
        header: HeaderMap,
        uri: Uri,
        Extension(app_dbs): Extension<Arc<AppDBs>>,
        Extension(cookies): Extension<Cookies>,
        Extension(random): Extension<Random>,
        ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
        Extension(openid_clients): Extension<Arc<HashMap<String,OpenidCli>>>,
        //axum::extract::Path(data): axum::extract::Path<String>,
        data: axum::extract::Query<OpenidData>,
        // Json(user_login): Json<UserLogin>,
    ) -> Response {
        dbg!(&header);
        dbg!(&uri);
        let auth_state = get_auth_state(true, &cookies, &app_dbs).await;
        let user_data = auth_state.user_data();
        //let _req_handler = ReqHandler::new(
        //    ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info),
        //    &app_dbs,
        //    &uri,
        //    &auth_state,
        //    &random,
        //    "post_login_handler"
        //);
        if user_data.len() < 1 || user_data[1] != data.state {
            println!("State {} not found=", data.state);
            return Redirect::temporary(&format!("/login")).into_response();
        }
        println!("OK State {} == {}", user_data[1], data.state);
        let mut user_login = UserLogin::default();
        if let Some(openid_conf) = app_dbs.config.openid_auths.get(&user_data[0]) {
            if let Some(openid_cli) = openid_clients.get(&user_data[0]) {
                match openid_conf.get_token(openid_cli, data.code.to_owned(), user_data[2].to_owned()).await {
                    Ok(data) => {
                        user_login = UserLogin {
                            name: data.name.to_owned(),
                            password: String::from(""),
                            otp_auth: String::from(""),
                            email: data.email.to_owned(),
                        };
                    },
                    Err(e) => {
                        println!("Error openid login handler: {}", e);
                    }
                }
            }
        }
        if ! user_login.name.is_empty() {
            let res = login_user(header, uri,
               app_dbs, cookies, random,
               app_connect_info,
               user_login,
               "from_login_openid"
            ).await;
            res
        } else {
            let url="/login"; // https://tiicl.tls13.io:8800";
            Redirect::temporary(&format!("{}",url)).into_response()
        }
    }
    async fn logout_handler(
        header: HeaderMap,
        uri: Uri,
        Extension(app_dbs): Extension<Arc<AppDBs>>,
        Extension(cookies): Extension<Cookies>,
        Extension(random): Extension<Random>,
        ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
    ) -> Response {
        let auth_state = get_auth_state(true, &cookies, &app_dbs).await;
        let mut req_handler = ReqHandler::new(
            ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info),
            &app_dbs,
            &uri,
            &auth_state,
            &random,
            "logout_handler"
        );
        let id = auth_state.id();
        let res = auth_state.destroy(); 
        cookies.remove(Cookie::new(SESSION_COOKIE_NAME, ""));
        if app_dbs.config.verbose > 1 { println!("Session: {} destroyed: {}",&id, &res); }
        let _ = req_handler.trace_req(format!("Session '{}' logout",&id));
        let auth_state = AuthState::default();
        req_handler = ReqHandler::new(
            req_handler.req_header,
            &app_dbs,
            &uri,
            &auth_state,
            &random,
            "logout_handler"
        );
        let mut res_headers = HeaderMap::new();
        if req_handler.req_header.is_browser() {
            res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap());
        }
        // these is to use user_items like color_theme 
        // let user_items = User::hash_items(&auth_state.user_items());
        // req_handler.context.insert("usr_items", &user_items);
        req_handler.context.insert("with_menu", "1");
        let result = if let Some(tpl) = app_dbs.config.tpls.get("logout") {
            req_handler.render_template(&tpl,"logout")
        } else {
            String::from("logout")
        };
        (
            res_headers,
            result.to_owned()
        ).into_response()
    }
    async fn check_item_handler(
        header: HeaderMap,
        uri: Uri,
        Extension(app_dbs): Extension<Arc<AppDBs>>,
        Extension(cookies): Extension<Cookies>,
        Extension(random): Extension<Random>,
        ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
        Json(user_item): Json<UserItem>,
    ) -> Response {
        let auth_state = get_auth_state(true, &cookies, &app_dbs).await;
        let req_handler = ReqHandler::new(
            ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info),
            &app_dbs,
            &uri,
            &auth_state,
            &random,
            "logout_handler"
        );
        let result = if user_item.name == "password" {
            let _ = req_handler.trace_req(format!("Password estimate"));
            User::estimate_password(&user_item.value)
        } else  {
            let user_sel = User::select(&user_item.name, &user_item.value, false,&app_dbs.user_store).await.unwrap_or_default();
            if !user_sel.name.is_empty() {
                let _ = req_handler.trace_req(format!("User '{}' = '{}' not found",&user_item.name,&user_item.value));
                // User not exists
                return (
                    StatusCode::BAD_REQUEST,
                    req_handler.req_header.header,
                    "Error"
                ).into_response();
            }
            String::from("OK")
        };
        (
            req_handler.req_header.header,
            result.to_owned()
        ).into_response()
    }
    route(
        "/login", get(login_handler))
        .route("/login", post(post_login_handler))
        
        .route("/openid/:data", get(openid_login_handler))
        .route("/from_openid", get(openid_from_login_handler))

        .route("/signup", get(signup_handler))
        .route("/signup", post(post_signup_handler))

        .route("/logout", get(logout_handler)) 

        .route("/check", post(check_item_handler)) 
}
