<?php
use OTPHP\TOTP;

class UserHelper {

	const HASH_ALGO_SSHA512 = 'SSHA-512';
	const HASH_ALGO_SSHA256 = 'SSHA-256';
	const HASH_ALGO_MODE2   = 'MODE2';
	const HASH_ALGO_SHA1X   = 'SHA1X';
	const HASH_ALGO_SHA1    = 'SHA1';

	const HASH_ALGOS = [
		self::HASH_ALGO_SSHA512,
		self::HASH_ALGO_SSHA256,
		self::HASH_ALGO_MODE2,
		self::HASH_ALGO_SHA1X,
		self::HASH_ALGO_SHA1
	];

	static function authenticate(string $login = null, string $password = null, bool $check_only = false, string $service = null) {
		if (!Config::get(Config::SINGLE_USER_MODE)) {
			$user_id = false;
			$auth_module = false;

			PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_AUTH_USER,
				function ($result, $plugin) use (&$user_id, &$auth_module) {
					if ($result) {
						$user_id = (int)$result;
						$auth_module = strtolower(get_class($plugin));
						return true;
					}
				},
				$login, $password, $service);

			if ($user_id && !$check_only) {

				if (session_status() != PHP_SESSION_ACTIVE)
					session_start();

				session_regenerate_id(true);

				$user = ORM::for_table('ttrss_users')->find_one($user_id);

				if ($user) {
					$_SESSION["uid"] = $user_id;
					$_SESSION["auth_module"] = $auth_module;
					$_SESSION["name"] = $user->login;
					$_SESSION["access_level"] = $user->access_level;
					$_SESSION["csrf_token"] = bin2hex(get_random_bytes(16));
					$_SESSION["ip_address"] = UserHelper::get_user_ip();
					$_SESSION["pwd_hash"] = $user->pwd_hash;

					$user->last_login = Db::NOW();
					$user->save();

					return true;
				}

				return false;
			}

			if ($login && $password && !$user_id && !$check_only)
				Logger::log(E_USER_WARNING, "Failed login attempt for $login (service: $service) from " . UserHelper::get_user_ip());

			return false;

		} else {

			$_SESSION["uid"] = 1;
			$_SESSION["name"] = "admin";
			$_SESSION["access_level"] = 10;

			$_SESSION["hide_hello"] = true;
			$_SESSION["hide_logout"] = true;

			$_SESSION["auth_module"] = false;

			if (!$_SESSION["csrf_token"])
				$_SESSION["csrf_token"] = bin2hex(get_random_bytes(16));

			$_SESSION["ip_address"] = UserHelper::get_user_ip();

			return true;
		}
	}

	static function load_user_plugins(int $owner_uid, PluginHost $pluginhost = null) {

		if (!$pluginhost) $pluginhost = PluginHost::getInstance();

		if ($owner_uid && Config::get_schema_version() >= 100 && empty($_SESSION["safe_mode"])) {
			$plugins = get_pref(Prefs::_ENABLED_PLUGINS, $owner_uid);

			$pluginhost->load((string)$plugins, PluginHost::KIND_USER, $owner_uid);

			/*if (get_schema_version() > 100) {
				$pluginhost->load_data();
			}*/
		}
	}

	static function login_sequence() {
		$pdo = Db::pdo();

		if (Config::get(Config::SINGLE_USER_MODE)) {
			if (session_status() != PHP_SESSION_ACTIVE)
					session_start();

			self::authenticate("admin", null);
			startup_gettext();
			self::load_user_plugins($_SESSION["uid"]);
		} else {
			if (!\Sessions\validate_session())
				$_SESSION["uid"] = null;

			if (empty($_SESSION["uid"])) {

				if (Config::get(Config::AUTH_AUTO_LOGIN) && self::authenticate(null, null)) {
					$_SESSION["ref_schema_version"] = get_schema_version();
				} else {
					 self::authenticate(null, null, true);
				}

				if (empty($_SESSION["uid"])) {
					UserHelper::logout();

					Handler_Public::_render_login_form();
					exit;
				}

			} else {
				/* bump login timestamp */
				$user = ORM::for_table('ttrss_users')->find_one($_SESSION["uid"]);
				$user->last_login = Db::NOW();
				$user->save();

				$_SESSION["last_login_update"] = time();
			}

			if ($_SESSION["uid"]) {
				startup_gettext();
				self::load_user_plugins($_SESSION["uid"]);
			}
		}
	}

	static function print_user_stylesheet() {
		$value = get_pref(Prefs::USER_STYLESHEET);

		if ($value) {
			print "<style type='text/css' id='user_css_style'>";
			print str_replace("<br/>", "\n", $value);
			print "</style>";
		}

	}

	static function get_user_ip() {
		foreach (["HTTP_X_REAL_IP", "REMOTE_ADDR"] as $hdr) {
			if (isset($_SERVER[$hdr]))
				return $_SERVER[$hdr];
		}

		return null;
	}

	static function get_login_by_id(int $id) {
		$user = ORM::for_table('ttrss_users')
			->find_one($id);

		if ($user)
			return $user->login;
		else
			return null;
	}

	static function find_user_by_login(string $login) {
		$user = ORM::for_table('ttrss_users')
			->where('login', $login)
			->find_one();

		if ($user)
			return $user->id;
		else
			return null;
	}

	static function logout() {
		if (session_status() === PHP_SESSION_ACTIVE)
			session_destroy();

		if (isset($_COOKIE[session_name()])) {
		   setcookie(session_name(), '', time()-42000, '/');

		}
		session_commit();
	}

	static function get_salt() {
		return substr(bin2hex(get_random_bytes(125)), 0, 250);
	}

	static function reset_password($uid, $format_output = false, $new_password = "") {

		$user = ORM::for_table('ttrss_users')->find_one($uid);
		$message = "";

		if ($user) {

			$login = $user->login;

			$new_salt = self::get_salt();
			$tmp_user_pwd = $new_password ? $new_password : make_password();

			$pwd_hash = self::hash_password($tmp_user_pwd, $new_salt, self::HASH_ALGOS[0]);

			$user->pwd_hash = $pwd_hash;
			$user->salt = $new_salt;
			$user->save();

			$message = T_sprintf("Changed password of user %s to %s", "<strong>$login</strong>", "<strong>$tmp_user_pwd</strong>");
		} else {
			$message = __("User not found");
		}

		if ($format_output)
			print_notice($message);
		else
			print $message;
	}

	static function check_otp(int $owner_uid, int $otp_check) : bool {
		$otp = TOTP::create(self::get_otp_secret($owner_uid, true));

		return $otp->now()  == $otp_check;
	}

	static function disable_otp(int $owner_uid) : bool {
		$user = ORM::for_table('ttrss_users')->find_one($owner_uid);

		if ($user) {
			$user->otp_enabled = false;

			// force new OTP secret when next enabled
			if (Config::get_schema_version() >= 143) {
				$user->otp_secret = null;
			}

			$user->save();

			return true;
		} else {
			return false;
		}
	}

	static function enable_otp(int $owner_uid, int $otp_check) : bool {
		$secret = self::get_otp_secret($owner_uid);

		if ($secret) {
			$otp = TOTP::create($secret);
			$user = ORM::for_table('ttrss_users')->find_one($owner_uid);

			if ($otp->now() == $otp_check && $user) {

				$user->otp_enabled = true;
				$user->save();

				return true;
			}
		}
		return false;
	}


	static function is_otp_enabled(int $owner_uid) : bool {
		$user = ORM::for_table('ttrss_users')->find_one($owner_uid);

		if ($user) {
			return $user->otp_enabled;
		} else {
			return false;
		}
	}

	static function get_otp_secret(int $owner_uid, bool $show_if_enabled = false) {
		$user = ORM::for_table('ttrss_users')->find_one($owner_uid);

		if ($user) {

			$salt_based_secret = mb_substr(sha1($user->salt), 0, 12);

			if (Config::get_schema_version() >= 143) {
				$secret = $user->otp_secret;

				if (empty($secret)) {

					/* migrate secret if OTP is already enabled, otherwise make a new one */
					if ($user->otp_enabled) {
						$user->otp_secret = $salt_based_secret;
					} else {
						$user->otp_secret = bin2hex(get_random_bytes(10));
					}

					$user->save();

					$secret = $user->otp_secret;
				}
			} else {
				$secret = $salt_based_secret;
			}

			if (!$user->otp_enabled || $show_if_enabled) {
				return \ParagonIE\ConstantTime\Base32::encodeUpperUnpadded($secret);
			}
		}

		return null;
	}

	static function is_default_password() {
		$authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]);

		if ($authenticator &&
                method_exists($authenticator, "check_password") &&
                $authenticator->check_password($_SESSION["uid"], "password")) {

			return true;
		}
		return false;
	}

	static function hash_password(string $pass, string $salt, string $algo = "") {

		if (!$algo) $algo = self::HASH_ALGOS[0];

		$pass_hash = "";

		switch ($algo) {
			case self::HASH_ALGO_SHA1:
				$pass_hash = sha1($pass);
				break;
			case self::HASH_ALGO_SHA1X:
				$pass_hash = sha1("$salt:$pass");
				break;
			case self::HASH_ALGO_MODE2:
			case self::HASH_ALGO_SSHA256:
				$pass_hash = hash('sha256', $salt . $pass);
				break;
			case self::HASH_ALGO_SSHA512:
				$pass_hash = hash('sha512', $salt . $pass);
				break;
			default:
				user_error("hash_password: unknown hash algo: $algo", E_USER_ERROR);
		}

		if ($pass_hash)
			return "$algo:$pass_hash";
		else
			return false;
	}
}