<?php
/**
 * FeAccount class.
 *
 * @since 2.0
 */

declare(strict_types=1);
namespace FeData\FastEvents\Accounts;

use FeData\FastEvents\Storage\UserMeta;
use FeData\FastEvents\Utilities\UniqIds;
use stdClass;
use WP_Application_Passwords;
use WP_Error;
use WP_REST_Request;

defined( 'ABSPATH' ) || exit;

/**
 * Invoice class.
 */
class FeAccount {

	/**
	 * The unique user ID (WP_User).
	 *
	 * @since 2.0
	 * @var int
	 */
	private int $ID;

	/**
	 * The application password (only used during creation).
	 *
	 * @since 2.0
	 * @var string
	 */
	private string $application_pwd = '';

	/**
	 * The user_login name (WP_User).
	 *
	 * @since 2.0
	 * @var string
	 */
	private string $user_login;

	/**
	 * The user_email name (WP_User).
	 *
	 * @since 2.0
	 * @var string
	 */
	private string $user_email;

	/**
	 * The display name of the user (WP_User).
	 *
	 * @since 2.0
	 * @var string
	 */
	private string $display_name;

	/**
	 * The uuid of the application password.
	 *
	 * @since 2.0
	 * @var string
	 */
	public string $uuid;

	/**
	 * Is the user allowed to log in to the admin dashboard of WordPress?
	 * This will also disable password resets.
	 *
	 * @since 2.0
	 * @var bool
	 */
	public bool $disable_login = true;

	/**
	 * Are the user and all sub-accounts blocked for REST calls?
	 *
	 * @since 2.0
	 * @var bool
	 */
	public bool $blocked = false;

	/**
	 * The REST API key for this user and all sub-accounts.
	 *
	 * @since 2.0
	 * @var string
	 */
	public string $api_key;

	/**
	 * A comma separated list of REST API endpoints.
	 *
	 * @since 2.0
	 * @var string
	 */
	public string $allowed_endpoints = '/fast-events/v1';

	/**
	 * A comma separated list of event ids. Sub-accounts can either use all or a subset.
	 *
	 * @since 2.0
	 * @var string
	 */
	public string $allowed_events = '';

	/**
	 * How many sub-accounts can this user create? This is basically the number of additional application passwords.
	 *
	 * @since 2.0
	 * @var int
	 */
	public int $max_sub_accounts = 0;

	/**
	 * List of permissions. Every byte is a 0 or 1 for a specific permission (see 'class-fast-events-auth' for the definition).
	 *
	 * @since 2.0
	 * @var string
	 */
	public string $permission_list;

	/**
	 * Array of sub-accounts.
	 *
	 * @since 2.0
	 * @var array
	 */
	public array $sub_accounts = array();

	/**
	 * Account constructor.
	 *
	 * @since 2.0
	 * @since 2.5.0 Optionally use encryption.
	 *
	 * @param int    $user_id If this is not '0' get the user.
	 * @param string $user    The unique username.
	 * @param string $email   The user emailaddress.
	 * @param string $name    The display name.
	 */
	public function __construct( int $user_id, string $user = '', string $email = '', string $name = '' ) {
		if ( 0 !== $user_id ) {
			$result = $this->get_account( $user_id );
			if ( false === $result ) {
				$this->ID = 0;
			} elseif ( ! is_null( $result ) ) {
				$this->uuid              = $result->uuid;
				$this->disable_login     = (bool) $result->disable_login;
				$this->blocked           = (bool) $result->blocked;
				$this->api_key           = $result->api_key;
				$this->allowed_endpoints = $result->allowed_endpoints;
				$this->allowed_events    = $result->allowed_events;
				$this->max_sub_accounts  = (int) $result->max_sub_accounts;
				$this->permission_list   = $result->permission_list;
				foreach ( (array) $result->sub_accounts as $item ) {
					$sub_account                  = new FeSubAccount();
					$sub_account->uuid            = $item->uuid;
					$sub_account->blocked         = (bool) $item->blocked;
					$sub_account->allowed_events  = $item->allowed_events;
					$sub_account->permission_list = $item->permission_list;
					$this->sub_accounts[]         = $sub_account;
				}
			}
		} else {
			$this->user_login   = $user;
			$this->user_email   = $email;
			$this->display_name = $name;

			$this->api_key         = UniqIds::uniq_id();
			$this->permission_list = str_repeat( '0', 100 );
			$this->sub_accounts    = array();
		}
	}


	/**
	 * Return the unique user ID
	 *
	 * @since 2.0
	 *
	 * @return int
	 */
	public function get_user_id(): int {
		return $this->ID;
	}


	/**
	 * Return the application password
	 *
	 * @since 2.0
	 *
	 * @return string
	 */
	public function get_application_pwd(): string {
		return $this->application_pwd;
	}


	/**
	 * Return the user_login name
	 *
	 * @since 2.0
	 *
	 * @return string
	 */
	public function get_user_login(): string {
		return $this->user_login;
	}


	/**
	 * Return the user_email address
	 *
	 * @since 2.0
	 *
	 * @return string
	 */
	public function get_user_email(): string {
		return $this->user_email;
	}


	/**
	 * Return the user display name
	 *
	 * @since 2.0
	 *
	 * @return string
	 */
	public function get_display_name(): string {
		return $this->display_name;
	}


	/**
	 * Create the user account
	 *
	 * @since 2.0
	 *
	 * @param WP_REST_Request $request The REST API request.
	 *
	 * @return bool|WP_Error
	 */
	public function create_account( WP_REST_Request $request ): bool|WP_Error {

		// Create the user.
		$result = wp_insert_user(
			array(
				'user_login'   => $this->user_login,
				'display_name' => $this->display_name,
				'user_pass'    => wp_generate_password( 20 ),
				'user_email'   => $this->user_email,
				'role'         => 'fe_account',
			)
		);
		if ( is_wp_error( $result ) ) {
			return $result;
		}
		$this->ID = (int) $result;

		// Create the application password.
		$apw = WP_Application_Passwords::create_new_application_password(
			$this->ID,
			array( 'name' => 'Fast Events - ' . $this->user_login . ' - ' . UniqIds::uniq_field() )
		);
		if ( is_wp_error( $apw ) ) {
			return $apw;
		}
		$this->application_pwd = $apw[0];
		$this->uuid            = $apw[1]['uuid'];

		$this->set_permission_info( $request );
		$this->save();
		return true;
	}


	/**
	 * Update the WP_User display name
	 *
	 * @since 2.0
	 *
	 * @param string $name The new name.
	 *
	 * @return int|WP_Error The result of the method.
	 */
	public function update_user_display_name( string $name ): int|WP_Error {
		$result = wp_update_user(
			array(
				'ID'           => $this->ID,
				'display_name' => $name,
			)
		);

		$this->display_name = $name;
		return $result;
	}


	/**
	 * Update the WP_User user emailaddress
	 *
	 * @since 2.0
	 *
	 * @param string $email The new emailaddress.
	 *
	 * @return int|WP_Error The result of the method.
	 */
	public function update_user_email( string $email ): int|WP_Error {
		$result = wp_update_user(
			array(
				'ID'         => $this->ID,
				'user_email' => $email,
			)
		);

		$this->user_email = $email;
		return $result;
	}


	/**
	 * Check if the permissions structure (metadata) and application password still exists.
	 * If not, create a new one
	 *
	 * @since 2.0
	 */
	public function permissions_exists_for_account(): void {
		if ( empty( $this->uuid ) || is_null( WP_Application_Passwords::get_user_application_password( $this->ID, $this->uuid ) ) ) {
			if ( ! isset( $this->api_key ) ) {
				$this->api_key         = UniqIds::uniq_id();
				$this->permission_list = str_repeat( '0', 100 );
				$this->sub_accounts    = array();
			}
			$apw                   = WP_Application_Passwords::create_new_application_password( $this->ID, array( 'name' => 'Fast Events - ' . $this->user_login . ' - ' . UniqIds::uniq_field() ) );
			$this->application_pwd = $apw[0];
			$this->uuid            = $apw[1]['uuid'];
		}
	}


	/**
	 * Update account
	 *
	 * @since 2.0
	 * @since 2.3.0 Use fast_events_json_encode.
	 * @since 2.5.0 Use optional encryption.
	 * @since 3.0.0 Moved 'save' to UserMeta class.
	 */
	public function save(): void {
		global $fevt_container;

		$fevt_container->get( UserMeta::class )->set_user_api_permissions( $this->ID, $this );
	}


	/**
	 * Delete the account
	 *
	 * @since 2.0
	 */
	public function delete(): void {
		if ( is_multisite() ) {
			require_once ABSPATH . 'wp-admin/includes/ms.php';
			wpmu_delete_user( $this->ID );
		} else {
			require_once ABSPATH . 'wp-admin/includes/user.php';
			wp_delete_user( $this->ID );
		}
	}


	/**
	 * Set properties from Rest request
	 *
	 * @since 2.0
	 *
	 * @param WP_REST_Request $request The REST API request.
	 */
	public function set_permission_info( WP_REST_Request $request ): void {
		if ( isset( $request['disable_login'] ) ) {
			$this->disable_login = $request['disable_login'];
		}

		if ( isset( $request['blocked'] ) ) {
			$this->blocked = $request['blocked'];
		}

		if ( isset( $request['api_key'] ) ) {
			$this->api_key = $request['api_key'];
		}

		if ( isset( $request['allowed_endpoints'] ) ) {
			$this->allowed_endpoints = $request['allowed_endpoints'];
		}

		// Check the sub-accounts if they have a (full)subset of the allowed events on the account level.
		if ( isset( $request['allowed_events'] ) && ! empty( $request['allowed_events'] ) ) {
			$this->allowed_events = $request['allowed_events'];
			$account_events       = explode( ',', $this->allowed_events );
			foreach ( $this->sub_accounts as $key  => $sub_account ) {
				if ( ! empty( $sub_account->allowed_events ) ) {
					$sub_events = explode( ',', $sub_account->allowed_events );
					$new_events = array();
					foreach ( $sub_events as $sub_event ) {
						if ( in_array( $sub_event, $account_events, true ) ) {
							$new_events[] = $sub_event;
						}
					}
					$this->sub_accounts[ $key ]->allowed_events = implode( ',', $new_events );
				}
			}
		}

		// Also check if sub-accounts need to be changed.
		// If sub-accounts permissions are still enabled and the account permission is not, switch it off.
		if ( isset( $request['permission_list'] ) ) {
			$this->permission_list = $request['permission_list'];
			foreach ( $this->sub_accounts as $key  => $sub_account ) {
				if ( isset( $sub_account->permission_list ) && 100 === strlen( $sub_account->permission_list ) ) {
					for ( $i = 0; $i < 100; $i++ ) {
						if ( '1' === $sub_account->permission_list[ $i ] && '0' === $this->permission_list[ $i ] ) {
							$sub_account->permission_list[ $i ] = '0';
						}
					}
					$this->sub_accounts[ $key ]->permission_list = $sub_account->permission_list;
				}
			}
		}

		if ( isset( $request['max_sub_accounts'] ) ) {
			$this->max_sub_accounts = $request['max_sub_accounts'];
		}
	}


	/**
	 * Get the account class info
	 *
	 * @since 2.0
	 *
	 * @param int $user_id   The unique user id.
	 *
	 * @return bool|stdClass|null The result of the method.
	 */
	public function get_account( int $user_id ): bool|stdClass|null {
		global $fevt_container;

		// Look up the user.
		$result = get_user_by( 'id', $user_id );
		if ( false === $result ) {
			return false;
		}

		// Set the private vars.
		$this->ID           = $result->ID;
		$this->user_login   = $result->user_login;
		$this->uuid         = $result->uuid;
		$this->display_name = $result->display_name;
		$this->user_email   = $result->user_email;

		return $fevt_container->get( UserMeta::class )->get_user_api_permissions( $user_id );
	}
}
