<?php
/**
 * FE Admin Accounts controller.
 *
 * @since 2.0
 */

declare( strict_types=1 );
namespace FeData\FastEvents\RestAPI\Public;

defined( 'ABSPATH' ) || exit;

use FeData\FastEvents\Accounts\FeAccount;
use stdClass;
use WP_Application_Passwords;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;


/**
 * FE Admin Accounts controller class.
 */
class AccountsController extends WP_REST_Controller {

	/**
	 * Admin Accounts constructor.
	 *
	 * @since 2.0
	 */
	public function __construct() {
		$this->namespace = 'fast-events/v1';
		$this->rest_base = 'admin/accounts';

		// Check if the 'fe_account' role exists.
		if ( ! wp_roles()->is_role( 'fe_account' ) ) {
			add_role( 'fe_account', 'Fast Events user', array( 'read' => true ) );
		}
	}


	/**
	 * Register the routes for the objects of the controller.
	 *
	 * @since 2.0
	 */
	public function register_routes(): void {

		// Get ticket types.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'fast_events_admin_permissions' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'fast_events_admin_permissions' ),
					'args'                => $this->get_endpoint_args_for_item_schema(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'   => array(
					'id' => array(
						'description'       => __( 'The unique id of the user', 'fast-events' ),
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'fast_events_admin_permissions' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'fast_events_admin_permissions' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'fast_events_admin_permissions' ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)/reset_password',
			array(
				'args'   => array(
					'id' => array(
						'description'       => __( 'The unique id of the user', 'fast-events' ),
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'reset_password' ),
					'permission_callback' => array( $this, 'fast_events_admin_permissions' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}


	/**
	 * Only administrators are allowed to use these endpoints.
	 *
	 * @return WP_Error|bool
	 *
	 * @since 2.0
	 */
	public function fast_events_admin_permissions(): WP_Error|bool {
		return is_user_logged_in() && in_array( 'administrator', wp_get_current_user()->roles, true );
	}


	/**
	 * Get all 'fe_account' users.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return WP_REST_Response|WP_Error|array
	 *
	 * @since 2.0
	 */
	public function get_items( $request ): WP_Error|WP_REST_Response|array {
		$args  = array(
			'role'    => 'fe_account',
			'orderby' => 'user_nicename',
			'order'   => 'ASC',
		);
		$users = get_users( $args );

		$user_info = array();
		foreach ( $users as $user ) {
			$item        = new FeAccount( $user->ID );
			$user_info[] = $this->prepare_response_for_collection(
				$this->prepare_item_for_response( $item, $request )
			);
		}

		$response = rest_ensure_response( $user_info );

		// Generate the totals headers.
		$response->header( 'X-WP-Total', count( $user_info ) );
		$response->header( 'X-WP-TotalPages', 1 );

		return $response;
	}


	/**
	 * Retrieve a single 'fe_account' user.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|array Response object on success, or WP_Error object on failure.
	 *
	 * @since 2.0
	 */
	public function get_item( $request ): WP_Error|WP_REST_Response|array {

		// Get the account info.
		$user = new FeAccount( $request['id'] );
		if ( 0 === $user->get_user_id() ) {
			return new WP_Error( 'not_found', __( 'User not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		return rest_ensure_response( $this->prepare_item_for_response( $user, $request ) );
	}


	/**
	 * Create a new 'fe_account' user.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array|stdClass Response object on success, or WP_Error object on failure.
	 *
	 * @since 2.0
	 */
	public function create_item( $request ): WP_Error|WP_REST_Response|bool|array|stdClass {

		// Validate username and email.
		$username = strtolower( $request['username'] );
		$email    = $request['email'];
		if ( ! validate_username( $username ) ) {
			return new WP_Error(
				'fe_account_invalid_username',
				__( 'Sorry, that username is not allowed.', 'fast-events' ),
				array( 'status' => 400 )
			);
		}
		if ( ! is_email( $email ) ) {
			return new WP_Error(
				'fe_account_invalid_email',
				__( 'Sorry, that emailaddress is not allowed.', 'fast-events' ),
				array( 'status' => 400 )
			);
		}

		// Create the user.
		$account = new FeAccount( 0, $username, $email, $request['name'] );
		$result  = $account->create_account( $request );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		$request->set_param( 'context', 'view' );

		return rest_ensure_response( $this->prepare_item_for_response( $account, $request ) );
	}


	/**
	 * Update a single 'fe_account' user.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array|stdClass Response object on success, or WP_Error object on failure.
	 *
	 * @since 2.0
	 */
	public function update_item( $request ): WP_Error|WP_REST_Response|bool|array|stdClass {

		// Get the account info.
		$account = new FeAccount( $request['id'] );
		if ( 0 === $account->get_user_id() ) {
			return new WP_Error( 'not_found', __( 'User not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		// Did the name change?
		if ( isset( $request['name'] ) && $request['name'] !== $account->get_display_name() ) {
			$result = $account->update_user_display_name( $request['name'] );
			if ( is_wp_error( $result ) ) {
				return $result;
			}
		}

		// Did the email change?
		if ( isset( $request['email'] ) && $request['email'] !== $account->get_user_email() ) {
			$result = $account->update_user_email( $request['email'] );
			if ( is_wp_error( $result ) ) {
				return $result;
			}
		}

		// Do the permission structure and application password still exist?
		$account->permissions_exists_for_account();

		// Update the other info.
		$account->set_permission_info( $request );
		$account->save();

		$request->set_param( 'context', 'edit' );

		return rest_ensure_response( $this->prepare_item_for_response( $account, $request ) );
	}


	/**
	 * Reset the password of a 'fe_account' user.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array|stdClass Response object on success, or WP_Error object on failure.
	 *
	 * @since 2.0
	 */
	public function reset_password( WP_REST_Request $request ): WP_Error|WP_REST_Response|bool|array|stdClass {

		// Get the account info.
		$id      = $request['id'];
		$account = new FeAccount( $id );
		if ( 0 === $account->get_user_id() || ! isset( $account->api_key ) ) {
			return new WP_Error( 'not_found', __( 'Account not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		// Is there still an uuid from the application password?
		if ( ! empty( $account->uuid ) && ! is_null( WP_Application_Passwords::get_user_application_password( $id, $account->uuid ) ) ) {
			WP_Application_Passwords::delete_application_password( $id, $account->uuid );
		}
		$account->permissions_exists_for_account();
		$account->save();

		return rest_ensure_response( $this->prepare_item_for_response( $account, $request ) );
	}


	/**
	 * Delete a single 'fe_account' user.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array|stdClass Response object on success, or WP_Error object on failure.
	 *
	 * @since 2.0
	 */
	public function delete_item( $request ): WP_Error|WP_REST_Response|bool|array|stdClass {

		// Get the account info.
		$old_data = new FeAccount( $request['id'] );
		if ( 0 === $old_data->get_user_id() ) {
			return new WP_Error( 'not_found', __( 'User not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		$request->set_param( 'context', 'edit' );

		$previous = $this->prepare_item_for_response( $old_data, $request );

		$old_data->delete();

		$response = new WP_REST_Response();
		$response->set_data(
			array(
				'deleted'  => true,
				'previous' => $previous->get_data(),
			)
		);

		return $response;
	}


	/**
	 * Retrieves the query params for the collections.
	 *
	 * @return array Query parameters for the collection.
	 *
	 * @since 2.0
	 */
	public function get_collection_params(): array {
		return array(
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
		);
	}


	/**
	 * Prepares a single ticket type output for response.
	 *
	 * @param mixed|FeAccount $item Fast_Events_Account object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 *
	 * @since 2.0
	 */
	public function prepare_item_for_response( $item, $request ): WP_REST_Response {

		$data   = array();
		$fields = $this->get_fields_for_response( $request );

		if ( rest_is_field_included( 'id', $fields ) ) {
			$data['id'] = $item->get_user_id();
		}

		if ( rest_is_field_included( 'username', $fields ) ) {
			$data['username'] = $item->get_user_login();
		}

		if ( rest_is_field_included( 'name', $fields ) ) {
			$data['name'] = $item->get_display_name();
		}

		if ( rest_is_field_included( 'email', $fields ) ) {
			$data['email'] = $item->get_user_email();
		}

		if ( rest_is_field_included( 'application_pwd', $fields ) && ! empty( $item->get_application_pwd() ) ) {
			$data['application_pwd'] = WP_Application_Passwords::chunk_password( $item->get_application_pwd() );
		}

		if ( isset( $item->api_key ) ) {
			if ( rest_is_field_included( 'disable_login', $fields ) ) {
				$data['disable_login'] = $item->disable_login;
			}

			if ( rest_is_field_included( 'blocked', $fields ) ) {
				$data['blocked'] = $item->blocked;
			}

			if ( rest_is_field_included( 'api_key', $fields ) ) {
				$data['api_key'] = $item->api_key;
			}

			if ( rest_is_field_included( 'allowed_endpoints', $fields ) ) {
				$data['allowed_endpoints'] = $item->allowed_endpoints;
			}

			if ( rest_is_field_included( 'allowed_events', $fields ) ) {
				$data['allowed_events'] = $item->allowed_events;
			}

			if ( rest_is_field_included( 'max_sub_accounts', $fields ) ) {
				$data['max_sub_accounts'] = $item->max_sub_accounts;
			}

			if ( rest_is_field_included( 'total_sub_accounts', $fields ) ) {
				$data['total_sub_accounts'] = count( $item->sub_accounts );
			}

			if ( rest_is_field_included( 'permission_list', $fields ) ) {
				$data['permission_list'] = $item->permission_list;
			}
		}

		if ( isset( $item->uuid ) ) {
			$result = WP_Application_Passwords::get_user_application_password( $item->get_user_id(), $item->uuid );

			if ( rest_is_field_included( 'created', $fields ) && ! is_null( $result ) ) {
				$data['created'] = $result['created'] ? gmdate( 'Y-m-d\TH:i:s', $result['created'] ) : '';
			}

			if ( rest_is_field_included( 'last_used', $fields ) && ! is_null( $result ) ) {
				$data['last_used'] = $result['last_used'] ? gmdate( 'Y-m-d', $result['last_used'] ) : '';
			}

			if ( rest_is_field_included( 'last_ip', $fields ) && ! is_null( $result ) ) {
				$data['last_ip'] = $result['last_ip'] ?? '';
			}
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'embed';

		$data = $this->add_additional_fields_to_object( $data, $request );
		$data = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $item->get_user_id() ) );
		}

		return $response;
	}


	/**
	 * Prepares links for the accounts request.
	 *
	 * @param int $user_id The unique user id.
	 * @return array Links for the given order.
	 *
	 * @since 2.0
	 */
	protected function prepare_links( int $user_id ): array {
		return array(
			'self'         => array(
				'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user_id ) ),
			),
			'collection'   => array(
				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
			),
			'sub_accounts' => array(
				'href' => rest_url( sprintf( '%s/%s/%d/sub_accounts', $this->namespace, $this->rest_base, $user_id ) ),
			),
		);
	}


	/**
	 * Return the accounts' schema.
	 *
	 * @return array
	 *
	 * @since 2.0
	 */
	public function get_item_schema(): array {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'accounts',
			'type'       => 'object',
			'properties' => array(
				'id'                 => array(
					'description' => __( 'The unique id of the user.', 'fast-events' ),
					'context'     => array( 'view', 'embed', 'edit' ),
					'type'        => 'string',
					'readonly'    => true,
				),
				'username'           => array(
					'description' => __( 'The username of the user.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'required'    => true,
					'minLength'   => 4,
					'maxLength'   => 50,
					'readonly'    => true,
				),
				'name'               => array(
					'description' => __( 'The name of the user.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'required'    => true,
					'minLength'   => 4,
					'maxLength'   => 50,
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'email'              => array(
					'description' => __( 'The emailaddress of the user.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'required'    => true,
					'format'      => 'email',
					'maxLength'   => 100,
				),
				'application_pwd'    => array(
					'description' => __( 'The application password.', 'fast-events' ),
					'context'     => array( 'view', 'edit' ),
					'type'        => 'string',
					'readonly'    => true,
				),
				'disable_login'      => array(
					'description' => __( 'Disabling the WordPress dashboard login.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'boolean',
					'default'     => true,
				),
				'blocked'            => array(
					'description' => __( 'Is the user blocked.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'boolean',
					'default'     => false,
				),
				'api_key'            => array(
					'description' => __( 'The REST API key.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'pattern'     => '^[\w]{16}$',
				),
				'allowed_endpoints'  => array(
					'description' => __( 'Comma separated list of allowed endpoints.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'maxLength'   => 256,
					'default'     => '/fast-events/v1',
				),
				'allowed_events'     => array(
					'description' => __( 'Comma separated list of allowed event ids.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'maxLength'   => 128,
					'pattern'     => '^\d*(,\d+)*$',
				),
				'max_sub_accounts'   => array(
					'description' => __( 'The maximum number of sub-account that can be created.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'integer',
					'default'     => 0,
					'arg_options' => array(
						'sanitize_callback' => 'absint',
					),
				),
				'total_sub_accounts' => array(
					'description' => __( 'The number of sub-accounts.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'integer',
					'readonly'    => true,
				),
				'permission_list'    => array(
					'description' => __( 'List of permissions - every byte is a 0 or 1.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'pattern'     => '^[0-1]{100}$',
				),
				'created'            => array(
					'description' => __( 'The GMT date the application password was created.' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'last_used'          => array(
					'description' => __( 'The GMT date the application password was last used.' ),
					'type'        => array( 'string', 'null' ),
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'last_ip'            => array(
					'description' => __( 'The IP address the application password was last used by.' ),
					'type'        => array( 'string', 'null' ),
					'format'      => 'ip',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
			),
		);

		return $this->add_additional_fields_schema( $this->schema );
	}
}
