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

use crate::{
    route,
    defs::{
        AppDBs,
        AuthState,
        ReqHandler,
        ReqHeaderMap,
        MailMessage,
        TotpMode,
        Random,
        AppConnectInfo,
    },
    users::{
        User,
        UserData,
        UserItem,
        UserStatus,
    },
    login_password::generate_hash,
    handlers::{
        add_session_cookie,
        get_auth_state,
    },
};
pub fn users_password_router_handlers() -> Router {
    async fn post_reset_password_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 mut req_handler = ReqHandler::new(
            ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info),
            &app_dbs,
            &uri,
            &auth_state,
            &random,
            "post_reset_password_handler"
        );
        if ! app_dbs.config.use_mail {
            let _ = req_handler.trace_req(format!("Mail disabled in config, user '{}' password can not be reset",&user_item.name));
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error no service"
            ).into_response();
        }
        if user_item.name.is_empty() {
            let _ = req_handler.trace_req(format!("No user name"));
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error"
            ).into_response();
        }
        let field = if user_item.name.contains("@") {
            "email"
        } else {
            "name"
        };
        let mut user_sel = User::select(&field, &user_item.name, false, &app_dbs.user_store).await.unwrap_or_else(|e|{
            println!("Error select: {}", e);
            User::default()
        });
        if user_sel.name.is_empty() {
            let _ = req_handler.trace_req(format!("User '{}' = '{}' not found",&field,&user_item.name));
            // User not exists
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error data"
            ).into_response();
        }
        if user_sel.status != UserStatus::Active && user_sel.status != UserStatus::Created {
            let _ = req_handler.trace_req(format!("user '{}' in not valid status: {}",&user_item.name, &user_sel.status));
            return (
                StatusCode::UNAUTHORIZED,
                req_handler.req_header.header,
                "Error status"
            ).into_response();
        }
        if app_dbs.config.totp_mode != TotpMode::No {
            if user_sel.otp_base32.is_empty() 
                && (app_dbs.config.totp_mode == TotpMode::Mandatory || user_sel.otp_enabled)
            {
                let _ = req_handler.trace_req(format!("user '{}' not valid Totp: {}",&user_sel.name, &user_sel.otp_enabled));
                return (
                    StatusCode::UNAUTHORIZED,
                    req_handler.req_header.header,
                   "Error data"
                ).into_response();
            } else if user_sel.otp_enabled && user_sel.otp_verified 
                && !user_sel.otp_base32.is_empty() && !user_sel.otp_defs.is_empty() 
            {
                match req_handler.otp_check(&user_sel.otp_base32,&user_item.value, &user_sel.otp_defs) {
                    Ok(val) => {
                        if !val {
                            let _ = req_handler.trace_req(format!("User '{}' not valid TOTP code",&user_item.name));
                            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_item.name,e));
                        return (
                            StatusCode::UNAUTHORIZED,
                            req_handler.req_header.header,
                            "Error"
                        ).into_response();
                    }
                } 
            } 
        } 
        let session_token = req_handler.new_token();
        user_sel.status = UserStatus::Pending;
        let user_data = user_sel.session_data();
        let session_cookie = add_session_cookie(false,&cookies, &session_token, &user_data, app_dbs.config.session_expire, &app_dbs, "invitation").await;
        let session_encoded_key = encode(session_cookie.as_str());
        let body=format!("This is a user password reset request for docserver service");
        let subject = format!("DocServer password reset");
        let reset_url= format!(
            "{}://{}/reset",
            &app_dbs.config.protocol,
            &app_dbs.config.hostport,
        );
        let reset_expiration = format!("{} minutes",(&app_dbs.config.session_expire/60));
        req_handler.context.insert("reset_url", &reset_url);
        req_handler.context.insert("reset_expiration", &reset_expiration);
        req_handler.context.insert("email_body",&body);
        req_handler.context.insert("reset_key",&session_encoded_key);
        req_handler.context.insert("email_subject",&subject);
        let mail_content= if let Some(tpl) = app_dbs.config.tpls.get("reset_password_mail_txt") {
            req_handler.render_template(tpl, "reset password")
        } else {
            format!("{}\n{}\n{}/{}\n",&subject,&body,&reset_url,&session_cookie)
        };
        let mail_html_content = if let Some(tpl) = app_dbs.config.tpls.get("reset_password_mail_html") {
            req_handler.render_template(tpl, "invite")
        } else {
            format!("{}\n{}\n{}/{}\n",&subject,&body,&reset_url,&session_cookie)
        }; 
        let mail_check = MailMessage::check(&app_dbs);
        if ! mail_check.is_empty() {
            let _ = req_handler.trace_req(format!("Mail service check error: {}",&mail_check));
            (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error service"
            ).into_response()
        } else {
            match MailMessage::new(
                &app_dbs.config.mail_from,
                &user_sel.email,
            &app_dbs.config.mail_reply_to,
            ) {
                Ok(mail_message) => {
                    match mail_message.send_html_message(
                        &subject,
                        &mail_content,
                        &mail_html_content,
                        &app_dbs
                    ).await {
                        Ok(_) => {
                            let _ = req_handler.trace_req(format!("Mail sent to: '{}' reset url: {} cookie: {}",
                                &user_sel.name, &reset_url,&session_cookie
                            ));
                            (
                                StatusCode::OK,
                                format!("Mail sent to {}",&user_sel.name)
                            ).into_response()
                        },
                        Err(e) => { 
                            let _ = req_handler.trace_req(format!("Mail message send to: '{}' Error: {} ",&user_sel.name,e));
                            println!("Error mail message send: {}",e);
                            (
                                StatusCode::INTERNAL_SERVER_ERROR,
                                req_handler.req_header.header,
                                "Error service"
                            ).into_response()
                        }
                    }
                },
                Err(e) => {
                    let _ = req_handler.trace_req(format!("Mail message send to: '{}' Creation error: {} ",&user_sel.name,e));
                    println!("Error mail message creation: {}",e);
                    (
                        StatusCode::BAD_REQUEST,
                        req_handler.req_header.header,
                        "Error service"
                    ).into_response()
                }
            }
        }
        //let result=format!("{}","OK");
        // if ! auth_state.has_auth_role(&app_dbs.config.auth_roles) {
        //    return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response();
        // //    return (
        // //         StatusCode::UNAUTHORIZED,
        // //         header,
        // //         "Error authorization"
        // //     ).into_response(); 
        // }
        // (
        //     //status,
        //     req_handler.req_header.header,
        //     result,
        // ).into_response()
    }
    async fn reset_password_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>,
        // Query(req_params): Query<ReqParams>,
        axum::extract::Path(data): axum::extract::Path<String>,
    ) -> Response {
        let session_cookie = decode(&data).unwrap_or_default().to_string();
        let auth_state = AuthState::from_cookie(session_cookie.to_owned(), &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,
            "reset_password_handler"
        ); 
        let user_id = auth_state.user_id();
        if user_id.is_empty() {
            let _ = req_handler.trace_req(format!("No user found"));
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error"
            ).into_response();
        }
        let user_sel = User::select("id", &user_id, false, &app_dbs.user_store).await.unwrap_or_else(|e|{
            println!("Error select: {}", e);
            User::default()
        });
        if user_sel.name.is_empty() {
            let _ = req_handler.trace_req(format!("User 'id' = '{}' not found",&user_id));
            // User not exists
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error data"
            ).into_response();
        }
        if user_sel.status != UserStatus::Active {
            let _ = req_handler.trace_req(format!("user '{}' in not valid status: {}",&user_id, &user_sel.status));
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error status" 
            ).into_response();
        }
        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("with_menu", "1");
        req_handler.context.insert("user", &user_sel);
        req_handler.context.insert("edit_target", "password");
        req_handler.context.insert("edit_reset", "password");
        req_handler.context.remove("web_menu_items");
        let result = if let Some(tpl) = app_dbs.config.tpls.get("user_settings") {
            req_handler.render_template(&tpl,"user setting")
        } else {
            String::from("user settings")
        };
        let user_data = user_sel.session_data();
        let session_token = req_handler.new_token();
        let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await;
        if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) };
        let _ = req_handler.trace_req(format!("user '{}' reset password",&user_id));
        (
            res_headers,
            result.to_owned()
        ).into_response()
    }
    async fn post_user_password_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_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_reset_password_handler"
        );
        if user_data.password.is_empty() {
            let _ = req_handler.trace_req(format!("No passwordfound"));
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error"
            ).into_response();
        }
        if auth_state.session.is_none() {
            let _ = req_handler.trace_req(format!("No session found for user '{}'",&user_data.id));
            return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response();
        }
        let user_id = auth_state.user_id();
        if user_id.is_empty() {
            let _ = req_handler.trace_req(format!("No user found"));
            return (
                StatusCode::BAD_REQUEST,
                req_handler.req_header.header,
                "Error"
            ).into_response();
        }
        let mut user_sel = User::select("id", &user_id, 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_id));
            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_id,&user_data.password, passwd_score,app_dbs.config.password_score)
            );
            return (
                StatusCode::UNAUTHORIZED,
                req_handler.req_header.header,
                "Error"
            ).into_response();
        }
        user_sel.password = generate_hash(&user_data.password);
        let user_data = user_sel.session_data();
        match user_sel.update(&app_dbs.user_store).await {
            Ok(_) => {
                let session_token = auth_state.id();
                let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await;
                if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) };
                let _ = req_handler.trace_req(format!("user '{}' reset password OK",&user_id));
                let result =String::from("OK");
                (
                    req_handler.req_header.header,
                    result.to_owned()
                ).into_response()
            },
            Err(e) => {
                let _ = req_handler.trace_req(format!("Error user '{}' reset password: {}",&user_id,e));
                (
                    StatusCode::BAD_REQUEST,
                    req_handler.req_header.header,
                    "Error"
                ).into_response()
            },
        }
    }
    route(
        "/reset/:data", get(reset_password_handler))
        .route("/reset", post(post_reset_password_handler))
        .route("/resetup", post(post_user_password_handler))
}
