use std::sync::Arc;
use axum::{
	http::{
		StatusCode,
		Uri,
		header::HeaderMap,
	},
	response::{IntoResponse,Response,Redirect},
};
use tower_cookies::Cookies;
use log::{trace,error};

use crate::{
	defs::{
		AppDBs,
		AuthState,
		ReqHandler,
		ReqHeaderMap,
		TotpMode,
		Random,
		AppConnectInfo,
	},
	users::{
		User,
		UserLogin,
		UserStatus,
	},
	login_password::verify_password,
	handlers::{
		add_session_cookie,
		get_auth_state,
	},
};
use crate::users::{OpenidUser, UserStore};

pub async fn add_openid_user<'a>(openid_user: OpenidUser, curr_id: i64, curr_name: &str,  user_store: &UserStore, req_handler: &ReqHandler<'a>) -> bool {
	let info_add = format!("user {}/{} ({}) -> {} created -> {}",
						   &openid_user.application,
						   &openid_user.appkey,
						   &openid_user.name,
						   &openid_user.userid,
						   &curr_id
	);
	let data_add = format!("{}|{}|{}|{}|{}",
						   &curr_id,
						   &openid_user.application,
						   &openid_user.appkey,
						   &openid_user.name,
						   &openid_user.userid,
	);
	 match openid_user.add(&user_store).await {
		Ok(id) => {
			trace!("{} - {}",&info_add,id);
			let _ = req_handler.trace_req(format!("user '{}', openid link {} ",
					&curr_name,
					&data_add
			));
			true
		},
		Err(e) => {
			let _ = req_handler.trace_req(format!("{} error: {}", &info_add, e));
			error!("{} error -> {:#}",&info_add,e);
			false
		}
	}
}
pub async fn login_user (
	header: HeaderMap,
	uri: Uri,
	app_dbs: Arc<AppDBs>,
	cookies: Cookies,
	random: Random,
	app_connect_info: AppConnectInfo,
	user_login: UserLogin,
	source: &str,
) -> 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_login_handler"
	);
	if user_login.name.is_empty() || source != "from_login_openid" &&  user_login.password.is_empty() {
		let _ = req_handler.trace_req(String::from("Empty name or password"));
		//     return Err(error_page(&SignupError::MissingDetails));
		return (
			StatusCode::BAD_REQUEST,
			req_handler.req_header.header,
			"Error"
		).into_response();
	}
	let field = if user_login.name.contains("@") {
		"email"
	} else {
		"name"
	};
	let (mut user_sel, mut openid_user) = if source == "from_login_openid" {
		let user_data = auth_state.user_data();
		let openid_conf = if let Some(openid_conf) = app_dbs.config.openid_auths.get(&user_data[0]) {
			openid_conf
		} else {
			let _ = req_handler.trace_req(format!("No name openid conf for '{}' found",&user_login.name));
			return (
				StatusCode::BAD_REQUEST,
				req_handler.req_header.header,
				"Error openid auth"
			).into_response();
		};
		let mut openid_user = OpenidUser::app_select(&openid_conf.signup_application, &field, &user_login.name, false, &app_dbs.user_store).await.unwrap_or_else(|e| {
			error!("Error openid {}/{} select: {}", &openid_conf.signup_application, &user_login.name, e);
			OpenidUser::default()
		});
		if openid_user.name.is_empty() &&  openid_conf.trusted {
			let user_sel = User::select("name", &user_login.name, false, &app_dbs.user_store).await.unwrap_or_else(|e| {
				error!("Error select: {}", e);
				User::default()
			});
			if !user_sel.name.is_empty() && user_sel.id > 0 {
				openid_user = OpenidUser {
					id: 0,
					name: user_login.name.to_owned(),
					application: openid_conf.signup_application.to_owned(),
					appkey: user_data[0].to_owned(),
					userid: user_sel.id,
					description: String::from("created from user"),
					created: chrono::Utc::now().timestamp().to_string(),
					lastaccess: String::from(""),
				};
				let _ = add_openid_user(openid_user.to_owned(), user_sel.id, &user_login.name, &app_dbs.user_store, &req_handler).await;
			}
		}
		if openid_user.name.is_empty() {
			let str_user_data = format!("openid|{}|{}|{}|{}|{}|", &user_data[0], &user_data[1], &user_data[2], &openid_conf.signup_application, &user_login.name);
			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;
			let _ = req_handler.trace_req(format!("No openid name '{}' found", &openid_user.name));
			// User not exists
			return Redirect::temporary(&format!("/login")).into_response();
		}
		(
			User::select("id",&openid_user.userid.to_string(), false, &app_dbs.user_store).await.unwrap_or_else(|e|{
				error!("Error select: {}", e);
				User::default()
			}),
			openid_user
		)
	} else {
		(
			User::select( &field, &user_login.name, false, &app_dbs.user_store).await.unwrap_or_else( | e |{
				error!("Error select: {}", e);
				User::default()
			}),
			OpenidUser::default()
		)
	};
	if user_sel.name.is_empty() {
		let _ = req_handler.trace_req(format!("No name '{}' found",&user_login.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_login.name, &user_sel.status));
		return (
			StatusCode::UNAUTHORIZED,
			req_handler.req_header.header,
			"Error status"
		).into_response();
	}
	let result = if source == "from_login_openid" {
		format!("{}:true", "OK")
	} else {
		if verify_password(&user_login.password, &user_sel.password).is_err() {
			let _ = req_handler.trace_req(format!("user '{}' not valid password: {}", &user_login.name, &user_sel.password));
			error!("password NOT valid");
			// TODO
			//return Err(error_page(&SignupError::PasswordsDoNotMatch))
			return (
				StatusCode::BAD_REQUEST,
				req_handler.req_header.header,
				"Error data"
			).into_response();
		}
		let result = format!("{}:{}", "OK", &user_sel.otp_verified);
		if app_dbs.config.totp_mode != TotpMode::No {
			if user_login.otp_auth.is_empty()
				&& (app_dbs.config.totp_mode == TotpMode::Mandatory || user_sel.otp_enabled)
			{
				let _ = req_handler.trace_req(format!("user '{}' not valid Totp: {}", &user_login.name, &user_sel.otp_enabled));
				return (
					req_handler.req_header.header,
					result
				).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_login.otp_auth, &user_sel.otp_defs) {
					Ok(val) => {
						if !val {
							let _ = req_handler.trace_req(format!("user '{}' not valid TOTP code", &user_login.name));
							return (
								StatusCode::UNAUTHORIZED,
								req_handler.req_header.header,
								"Error"
							).into_response();
						}
					},
					Err(e) => {
						let _ = req_handler.trace_req(format!("user '{}' TOTP check error: {}", &user_login.name, e));
						error!("TOTP check: {}", e);
						return (
							StatusCode::UNAUTHORIZED,
							req_handler.req_header.header,
							"Error"
						).into_response();
					}
				}
			}
		}
		result
	};
	let session_user_data = auth_state.user_data();
	user_sel.lastaccess = chrono::Utc::now().timestamp().to_string();
	if user_sel.status != UserStatus::Active { user_sel.status = UserStatus::Active }
	let user_data = user_sel.session_data();
	let curr_id = user_sel.id;
	match user_sel.update(&app_dbs.user_store).await {
		Ok(_) => {
			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 new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await;
			if openid_user.userid == curr_id {
				openid_user.lastaccess = chrono::Utc::now().timestamp().to_string();
				let info_update = format!("User '{}/{} ({})' -> {} update",
					&openid_user.application,
					&openid_user.appkey,
					&openid_user.name,
					&openid_user.userid,
				);
			    match openid_user.update(&app_dbs.user_store).await {
					Ok(_) => trace!("{}d !", &info_update),
					Err(e) => error!("{} error: {}", &info_update, e)
				}
				return Redirect::temporary( &format!("/")).into_response();
			} else if session_user_data.len() > 4 && session_user_data[0] == "openid" && !session_user_data[4].is_empty() && !session_user_data[5].is_empty() {
				let openid_user = OpenidUser {
					id: 0,
					name: session_user_data[5].to_owned(),
					application: session_user_data[4].to_owned(),
					appkey: session_user_data[1].to_owned(),
					userid: curr_id,
					description: String::from("created from user"),
					created: chrono::Utc::now().timestamp().to_string(),
					lastaccess: String::from(""),
				};
				if ! add_openid_user(openid_user, curr_id, &user_login.name,  &app_dbs.user_store, &req_handler).await {
					return (
						StatusCode::NOT_FOUND,
						req_handler.req_header.header,
						"Error create openid"
					).into_response();
				}
			}
			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));
			// TODO Is this a bug ??
			// http -> tokio-runtime-worker' panicked at 'payload claims content-length of 10, custom content-length header claims 34
			if app_dbs.config.protocol == "https" {
				(
					req_handler.req_header.header,
					result
				).into_response()
			} else {
				(
					result
				).into_response()
			}
		},
		Err(e) => {
			let _ = req_handler.trace_req(format!("user '{}' update error: {}",&user_login.name,e));
			(
				StatusCode::BAD_REQUEST,
				req_handler.req_header.header,
				"Error"
			).into_response()
		}
	}
}