<?php
/**
 * VMPFence Strong Password Validator
 *
 * Implements strong password validation algorithm
 * with comprehensive security checks.
 *
 * @package VMPFence
 * @since 2.0.0
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Strong Password Validator Class
 *
 * Validates passwords according to strong password requirements
 */
class VMPFence_Strong_Password_Validator {

    /**
     * Minimum password length requirement
     *
     * @var int
     */
    const MIN_LENGTH = 12;

    /**
     * Common weak words that should be blocked
     *
     * @var array
     */
    private static $blocked_words = array(
        'password', 'passwd', 'mypass', 'wordpress',
        'admin', 'administrator', 'login', 'user',
        'root', 'manager', 'guest', 'demo', 'test'
    );

    /**
     * Common weak password patterns
     *
     * @var array
     */
    private static $weak_passwords = array(
        'password', 'password1', 'password123', '123456', '123456789',
        'qwerty', 'abc123', 'password1!', 'admin', 'administrator',
        'root', 'test', 'guest', 'user', 'demo', 'welcome', 'letmein',
        '1234567890', 'qwertyuiop', 'asdfghjkl', 'zxcvbnm', 'monkey',
        'dragon', 'master', 'superman', 'batman', 'football', 'baseball',
        'basketball', 'soccer', 'princess', 'flower', 'sunshine',
        'rainbow', 'shadow', 'silver', 'golden', 'winner', 'login',
        'pass', 'secret', 'trustno1', 'Password', 'Password1', 'Admin123',
        'Test123', 'Welcome1', '12345678', '1234567', '123123', '111111',
        '000000', 'password!', 'pass123', 'admin123', 'root123',
        'guest123', 'test123'
    );

    /**
     * Validate password strength
     *
     * @param string $password The password to validate
     * @param string $username The username to check against
     * @return bool|array True if strong, error details if weak
     */
    public static function validate_password($password, $username = '') {
        $password = trim($password);
        $errors = array();

        // Check minimum length
        if (strlen($password) < self::MIN_LENGTH) {
            $errors[] = sprintf('Password must be at least %d characters long', self::MIN_LENGTH);
        }

        // Check if password equals username (case-insensitive)
        if (!empty($username) && strtolower($password) === strtolower($username)) {
            $errors[] = 'Password cannot be the same as your username';
        }

        // Check for blocked words
        foreach (self::$blocked_words as $word) {
            if (stripos($password, $word) !== false) {
                $errors[] = 'Password cannot contain common words like "' . $word . '"';
                break; // Only report first match
            }
        }

        // Check character repetition (3+ consecutive same characters)
        if (preg_match('/(.)\1{2,}/', $password)) {
            $errors[] = 'Password cannot contain the same character repeated 3 or more times';
        }

        // Check for sequential characters
        $sequential_error = self::check_sequential_characters($password);
        if ($sequential_error) {
            $errors[] = $sequential_error;
        }

        // Check for required character types
        $character_errors = self::check_character_requirements($password);
        if (!empty($character_errors)) {
            $errors = array_merge($errors, $character_errors);
        }

        // Check against common weak passwords
        if (in_array(strtolower($password), array_map('strtolower', self::$weak_passwords))) {
            $errors[] = 'This password is commonly used and not secure';
        }

        // Return result
        if (empty($errors)) {
            return true;
        }

        return array(
            'valid' => false,
            'errors' => $errors
        );
    }

    /**
     * Check for sequential characters (alphabetic and numeric)
     *
     * @param string $password The password to check
     * @return string|false Error message or false if valid
     */
    private static function check_sequential_characters($password) {
        $length = strlen($password);
        $last = null;
        $sequence_length = 1;
        $alphabetic = true;

        for ($i = 0; $i < $length; $i++) {
            $current = ord(strtolower($password[$i]));
            
            if ($last !== null) {
                if (abs($current - $last) === 1) {
                    // Check if current character maintains alphabetic sequence
                    $alphabetic &= ctype_alpha($password[$i]);
                    
                    if (++$sequence_length > ($alphabetic ? 3 : 2)) {
                        if ($alphabetic) {
                            return 'Password cannot contain more than 3 consecutive alphabetic characters (e.g., "abcd")';
                        } else {
                            return 'Password cannot contain more than 2 consecutive numeric characters (e.g., "123")';
                        }
                    }
                } else {
                    // Reset sequence tracking
                    $sequence_length = 1;
                    $alphabetic = true;
                }
            }
            $last = $current;
        }

        return false;
    }

    /**
     * Check character type requirements
     *
     * @param string $password The password to check
     * @return array Array of error messages
     */
    private static function check_character_requirements($password) {
        $errors = array();
        
        $character_types = array(
            '/[a-z]/' => 'Password must contain at least one lowercase letter',
            '/[A-Z]/' => 'Password must contain at least one uppercase letter',
            '/[0-9]/' => 'Password must contain at least one number',
            '/[^a-zA-Z0-9]/' => 'Password must contain at least one special character (!@#$%^&*)'
        );

        foreach ($character_types as $pattern => $error_message) {
            if (!preg_match($pattern, $password)) {
                $errors[] = $error_message;
            }
        }

        return $errors;
    }

    /**
     * Get password strength requirements as formatted text
     *
     * @return string HTML formatted requirements list
     */
    public static function get_requirements_text() {
        return '<ul class="vmpfence-password-requirements">
            <li>At least ' . self::MIN_LENGTH . ' characters</li>
            <li>Uppercase and lowercase letters</li>
            <li>At least one number</li>
            <li>At least one special character</li>
            <li>Cannot be the same as your username</li>
            <li>Avoid common words or sequences of letters/numbers</li>
            <li>Cannot contain the same character repeated 3+ times</li>
        </ul>';
    }

    /**
     * Check if enforcement should apply to user
     *
     * @param int|WP_User $user User ID or user object
     * @param string $scope Enforcement scope ('recommended' or 'all')
     * @return bool True if enforcement applies
     */
    public static function should_enforce_for_user($user, $scope = 'recommended') {
        if (is_numeric($user)) {
            $user_id = $user;
        } elseif (is_object($user) && isset($user->ID)) {
            $user_id = $user->ID;
        } else {
            return false;
        }

        switch ($scope) {
            case 'all':
                return true;
                
            case 'recommended':
            default:
                // Enforce for users who can publish posts (admins, editors, authors)
                return user_can($user_id, 'publish_posts');
        }
    }

    /**
     * Generate error message for validation failure
     *
     * @param array $errors Array of error messages
     * @return string Formatted error message
     */
    public static function format_error_message($errors) {
        if (empty($errors)) {
            return '';
        }

        $message = '<strong>ERROR:</strong> The password could not be changed. Please choose a stronger password and try again. A strong password will follow these guidelines:';
        $message .= self::get_requirements_text();

        if (!empty($errors)) {
            $message .= '<p><strong>Issues found:</strong></p><ul>';
            foreach ($errors as $error) {
                $message .= '<li>' . esc_html($error) . '</li>';
            }
            $message .= '</ul>';
        }

        return $message;
    }
}