<?php

namespace Bdp\Modules\LimitLoginAttempts\Controllers;

use ProtectLogin\Modules\LimitLoginAttempts\Requests\IpAddress;

class LoginHandler {
	public const DIRECT_ADDR =  'REMOTE_ADDR';
	public const PROXY_ADDR = 'HTTP_X_FORWARDED_FOR';

	public function onSuccessFullLogin($user, $password) {
		if (!is_wp_error($user) && $this->isLoginAllowedFromIp() ) {
			return $user;
		}

		global $limit_login_my_error_shown;
		$limit_login_my_error_shown = true;

		$error = new \WP_Error();
		// This error should be the same as in "shake it" filter below
		$error->add('too_many_retries', $this->composeErrorMessage());
		return $error;
	}

	public function onFailedLogin(string $username) {
		$ip = $this->getAddress();

		/* if currently locked-out, do not add to retries */
		$lockouts = get_option('protect_login_limit_login_lockouts', []);

		if(isset($lockouts[$ip]) && time() < $lockouts[$ip]) {
			return;
		}

		/* Get the arrays with retries and retries-valid information */
		$retries = get_option('kompass_limit_login_retries', []);
		$valid = get_option('kompass_limit_login_retries_valid', []);

		/* Check validity and add one to retries */
		if (isset($retries[$ip])) { //} && isset($valid[$ip]) && time() < $valid[$ip]) {
			$retries[$ip] ++;
		} else {
			$retries[$ip] = 1;
		}

		update_option('kompass_limit_login_retries', $retries);

		/* lockout? */
		if($retries[$ip] % get_option('kompass_limit_login_allowed_retries', 0) != 0) {
			return;
		}


		$retries_long = get_option('kompass_limit_login_allowed_retries', 1)
		                * get_option('kompass_limit_login_allowed_lockouts', 1);

		if ($retries[$ip] >= $retries_long) {
			$lockouts[$ip] = time() + get_option('kompass_limit_login_long_duration', 86400);

		} else {
			$lockouts[$ip] = time() + get_option('kompass_limit_login_lockout_duration', 900);
		}

		update_option('kompass_limit_login_lockouts', $lockouts);


		/* do any notification */
		$this->notify($username);

	}

	private function notifyByEmail($user)
	{
		$ip = $this->getAddress();

		$lockouts = get_option('kompass_limit_login_lockouts', []);
		if (!isset($lockouts[$ip])) {
			return;
		}

		$blocked_until = $lockouts[$ip];

		$retries = get_option('kompass_limit_login_retries', []);
		$currentRetries = $retries[$ip];

		$notify_after = get_option('kompass_limit_login_notify_email_after', 1);
		if ($currentRetries % $notify_after !== 0) {
			return;
		}

		$blogname = get_option('blogname', 'none');

		$subject = sprintf(__("[%s] Too many failed login attempts"
				, 'limit-login-attempts')
			, $blogname);

		$message = 'Neue Sperrung auf deiner Webseite: ' . PHP_EOL .
		           'IP-Adresse: ' . $ip . PHP_EOL .
		           'Gesperrt bis: ' . date('d.m.Y H:i', $blocked_until);

		$admin_email = get_option('admin_email');
		wp_mail($admin_email, $subject, $message);
	}


	/* Handle notification in event of lockout */
	private function notify($user) {
		$args = get_option('kompass_limit_login_lockout_notify', []);
		if (!is_array($args)) {
			$args = [$args];
		}
		foreach ($args as $mode) {
			switch (trim($mode)) {
				case 'email':
					$this->notifyByEmail($user);
					break;
			}
		}
	}
    private function composeErrorMessage() {
        $ip = $this->getAddress();
        $lockouts = get_option('protect_login_limit_login_lockouts');

        $msg = __('Too many failed login attempts.', BDP_LV_PLUGIN_SLUG) . ' ';

        if (!is_array($lockouts) || !isset($lockouts[$ip]) || time() >= $lockouts[$ip]) {
            /* Huh? No timeout active? */
            $msg .=  __('Please try again later.', BDP_LV_PLUGIN_SLUG);
            return $msg;
        }

        $when = ceil(($lockouts[$ip] - time()) / 60);
        if ($when > 60) {
            $when = ceil($when / 60);
            $msg .= sprintf(_n('Please try again in %d hour.', 'Please try again in %d hours.', $when, BDP_LV_PLUGIN_SLUG), $when);
        } else {
            $msg .= sprintf(_n('Please try again in %d minute.', 'Please try again in %d minutes.', $when, BDP_LV_PLUGIN_SLUG), $when);
        }

        return $msg;
    }

	private static function getAddress($typeName = '') {
		global $limitLoginAttemptsSettings;

		$typeOriginal = $typeName;
		if (empty($typeName)) {
			$typeName = get_option('kompass_limit_loginclient_type', self::DIRECT_ADDR);
		}

		if (isset($_SERVER[$typeName]) && filter_var($_SERVER[$typeName], FILTER_VALIDATE_IP)) {
			return $_SERVER[$typeName];
		}

		/*
		 * Not found. Did we get proxy type from option?
		 * If so, try to fall back to direct address.
		 */
		if ( empty($typeName) && $typeOriginal == self::PROXY_ADDR
		     && isset($_SERVER[self::DIRECT_ADDR])
		     && filter_var($_SERVER[self::DIRECT_ADDR], FILTER_VALIDATE_IP)) {

			/*
			 * NOTE: Even though we fall back to direct address -- meaning you
			 * can get a mostly working plugin when set to PROXY mode while in
			 * fact directly connected to Internet it is not safe!
			 *
			 * Client can itself send HTTP_X_FORWARDED_FOR header fooling us
			 * regarding which IP should be banned.
			 */

			return $_SERVER[self::DIRECT_ADDR];
		}

		return '';

	}

	public function isLoginAllowedFromIp() {
		$ip = $this->getAddress();

		if (in_array($ip, get_option('kompass_limit_login_blocklist', []))) {
			return false;
		}

		if (in_array($ip, get_option('kompass_limit_login_allowlist', []))) {
			return true;
		}

		/* lockout active? */
		$lockouts = get_option('kompass_limit_login_lockouts', []);
		return (!is_array($lockouts) || !isset($lockouts[$ip]) || time() >= $lockouts[$ip]);
	}

	public function checkFailedCookies($cookie_elements) {
		$this->clearAuthCookie();

		/*
		 * Invalid username gets counted every time.
		 */

		$this->onFailedLogin($cookie_elements['username']);
	}

	private function clearAuthCookie() {
		wp_clear_auth_cookie();

		if (!empty($_COOKIE[AUTH_COOKIE])) {
			$_COOKIE[AUTH_COOKIE] = '';
		}
		if (!empty($_COOKIE[SECURE_AUTH_COOKIE])) {
			$_COOKIE[SECURE_AUTH_COOKIE] = '';
		}
		if (!empty($_COOKIE[LOGGED_IN_COOKIE])) {
			$_COOKIE[LOGGED_IN_COOKIE] = '';
		}
	}

	public function onValidCookie($cookie_elements, $user) {
		/*
		 * As all meta values get cached on user load this should not require
		 * any extra work for the common case of no stored value.
		 */

		if (get_user_meta($user->ID, 'kompass_limit_login_previous_cookie')) {
			delete_user_meta($user->ID, 'kompass_limit_login_previous_cookie');
		}
	}

	function clearLoginCookie($cookie_elements) {
		$this->clearAuthCookie();

		/*
		 * Under some conditions an invalid auth cookie will be used multiple
		 * times, which results in multiple failed attempts from that one
		 * cookie.
		 *
		 * Unfortunately I've not been able to replicate this consistently and
		 * thus have not been able to make sure what the exact cause is.
		 *
		 * Probably it is because a reload of for example the admin dashboard
		 * might result in multiple requests from the browser before the invalid
		 * cookie can be cleard.
		 *
		 * Handle this by only counting the first attempt when the exact same
		 * cookie is attempted for a user.
		 */

		extract($cookie_elements, EXTR_OVERWRITE);

		// Check if cookie is for a valid user
		$user = get_user_by('login', $username);
		if (!$user) {
			// "shouldn't happen" for this action
			$this->onFailedLogin($username);
			return;
		}

		$previous_cookie = get_user_meta($user->ID, 'kompass_limit_login_previous_cookie', true);
		if ($previous_cookie && $previous_cookie == $cookie_elements) {
			// Identical cookies, ignore this attempt
			return;
		}

		// Store cookie
		if ($previous_cookie)
			update_user_meta($user->ID, 'kompass_limit_login_previous_cookie', $cookie_elements);
		else
			add_user_meta($user->ID, 'kompass_limit_login_previous_cookie', $cookie_elements, true);

		$this->onFailedLogin($username);
	}

	public function handleCookies() {
		if ($this->isLoginAllowedFromIp()) {
			return;
		}

		$this->clearAuthCookie();
	}
}