<?php
/**
 * Handles all the functionality related to Blocking and Unblocking of User
 *
 * Provides functionality to block/unblock users,
 * prevent blocked users from logging in, force logout,
 * and help modify user queries & admin display.
 *
 * @package Author_Kit
 * @since 1.0.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class Author_Kit_Block_User
 *
 * Provides functionality to block/unblock users,
 * prevent blocked users from logging in, force logout,
 * and help modify user queries & admin display.
 */
class Author_Kit_Block_User {


	/**
	 * Meta key used to store blocked status.
	 */
	const META_KEY = 'author_kit_is_user_blocked';

	/**
	 * Constructor.
	 *
	 * Registers hooks for:
	 * - Preventing login for blocked users.
	 * - Adding a custom column to the users list.
	 */
	public function __construct() {
		// Prevent blocked users from logging in.
		add_filter( 'wp_authenticate_user', array( $this, 'prevent_login_for_blocked_user' ), 10, 2 );

		// Add a column in the Users table for blocked status.
		add_filter( 'manage_users_columns', array( $this, 'add_blocked_status_column' ) );
		add_filter( 'manage_users_custom_column', array( $this, 'render_blocked_status_column' ), 10, 3 );
	}

	/**
	 * Block a single user.
	 *
	 * Checks if user exists and is not an administrator.
	 * Updates the user meta to "1" (blocked) and forces logout.
	 *
	 * @param int $user_id User ID to block.
	 * @return bool True if blocked successfully; false otherwise.
	 */
	public function block_user( $user_id ) {
		$user = get_userdata( $user_id );
		if ( ! $user ) {
			return false; // User does not exist.
		}
		// Do not block administrators.
		if ( in_array( 'administrator', (array) $user->roles, true ) ) {
			return false;
		}
		// If already blocked, do nothing.
		if ( $this->is_user_blocked( $user_id ) ) {
			return true;
		}
		// Set the meta value to "1" to mark as blocked.
		$result = update_user_meta( $user_id, self::META_KEY, '1' );
		if ( $result ) {
			delete_transient( 'author_kit_total_blocked_users' );
			// Force logout by destroying user sessions.
			$this->destroy_user_session( $user_id );
			return true;
		}
		return false;
	}

	/**
	 * Block multiple users by updating their user meta.
	 *
	 * @param array $user_ids List of user IDs to be blocked.
	 * @return array Associative array containing success status, message,
	 *               blocked user IDs, and failed user IDs.
	 */
	public function block_multiple_users( $user_ids ) {
		if ( ! is_array( $user_ids ) || empty( $user_ids ) ) {
			return array(
				'success'       => false,
				'message'       => __( 'No valid users selected for blocking.', 'author-kit' ),
				'blocked_users' => array(),
				'failed_users'  => array(),
			);
		}

		$blocked_users = array();
		$failed_users  = array();

		foreach ( $user_ids as $user_id ) {
			$result = $this->block_user( $user_id ); // Reusing single-user blocking function.
			if ( $result ) {
				$blocked_users[] = $user_id;
			} else {
				$failed_users[] = $user_id;
			}
		}

		// Determine message based on results.
		if ( empty( $blocked_users ) ) {
			$message = __( 'No users could be blocked.', 'author-kit' );
			$success = false;
		} elseif ( empty( $failed_users ) ) {
			$message = ( count( $blocked_users ) === 1 )
				? __( 'User has been blocked successfully.', 'author-kit' )
				: __( 'Users have been blocked successfully.', 'author-kit' );
			$success = true;
		} else {
			$message = __( 'Some users were blocked, but others could not be processed.', 'author-kit' );
			$success = false;
		}

		return array(
			'success'       => $success,
			'message'       => $message,
			'blocked_users' => $blocked_users,
			'failed_users'  => $failed_users,
		);
	}


	/**
	 * Unblock a single user.
	 *
	 * Removes the block meta.
	 *
	 * @param int $user_id User ID to unblock.
	 * @return bool True if unblocked successfully; false otherwise.
	 */
	public function unblock_user( $user_id ) {
		$user = get_userdata( $user_id );
		if ( ! $user ) {
			return false; // User does not exist.
		}
		// Removing the meta key; if not set, deletion returns false.
		$result = delete_user_meta( $user_id, self::META_KEY );

		delete_transient( 'author_kit_total_blocked_users' );

		return $result;
	}

	/**
	 * Unblock multiple users by removing their block status.
	 *
	 * @param array $user_ids List of user IDs to be unblocked.
	 * @return array Associative array containing success status, message,
	 *               unblocked user IDs, and failed user IDs.
	 */
	public function unblock_multiple_users( $user_ids ) {
		if ( ! is_array( $user_ids ) || empty( $user_ids ) ) {
			return array(
				'success'         => false,
				'message'         => __( 'No valid users selected for unblocking.', 'author-kit' ),
				'unblocked_users' => array(),
				'failed_users'    => array(),
			);
		}

		$unblocked_users = array();
		$failed_users    = array();

		foreach ( $user_ids as $user_id ) {
			$result = $this->unblock_user( $user_id ); // Reusing single-user unblocking function.
			if ( $result ) {
				$unblocked_users[] = $user_id;
			} else {
				$failed_users[] = $user_id;
			}
		}

		// Determine message based on results.
		if ( empty( $unblocked_users ) ) {
			$message = __( 'No users could be unblocked.', 'author-kit' );
			$success = false;
		} elseif ( empty( $failed_users ) ) {
			$message = ( count( $unblocked_users ) === 1 )
				? __( 'User has been unblocked successfully.', 'author-kit' )
				: __( 'Users have been unblocked successfully.', 'author-kit' );
			$success = true;
		} else {
			$message = __( 'Some users were unblocked, but others could not be processed.', 'author-kit' );
			$success = false;
		}

		return array(
			'success'         => $success,
			'message'         => $message,
			'unblocked_users' => $unblocked_users,
			'failed_users'    => $failed_users,
		);
	}


	/**
	 * Destroy all sessions for a given user.
	 *
	 * Forces a logout by destroying the user's session tokens.
	 *
	 * @param int $user_id User ID.
	 */
	public function destroy_user_session( $user_id ) {
		if ( class_exists( 'WP_Session_Tokens' ) ) {
			$sessions = WP_Session_Tokens::get_instance( $user_id );
			$sessions->destroy_all();
		}
	}

	/**
	 * Prevent blocked users from logging in.
	 *
	 * Hooked into wp_authenticate_user. If a user is blocked,
	 * returns a WP_Error to prevent login.
	 *
	 * @param WP_User|WP_Error $user User object or error.
	 * @param string           $password Password provided.
	 * @return WP_User|WP_Error
	 */
	public function prevent_login_for_blocked_user( $user, $password ) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
		// If there's already an error, do not continue.
		if ( is_wp_error( $user ) ) {
			return $user;
		}
		// If user is blocked, return a WP_Error.
		if ( $this->is_user_blocked( $user->ID ) ) {
			return new WP_Error( 'blocked_user', __( 'Your account is blocked.', 'author-kit' ) );
		}
		return $user;
	}

	/**
	 * Check if a user is blocked.
	 *
	 * @param int $user_id User ID.
	 * @return bool True if blocked; false otherwise.
	 */
	public function is_user_blocked( $user_id ) {
		return ( '1' === get_user_meta( $user_id, self::META_KEY, true ) );
	}

	/**
	 * Add a custom column to the Users list table.
	 *
	 * @param array $columns Existing columns.
	 * @return array Modified columns.
	 */
	public function add_blocked_status_column( $columns ) {
		// Insert our column after the 'username' column.
		$new_columns = array();
		foreach ( $columns as $key => $value ) {
			$new_columns[ $key ] = $value;
			if ( 'username' === $key ) {
				$new_columns['blocked_status'] = __( 'Blocked', 'author-kit' );
			}
		}
		return $new_columns;
	}

	/**
	 * Render content for the custom "Blocked" column.
	 *
	 * @param mixed  $value Current column value.
	 * @param string $column_name Name of the column.
	 * @param int    $user_id User ID.
	 * @return string Column output.
	 */
	public function render_blocked_status_column( $value, $column_name, $user_id ) {
		if ( 'blocked_status' !== $column_name ) {
			return $value;
		}
		// Administrators should always show as "N/A".
		$user = get_userdata( $user_id );
		if ( in_array( 'administrator', (array) $user->roles, true ) ) {
			return 'N/A';
		}
		return ( $this->is_user_blocked( $user_id ) ) ? __( 'Blocked', 'author-kit' ) : __( 'Active', 'author-kit' );
	}

	/**
	 * Returns a meta query array to exclude blocked users from a WP_User_Query.
	 *
	 * @return array Meta query array.
	 */
	public function get_meta_query_exclude_blocked() {
		return array(
			'relation' => 'OR',
			// Include users without the meta key.
			array(
				'key'     => self::META_KEY,
				'compare' => 'NOT EXISTS',
			),
			// …and users with a meta key value not equal to '1'.
			array(
				'key'     => self::META_KEY,
				'value'   => '1',
				'compare' => '!=',
			),
		);
	}

	/**
	 * Returns a meta query array to fetch only blocked users.
	 *
	 * @return array Meta query array.
	 */
	public function get_meta_query_only_blocked() {
		return array(
			array(
				'key'     => self::META_KEY,
				'value'   => '1',
				'compare' => '=',
			),
		);
	}

	/**
	 * Get the total count of blocked users.
	 *
	 * @return int Number of blocked users.
	 */
	public function get_total_blocked_users() {
		$cache_key    = 'author_kit_total_blocked_users';
		$cached_total = get_transient( $cache_key );

		if ( false !== $cached_total ) {
			return $cached_total;
		}

		$args = array(
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			'meta_query' => $this->get_meta_query_only_blocked(),
			'fields'     => 'ID',
			'number'     => -1, // Get all matching users.
		);

		$users = get_users( $args );
		$total = count( $users );

		set_transient( $cache_key, $total, HOUR_IN_SECONDS ); // Cache for 1 hour.

		return $total;
	}
}

// =============================================================================
// Initialization Example:
// =============================================================================

// Instantiate the class so that hooks are registered.
if ( class_exists( 'Author_Kit_Block_User' ) ) {
	$GLOBALS['author_kit_blocker'] = new Author_Kit_Block_User();
}
