<?php
declare(strict_types=1);

namespace WPUltimateSecurity\UltimateSecurity\Modules\Authentication;

use WPUltimateSecurity\UltimateSecurity\Support\Helpers\Utils;
use WPUltimateSecurity\UltimateSecurity\Modules\TestMode\TestModeService;
use WPUltimateSecurity\UltimateSecurity\Modules\Base\AbstractModule;
use WP_Error;
use WP_User;

class PasswordPolicies extends AbstractModule
{
	/**
	 * Module identifier.
	 */
	public const MODULE_ID = 'auth.password_policies';

	/**
	 * Module version.
	 */
	protected const VERSION = '2.0.0';

	/**
	 * Get the unique module identifier.
	 *
	 * @return string
	 */
	public static function getId(): string
	{
		return self::MODULE_ID;
	}

	/**
	 * Get the human-readable module name.
	 *
	 * @return string
	 */
	public static function getName(): string
	{
		return __('Password Policies', 'ultimate-security');
	}

	/**
	 * Get the module domain.
	 *
	 * @return string
	 */
	public static function getDomain(): string
	{
		return 'authentication';
	}

	/**
	 * Boot the module (registers hooks and handlers).
	 *
	 * @return void
	 */
	public static function boot(): void
	{
		$ultimate_security_options = Utils::ultimate_security_get_option();
		if (isset($ultimate_security_options['security']['password_policies']['activate_password_policies']) && $ultimate_security_options['security']['password_policies']['activate_password_policies']) {
			// Hooks
			add_action('admin_footer', [self::class, 'edit_profile_password_hints'], 9999);

			add_action('validate_password_reset', [self::class, 'ultimate_security_password_validation_on_reset'], 10, 2);
			add_action('user_profile_update_errors', [self::class, 'ultimate_security_password_validation_on_profile_update'], 10, 3);

			add_action('woocommerce_save_account_details_errors', [self::class, 'ultimate_security_password_validation_on_account_update'], 10, 2);

			add_filter('password_hint', [self::class, 'ultimate_security_change_password_hint_message']);

			add_action('wp_set_password', [self::class, 'ultimate_security_save_old_password_to_user_meta'], 10, 2);
			add_action('profile_update', [self::class, 'ultimate_security_profile_update_password_history'], 10);
			add_action('woocommerce_save_account_details', [self::class, 'ultimate_security_woocommerce_save_account_details_password_history'], 10);

			add_filter('lostpassword_url', [self::class, 'ultimate_security_custom_lostpassword_url'], 10, 2);
			add_action('template_redirect', [self::class, 'ultimate_security_redirect_lost_password_to_account']);

			add_action('wp_enqueue_scripts', [self::class, 'enqueue_scripts']);
			add_action('login_enqueue_scripts', [self::class, 'enqueue_scripts_login']);
			add_action('admin_enqueue_scripts', [self::class, 'enqueue_scripts_admin']);
		}

		add_action('wp_ajax_ultimate_security_get_password_hints', [self::class, 'ultimate_security_get_password_hints']);
		// add_action('wp_ajax_nopriv_ultimate_security_get_password_hints', [self::class, 'ultimate_security_get_password_hints']);

		add_action('wp_ajax_ultimate_security_password_validation_on_myaccount', [self::class, 'ultimate_security_password_validation_on_myaccount']);
		// add_action('wp_ajax_nopriv_ultimate_security_password_validation_on_myaccount', [self::class, 'ultimate_security_password_validation_on_myaccount']);

		add_filter('lost_password_html_link', [self::class, 'ultimate_security_remove_lost_password_html_link']);
	}
	public static function enqueue_scripts()
	{
		wp_deregister_script('password-strength-meter');
		wp_enqueue_script('plulse-password-strength-meter', ULTIMATE_SECURITY_URL . 'assets/admin/js/password-strength-meter.js', array('jquery'));

		// Pass password policies to JavaScript for real-time strength meter
		$options = Utils::ultimate_security_get_option();
		$policies = isset($options['security']['password_policies']) ? $options['security']['password_policies'] : [];

		wp_localize_script('plulse-password-strength-meter', 'ultimateSecurityPolicies', [
			'min_length' => isset($policies['min_length']) ? intval($policies['min_length']) : 12,
			'ui_rules_mix_case' => isset($policies['ui_rules_mix_case']) ? sanitize_text_field($policies['ui_rules_mix_case']) : '0',
			'ui_rules_numeric' => isset($policies['ui_rules_numeric']) ? sanitize_text_field($policies['ui_rules_numeric']) : '0',
			'ui_rules_special_chars' => isset($policies['ui_rules_special_chars']) ? sanitize_text_field($policies['ui_rules_special_chars']) : '0',
		]);
	}

	/**
	 * Enqueue password strength meter script in admin (profile/user edit pages)
	 */
	public static function enqueue_scripts_login()
	{
		// Don't load on reset password page to avoid conflicts with generate password button
		// WordPress uses both 'rp' and 'resetpass' for the password reset page
		if (isset($_GET['action']) && in_array($_GET['action'], ['rp', 'resetpass'], true)) {
			return;
		}

		// Load on other login pages
		self::enqueue_scripts();
	}

	public static function enqueue_scripts_admin($hook)
	{
		// Only load on profile and user edit pages
		if (!in_array($hook, ['profile.php', 'user-edit.php', 'user-new.php'], true)) {
			return;
		}

		wp_enqueue_script('plulse-password-strength-meter', ULTIMATE_SECURITY_URL . 'assets/admin/js/password-strength-meter.js', array('jquery'), ULTIMATE_SECURITY_VERSION, true);

		// Pass password policies to JavaScript for real-time strength meter
		$options = Utils::ultimate_security_get_option();
		$policies = isset($options['security']['password_policies']) ? $options['security']['password_policies'] : [];

		wp_localize_script('plulse-password-strength-meter', 'ultimateSecurityPolicies', [
			'min_length' => isset($policies['min_length']) ? intval($policies['min_length']) : 12,
			'ui_rules_mix_case' => isset($policies['ui_rules_mix_case']) ? sanitize_text_field($policies['ui_rules_mix_case']) : '0',
			'ui_rules_numeric' => isset($policies['ui_rules_numeric']) ? sanitize_text_field($policies['ui_rules_numeric']) : '0',
			'ui_rules_special_chars' => isset($policies['ui_rules_special_chars']) ? sanitize_text_field($policies['ui_rules_special_chars']) : '0',
		]);
	}

	// Hook into the password reset validation

	public static function ultimate_security_password_validation_on_reset(WP_Error $errors, $user): void
	{
		if (isset($_POST['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'update-user_' . $user->ID)) {
			// Only proceed if user object is valid.
			if (!$user instanceof WP_User) {
				return;
			}

			// Check if there are already any errors
			if (!empty($errors->errors)) {
				return; // Exit if there are existing errors
			}

			// Get the password from the POST request
			if (isset($_POST['pass1']) && !empty($_POST['pass1'])) {
				$password = sanitize_text_field(wp_unslash($_POST['pass1']));
				if (self::ultimate_security_password_is_valid($password, $user)) {
					$errors->add('invalid_password', esc_html__('Password does not meet security requirements. Please choose a different password.', 'ultimate-security'));
				}
			}
		}
	}
	// Hook into the user profile update process
	public static function ultimate_security_password_validation_on_profile_update($errors, $update, $user)
	{
		if (isset($_POST['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'update-user_' . $user->ID)) {
			// Only run this for users with a new password set
			if (isset($_POST['pass1']) && !empty($_POST['pass1'])) {
				$password = sanitize_text_field(wp_unslash($_POST['pass1']));
				if (self::ultimate_security_password_is_valid($password, $user)) {
					// $errors->add('invalid_password', self::ultimate_security_password_is_valid($password, $user));
					$errors->add('invalid_password', esc_html__('Password does not meet security requirements. Please choose a different password.', 'ultimate-security'));
				}
			}
		}
	}
	// Hook into WooCommerce save account details validation
	public static function ultimate_security_password_validation_on_account_update($errors, $user)
	{
		//wp_nonce_field( 'save_account_details', 'save-account-details-nonce' );
		if (isset($_POST['save-account-details-nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['save-account-details-nonce'])), 'save_account_details')) {
			// Check if the user has provided a new password
			if (!empty($_POST['password_1']) && !empty($_POST['password_2']) && $_POST['password_1'] === $_POST['password_2']) {
				$password = sanitize_text_field(wp_unslash($_POST['password_1']));
				if (self::ultimate_security_password_is_valid($password, $user)) {
					// $errors->add('invalid_password', self::ultimate_security_password_is_valid($password, $user));
					$errors->add('invalid_password', esc_html__('Password does not meet security requirements. Please choose a different password.', 'ultimate-security'));
				}
			}
		}
	}

	/**
	 * Change the wording of the password hint.
	 *
	 * @param string $hint
	 * @return string
	 */
	public static function ultimate_security_change_password_hint_message($hint)
	{
		$modifiedHint = self::ultimate_security_password_hints();

		return $modifiedHint ? $modifiedHint : $hint;
	}


	// Hook into `wp_set_password` for reset password actions (e.g., /wp-login.php?action=resetpass)
	// Save old password and date on password change
	public static function ultimate_security_save_old_password_to_user_meta($password, $user_id)
	{
		$current_date = current_time('Y-m-d H:i:s');

		// Retrieve the current password hash from the database
		$user = get_userdata($user_id);
		$current_password_hash = $user->user_pass;

		// Get password history limit from settings
		$options = Utils::ultimate_security_get_option();
		$history_limit = isset($options['security']['password_policies']['password_history'])
			? (int) $options['security']['password_policies']['password_history']
			: 1;

		// Retrieve existing password history, if any
		$password_history = get_user_meta($user_id, 'user_password_history', true);

		// Initialize as an empty array if there's no history
		if (!is_array($password_history)) {
			$password_history = [];
		}

		// Trim history to keep only up to the limit (excluding the current one we're about to add)
		if (count($password_history) >= $history_limit) {
			$password_history = array_slice($password_history, 0, $history_limit, true);
		}

		// Add current password hash with date as key (prepend to keep latest first)
		$password_history = array($current_date => $current_password_hash) + $password_history;

		// Update the user meta with the new password history
		update_user_meta($user_id, 'user_password_history', $password_history);
	}

	public static function ultimate_security_profile_update_password_history($user_id)
	{
		if (isset($_POST['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'update-user_' . $user_id)) {
			if (!empty($_POST['pass1'])) {
				$password = sanitize_text_field(wp_unslash($_POST['pass1']));
				// Save old password when admin or user updates password in profile
				self::ultimate_security_save_old_password_to_user_meta($password, $user_id);
			}
		}
	}
	public static function ultimate_security_get_password_hints()
	{

		if (isset($_POST['security']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'ultimate_security_nonce')) {
			$hints = self::ultimate_security_password_hints();
			wp_send_json_success(array('hints' => $hints));
		} else {
			wp_send_json_error(array('error_message' => esc_html__('Nonce verification failed. Please try again.', 'ultimate-security')));
		}
	}

	public static function ultimate_security_woocommerce_save_account_details_password_history($user_id)
	{
		if (isset($_POST['save-account-details-nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['save-account-details-nonce'])), 'save_account_details')) {
			if (!empty($_POST['password_1']) && !empty($_POST['password_2']) && $_POST['password_1'] === $_POST['password_2']) {
				$password = sanitize_text_field(wp_unslash($_POST['password_1']));
				// Save old password when user updates password in WooCommerce My Account
				self::ultimate_security_save_old_password_to_user_meta($password, $user_id);
			}
		}
	}

	public static function ultimate_security_remove_lost_password_html_link($html_link)
	{
		$ultimate_security_options = Utils::ultimate_security_get_option();
		if (isset($ultimate_security_options['security']['password_policies']['content_self_reset']) && $ultimate_security_options['security']['password_policies']['content_self_reset']) {
			// $html_link = sprintf('<a class="wp-login-lost-password" href="%s">%s</a>', esc_url(wp_lostpassword_url()), __('Changed Text'));
			$html_link = sprintf('<a class="wp-login-lost-password" href="%s">%s</a>', esc_url(wp_lostpassword_url()), esc_html($ultimate_security_options['security']['password_policies']['content_self_reset']));
		}
		return $html_link;
	}

    public static function ultimate_security_custom_lostpassword_url($lostpassword_url, $redirect)
    {
        $ultimate_security_options = Utils::ultimate_security_get_option();
        if (
            isset($ultimate_security_options['security']['password_policies']['disable_self_reset'])
            && $ultimate_security_options['security']['password_policies']['disable_self_reset'] === '1'
        ) {
            // Define your custom lost password URL
            $lostpassword_url = (isset($ultimate_security_options['security']['password_policies']['url_self_reset']) && !empty($ultimate_security_options['security']['password_policies']['url_self_reset'])) ? esc_url($ultimate_security_options['security']['password_policies']['url_self_reset']) : home_url('/'); // Replace with your custom URL

            // Add redirect if needed
            if (!empty($redirect)) {
                $lostpassword_url = add_query_arg('redirect_to', urlencode($redirect), $lostpassword_url);
            }
        }

        return $lostpassword_url;
    }


    public static function ultimate_security_redirect_lost_password_to_account()
    {
        $ultimate_security_options = Utils::ultimate_security_get_option();
        if (
            isset($ultimate_security_options['security']['password_policies']['disable_self_reset'])
            && $ultimate_security_options['security']['password_policies']['disable_self_reset'] === '1'
        ) {
            if (is_plugin_active('woocommerce/woocommerce.php') && is_account_page() && is_wc_endpoint_url('lost-password')) {
                $lostpassword_url = (isset($ultimate_security_options['security']['password_policies']['url_self_reset']) && !empty($ultimate_security_options['security']['password_policies']['url_self_reset'])) ? esc_url($ultimate_security_options['security']['password_policies']['url_self_reset']) : home_url('/');
                wp_redirect($lostpassword_url);
                exit;
            }
        }
    }
	public static function ultimate_security_password_validation_on_myaccount(): void
	{
		if (isset($_POST['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'reset_password')) {
			$password = isset($_POST['password_1']) ? sanitize_text_field(wp_unslash($_POST['password_1'])) : '';
			$password_2 = isset($_POST['password_2']) ? sanitize_text_field(wp_unslash($_POST['password_2'])) : '';
			$reset_login = isset($_POST['reset_login']) ? sanitize_text_field(wp_unslash($_POST['reset_login'])) : '';
			$user = get_user_by('login', $reset_login);
			if ($password === '' || $password_2 === '' || $password !== $password_2) {
				wp_send_json_error(array('error_message' => esc_html__('Passwords do not match. Please try again.', 'ultimate-security')));
			}
			if (!$user) {
				wp_send_json_error(array('error_message' => esc_html__('Invalid user login.', 'ultimate-security')));
			}
			if (self::ultimate_security_password_is_valid($password, $user)) {
				wp_send_json_error(array('error_message' => esc_html__('Password does not meet security requirements. Please choose a different password.', 'ultimate-security')));
			}
			wp_send_json_success();
		} else {
			wp_send_json_error(array('error_message' => esc_html__('Nonce verification failed. Please try again.', 'ultimate-security')));
		}
	}


	public static function edit_profile_password_hints()
	{
		global $pagenow;
		if ($pagenow === 'user-edit.php') {
			$content = self::ultimate_security_password_hints();
			?>
			<script>
				jQuery(document).ready(function ($) {
					$('.wp-pwd').append('<?php echo wp_kses_post($content) ?>');
				});
			</script>
			<?php
		}
	}

	public static function ultimate_security_password_is_valid($password, $user)
    {
        $user_by_id = get_user_by('id', $user->ID);
        $ultimate_security_options = Utils::ultimate_security_get_option();

        $user_login = $user->user_login ? $user->user_login : $user_by_id->data->user_login;

        $pass_length = (isset($ultimate_security_options['security']['password_policies']['min_length']) && $ultimate_security_options['security']['password_policies']['min_length']) ? esc_html($ultimate_security_options['security']['password_policies']['min_length']) : 12;

        // Define forbidden special characters
        $special_chars_required = '!@#$%^&*()-_ []{}<>~`+=,.;:/?|';
        $error = 0;
        // Rule 1: At least 16 characters
        if (strlen($password) < $pass_length) {
            $error++;
        }

        // Rule 2: Should not contain known words (e.g., "password", "admin", etc.)
        $known_words = [ "password", "123456", "123456789", "12345678", "12345", "1234567", "qwerty", "abc123", "letmein", "welcome", "admin", "football", "iloveyou", "monkey", "sunshine", "password1", "123123", "000000", "qwerty123", "1q2w3e", "trustno1", "master", "whatever", "freedom", "pass", "hello", "secret", "charlie", "superman", "liverpool", "batman", "flower", "lovely", "shadow", "cheese", "amanda", "computer", "daniel", "summer", "asdfgh", "buster", "thomas", "jessica", "jordan", "harley", "ginger", "tigger", "poohbear", "princess", "baseball", "soccer", "hockey", "george", "fishing", "pepper", "happy", "hunter", "michael", "maggie", "chocolate", "pepper", "cookie", "banana", "apple", "skywalker", "snoopy", "tigger", "qazwsx", "william", "ninja", "test", "welcome1", "mypass", "mypass123", "mypassword", "newyork", "lasvegas", "tokyo", "football1", "startrek", "bond007", "matrix", "hannah", "alexander", "sebastian", "charles", "internet", "security", "p@ssw0rd", "admin123", "abcd1234", "mypassword1", "easy123", "loveme", "london", "mexico", "123qwe", "qwertyuiop", "asdfghjkl", "zxcvbnm", "superuser", "dragon", "babygirl", "babyboy", "flower", "dallas", "chicago", "newyork", "family", "friends", "qwertyui", "windows", "iloveyou2", "orange", "rainbow", "purple", "google", "samsung", "iphone", "mobile", "guitar", "football2", "mypassw0rd", "default", "baseball1", "secret123", "user", "access", "login", "passw0rd", "test123", "sunflower", "spring", "summer2021", "autumn", "winter", "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december", "myname", "username", "guest", "welcome2", "q1w2e3", "pass1234", "abc12345", "letmein1", '1111', '2222', '3333', '4444', '5555', '6666', '7777', '8888', '9999', '0000', '1234', '5678', ];

        foreach ($known_words as $word) {
            if (stripos($password, $word) !== false) {
                $error++;
            }
        }

        // Rule 3: Cannot contain the username
        if (stripos($password, $user_login) !== false) {
            $error++;
        }
        if (isset($ultimate_security_options['security']['password_policies']['ui_rules_special_chars']) && $ultimate_security_options['security']['password_policies']['ui_rules_special_chars']) {
            // Rule 4: Must contain at least one allowed special character
            if (!preg_match('/[' . preg_quote($special_chars_required, '/') . ']/', $password)) {
                $error++;
            }
        }
        if (isset($ultimate_security_options['security']['password_policies']['excluded_special_chars']) && $ultimate_security_options['security']['password_policies']['excluded_special_chars']) {
            $forbidden_special_chars = $ultimate_security_options['security']['password_policies']['excluded_special_chars'];
            // Rule 5: Cannot contain forbidden special characters
            if (!empty($forbidden_special_chars)) {
                // Check each character individually
                $forbidden_chars = str_split($forbidden_special_chars);
                foreach ($forbidden_chars as $char) {
                    if (strpos($password, $char) !== false) {
                        $error++;
                        break; // Stop after finding first forbidden character
                    }
                }
            }
        }
        if (isset($ultimate_security_options['security']['password_policies']['ui_rules_mix_case']) && $ultimate_security_options['security']['password_policies']['ui_rules_mix_case']) {
            // Rule 6: Must contain at least one lowercase character
            if (!preg_match('/[a-z]/', $password)) {
                $error++;
            }

            // Rule 7: Must contain at least one uppercase character
            if (!preg_match('/[A-Z]/', $password)) {
                $error++;
            }
        }
        if (isset($ultimate_security_options['security']['password_policies']['ui_rules_numeric']) && $ultimate_security_options['security']['password_policies']['ui_rules_numeric']) {

            // Rule 8: Must contain at least one number
            if (!preg_match('/[0-9]/', $password)) {
                $error++;
            }
        }

        // Rule 8: Cannot use old passwords
        $oldpass = $user_by_id->data->user_pass ? $user_by_id->data->user_pass : '';
        if ($user && wp_check_password($password, $oldpass, $user->ID)) {
            $error++;
        }
        if (isset($ultimate_security_options['security']['password_policies']['password_history']) && $ultimate_security_options['security']['password_policies']['password_history'] > 1) {
            $count = (int) $ultimate_security_options['security']['password_policies']['password_history'] - 1;
            $user_password_history = get_user_meta($user->ID, 'user_password_history', true);
            if (!is_array($user_password_history)) {
                $user_password_history = [];
            }
            $n = 1;
            foreach ($user_password_history as $date => $hash) {
                if ($user && wp_check_password($password, $hash)) {
                    $error++;
                }
                if ($n >= $count)
                    break;
                $n++;
            }
        }
        // Test Mode: Log simulation and pretend password is valid
        if ($error > 0 && TestModeService::shouldSimulate($user->ID ?? null)) {
            TestModeService::logSimulation(
                'password_policies',
                'password_invalid',
                __('Password would fail policy validation', 'ultimate-security'),
                [
                    'user_id' => $user->ID ?? 0,
                    'error_count' => $error,
                ]
            );
            return 0; // Pretend password is valid
        }

        return $error;
    }
    public static function ultimate_security_password_hints()
    {
        $html = '';
        $ultimate_security_options = Utils::ultimate_security_get_option();
        if (isset($ultimate_security_options['security']['password_policies']['activate_password_policies']) && $ultimate_security_options['security']['password_policies']['activate_password_policies']) {
            $html .= '<div class="pass-strength-result"> <strong>' . 'Hints for a strong password' . ':</strong><ul><li class="strength">Do not use known words.</li><li class="username">Cannot contain the username.</li>';

            if (isset($ultimate_security_options['security']['password_policies']['min_length']) && $ultimate_security_options['security']['password_policies']['min_length'])
                $html .= '<li class="length">Must be at least ' . esc_html($ultimate_security_options['security']['password_policies']['min_length']) . ' characters long.</li>';

            if (isset($ultimate_security_options['security']['password_policies']['ui_rules_mix_case']) && $ultimate_security_options['security']['password_policies']['ui_rules_mix_case'])
                $html .= '<li class="mix_case">Must contain both UPPERCASE &amp; lowercase characters.</li>';

            if (isset($ultimate_security_options['security']['password_policies']['ui_rules_numeric']) && $ultimate_security_options['security']['password_policies']['ui_rules_numeric'])
                $html .= '<li class="numeric">Must contain numbers.</li>';

            if (isset($ultimate_security_options['security']['password_policies']['ui_rules_special_chars']) && $ultimate_security_options['security']['password_policies']['ui_rules_special_chars'])
                $html .= '<li class="special_chars">Must contain special characters such as <code>! @ # $ % ^ &amp; * ( ) _ ? £ " - + = ~ ; : € &lt; &gt;</code>.</li>';

            if (isset($ultimate_security_options['security']['password_policies']['excluded_special_chars']) && $ultimate_security_options['security']['password_policies']['excluded_special_chars'])
                $html .= '<li class="special_chars">Password cannot contain any of these special characters: <code>' . esc_html($ultimate_security_options['security']['password_policies']['excluded_special_chars']) . '</code>.</li>';

            $html .= '</ul></div>';
        }
        return $html;
    }
}
