<?php
/**
 * Class used to define Password Manager.
 *
 * @package automattic/jetpack-account-protection
 */

namespace Automattic\Jetpack\Account_Protection;

/**
 * Class Password_Manager
 */
class Password_Manager {
	/**
	 * Validaton service instance
	 *
	 * @var Validation_Service
	 */
	private $validation_service;

	/**
	 * Validation_Service constructor.
	 *
	 * @param ?Validation_Service $validation_service Password manager instance.
	 */
	public function __construct( ?Validation_Service $validation_service = null ) {
		$this->validation_service = $validation_service ?? new Validation_Service();
	}

	/**
	 * Validate the profile update.
	 *
	 * @param \WP_Error $errors The error object.
	 * @param bool      $update Whether the user is being updated.
	 * @param \stdClass $user A copy of the new user object.
	 *
	 * @return void
	 */
	public function validate_profile_update( \WP_Error $errors, bool $update, \stdClass $user ): void {
		if ( empty( $user->user_pass ) ) {
			return;
		}

		// If bypass is enabled, do not validate the password
		// phpcs:ignore WordPress.Security.NonceVerification
		if ( isset( $_POST['pw_weak'] ) && 'on' === $_POST['pw_weak'] ) {
			return;
		}

		$core_validation_errors    = $errors->get_error_messages( 'pass' );
		$jetpack_validation_errors = $this->validation_service->get_validation_errors( $user->user_pass, true, $user );
		$validation_errors         = array_diff( $jetpack_validation_errors, $core_validation_errors );

		foreach ( $validation_errors as $validation_error ) {
			$errors->add( 'pass', $validation_error, array( 'form-field' => 'pass1' ) );
		}
	}

	/**
	 * Validate the password reset.
	 *
	 * @param \WP_Error          $errors The error object.
	 * @param \WP_User|\WP_Error $user The user object.
	 *
	 * @return void
	 */
	public function validate_password_reset( \WP_Error $errors, $user ): void {
		if ( is_wp_error( $user ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		if ( empty( $_POST['pass1'] ) ) {
			return;
		}

		// If bypass is enabled, do not validate the password
		// phpcs:ignore WordPress.Security.NonceVerification
		if ( isset( $_POST['pw_weak'] ) && 'on' === $_POST['pw_weak'] ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$password = wp_unslash( $_POST['pass1'] );

		$core_validation_errors    = $errors->get_error_messages( 'pass' );
		$jetpack_validation_errors = $this->validation_service->get_validation_errors( $password );
		$validation_errors         = array_diff( $jetpack_validation_errors, $core_validation_errors );

		foreach ( $validation_errors as $validation_error ) {
			$errors->add( 'pass', $validation_error, array( 'form-field' => 'pass1' ) );
		}
	}

	/**
	 * Handle the profile update.
	 *
	 * @param int                     $user_id The user ID.
	 * @param \WP_User|\stdClass|null $old_user_data Object containing user data prior to update.
	 *
	 * @return void
	 */
	public function on_profile_update( int $user_id, $old_user_data ): void {
		if ( ! is_object( $old_user_data ) || empty( $old_user_data->user_pass ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		if ( isset( $_POST['action'] ) && $_POST['action'] === 'update' ) {
			$this->save_recent_password_hash( $user_id, $old_user_data->user_pass );
		}
	}

	/**
	 * Handle the password reset.
	 *
	 * @param \WP_User|\stdClass|null $user The user object.
	 *
	 * @return void
	 */
	public function on_password_reset( $user ): void {
		if ( ! is_object( $user ) || ! isset( $user->ID ) || empty( $user->user_pass ) ) {
			return;
		}

		$this->save_recent_password_hash( $user->ID, $user->user_pass );
	}

	/**
	 * Save the new password hash to the user's recent passwords list.
	 *
	 * @param int    $user_id  The user ID.
	 * @param string $password_hash The password hash to store.
	 *
	 * @return void
	 */
	public function save_recent_password_hash( int $user_id, string $password_hash ): void {
		$recent_passwords = get_user_meta( $user_id, Config::RECENT_PASSWORD_HASHES_USER_META_KEY, true );

		if ( ! is_array( $recent_passwords ) ) {
			$recent_passwords = array();
		}

		if ( in_array( $password_hash, $recent_passwords, true ) ) {
			return;
		}

		// Add the new hashed password and keep only the last 10
		array_unshift( $recent_passwords, $password_hash );
		$recent_passwords = array_slice( $recent_passwords, 0, Config::PASSWORD_MANAGER_RECENT_PASSWORDS_LIMIT );

		update_user_meta( $user_id, Config::RECENT_PASSWORD_HASHES_USER_META_KEY, $recent_passwords );
	}
}
