<?php

defined( 'ABSPATH' ) || exit;

class ZACLS_Password_Policy {

	public function __construct() {
		$this->register_hooks();
	}

	private function register_hooks() {
		add_action( 'user_profile_update_errors', array( $this, 'validate_password' ), 10, 3 );
		add_action( 'validate_password_reset', array( $this, 'validate_password_reset' ), 10, 2 );
		add_action( 'after_password_reset', array( $this, 'record_password' ), 10, 2 );
		add_action( 'profile_update', array( $this, 'record_password_on_update' ), 10, 2 );
	}

	private function get_policy() {
		$defaults = array(
			'min_length'      => 8,
			'require_upper'   => 'yes',
			'require_lower'   => 'yes',
			'require_number'  => 'yes',
			'require_special' => 'yes',
			'history_count'   => 5,
			'expiration_days' => 0,
			'block_common'    => 'yes',
		);
		$policy   = get_option( 'zacls_password_policy', $defaults );
		return wp_parse_args( $policy, $defaults );
	}

	public function validate_password( $errors, $update, $user ) {
		if ( empty( $user->user_pass ) ) {
			return;
		}
		$policy  = $this->get_policy();
		$message = $this->check_password_strength( $user->user_pass, $policy, $user );
		if ( ! empty( $message ) ) {
			$errors->add( 'pass', $message );
		}
	}

	public function validate_password_reset( $errors, $user ) {
		if ( ! isset( $_POST['pass1'] ) ) {
			return;
		}
		$password = wp_unslash( $_POST['pass1'] );
		if ( empty( $password ) ) {
			return;
		}
		$policy  = $this->get_policy();
		$message = $this->check_password_strength( $password, $policy, $user );
		if ( ! empty( $message ) ) {
			$errors->add( 'pass', $message );
		}
	}

	public function record_password( $user, $new_data ) {
		self::save_password_hash( $user->ID, $new_data['user_pass'] ?? $user->user_pass );
	}

	public function record_password_on_update( $user_id, $old_user_data ) {
		if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) {
			self::save_password_hash( $user_id, wp_unslash( $_POST['pass1'] ) );
		}
	}

	public static function save_password_hash( $user_id, $password ) {
		global $wpdb;
		$wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prefix . 'zacls_password_history',
			array(
				'user_id'       => $user_id,
				'password_hash' => wp_hash_password( $password ),
			),
			array( '%d', '%s' )
		);

		$policy = get_option( 'zacls_password_policy', array() );
		$history_count = is_array( $policy ) && isset( $policy['history_count'] ) ? absint( $policy['history_count'] ) : 5;

		if ( $history_count < 1 ) {
			return;
		}

		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"DELETE FROM {$wpdb->prefix}zacls_password_history WHERE user_id = %d AND id NOT IN (
					SELECT id FROM (
						SELECT id FROM {$wpdb->prefix}zacls_password_history WHERE user_id = %d ORDER BY created_at DESC LIMIT %d
					) AS keep_ids
				)",
				$user_id,
				$user_id,
				$history_count
			)
		);
	}

	private function check_password_strength( $password, $policy, $user = null ) {
		$errors = array();

		$min_length = absint( $policy['min_length'] );
		if ( $min_length > 0 && strlen( $password ) < $min_length ) {
			$errors[] = sprintf( esc_html__( 'Password must be at least %d characters long.', 'za-creative-login-shield' ), $min_length );
		}

		if ( 'yes' === ( $policy['require_upper'] ?? 'yes' ) && ! preg_match( '/[A-Z]/', $password ) ) {
			$errors[] = esc_html__( 'Password must contain at least one uppercase letter.', 'za-creative-login-shield' );
		}

		if ( 'yes' === ( $policy['require_lower'] ?? 'yes' ) && ! preg_match( '/[a-z]/', $password ) ) {
			$errors[] = esc_html__( 'Password must contain at least one lowercase letter.', 'za-creative-login-shield' );
		}

		if ( 'yes' === ( $policy['require_number'] ?? 'yes' ) && ! preg_match( '/[0-9]/', $password ) ) {
			$errors[] = esc_html__( 'Password must contain at least one number.', 'za-creative-login-shield' );
		}

		if ( 'yes' === ( $policy['require_special'] ?? 'yes' ) && ! preg_match( '/[^a-zA-Z0-9]/', $password ) ) {
			$errors[] = esc_html__( 'Password must contain at least one special character.', 'za-creative-login-shield' );
		}

		if ( 'yes' === ( $policy['block_common'] ?? 'yes' ) ) {
			$common_passwords = array(
				'password', '123456', '12345678', 'qwerty', 'abc123', 'monkey',
				'letmein', 'dragon', '111111', 'baseball', 'iloveyou', 'trustno1',
				'sunshine', 'master', 'welcome', 'shadow', 'ashley', 'football',
				'jesus', 'michael', 'ninja', 'mustang', 'password1', 'admin',
			);
			if ( in_array( strtolower( $password ), $common_passwords, true ) ) {
				$errors[] = esc_html__( 'This password is too common. Please choose a more secure password.', 'za-creative-login-shield' );
			}
		}

		if ( $user && ! empty( $user->ID ) ) {
			$history_count = absint( $policy['history_count'] ?? 5 );
			if ( $history_count > 0 ) {
				global $wpdb;
				$history = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
					$wpdb->prepare(
						"SELECT password_hash FROM {$wpdb->prefix}zacls_password_history WHERE user_id = %d ORDER BY created_at DESC LIMIT %d",
						$user->ID,
						$history_count
					)
				);
				foreach ( $history as $hash ) {
					if ( wp_check_password( $password, $hash ) ) {
						$errors[] = sprintf( esc_html__( 'You cannot reuse a previous password. The last %d passwords are remembered.', 'za-creative-login-shield' ), $history_count );
						break;
					}
				}
			}
		}

		return ! empty( $errors ) ? implode( " \n", $errors ) : '';
	}
}
