<?php
/*
 * Copyright (c) 2023 LatePoint LLC. All rights reserved.
 */

class OsRolesHelper {
	public static array $capabilities_for_controllers;

	public static function get_capabilities_for_all_controllers(){
		if(isset(self::$capabilities_for_controllers)) return self::$capabilities_for_controllers;
		$capabilities = include(LATEPOINT_CONFIG_ABSPATH . 'capabilities_for_controllers.php');

		/**
		 * Get array of capabilities for all controllers
		 *
		 * @since 4.7.0
		 * @hook latepoint_capabilities_for_controllers
		 *
		 * @param {array} $capabilities array of controllers with their default and per action capablities
		 * @returns {array} The filtered array of controllers with capability information
		 */
		self::$capabilities_for_controllers = apply_filters('latepoint_capabilities_for_controllers', $capabilities);
		return self::$capabilities_for_controllers;
	}

	public static function build_capability_name_from_model_action(string $model_class, string $action): string{
		$capability_names_for_models = [
			'OsActivityModel' => 'activity',
			'OsAgentModel' => 'agent',
			'OsAgentMetaModel' => 'agent',
			'OsOrderModel' => 'booking',
			'OsOrderIntentModel' => 'booking',
			'OsOrderMetaModel' => 'booking',
			'OsBookingModel' => 'booking',
			'OsBookingMetaModel' => 'booking',
			'OsConnectorModel' => 'connection',
			'OsCustomerModel' => 'customer',
			'OsCustomerMetaModel' => 'customer',
			'OsLocationModel' => 'location',
			'OsLocationCategoryModel' => 'location',
			'OsServiceModel' => 'service',
			'OsServiceExtraModel' => 'service',
			'OsServiceMetaModel' => 'service',
			'OsBundleModel' => 'bundle',
			'OsServiceCategoryModel' => 'service',
			'OsTransactionModel' => 'transaction',
			'OsInvoiceModel' => 'invoice',
			'OsProcessJobModel' => 'settings',
			'OsStepSettingsModel' => 'settings',
			'OsProcessModel' => 'settings',
			'OsSettingsModel' => 'settings',
			'OsWorkPeriodModel' => 'settings',
		];
		/**
		 * Get array of key => value connections between model name and capability name
		 *
		 * @since 4.7.0
		 * @hook latepoint_capability_names_for_models
		 *
		 * @param {array} $capability_names_for_models array of key => value pairs of model name => capability name
		 * @returns {array} The filtered array of value pairs
		 */
		$capability_names_for_models = apply_filters('latepoint_capability_names_for_models', $capability_names_for_models);
		if(isset($capability_names_for_models[$model_class])){
			$capability = $capability_names_for_models[$model_class].'__'.$action;
			$all_actions = self::get_all_available_actions_list();
			if(in_array($capability, $all_actions)){
				return $capability;
			}else{
				$capability = $capability_names_for_models[$model_class].'__edit';
				if(in_array($capability, $all_actions)) return $capability;
			}
		}
		return '';
	}

	public static function can_user_make_action_on_model_record(OsModel $model, string $action){
		if(OsAuthHelper::get_current_user()->backend_user_type == LATEPOINT_USER_TYPE_ADMIN) return true; // admins are allowed to do everything

		// check if customer is logged in and tries to edit records that belong to them
		if(OsAuthHelper::is_customer_logged_in()){
			// if it's customer - they can edit their customer record and also their bookings
			if($model instanceof OsBookingModel && $model->customer_id == OsAuthHelper::get_logged_in_customer_id()){
				return true;
			}
			if($model instanceof OsCustomerModel && $model->id == OsAuthHelper::get_logged_in_customer_id()){
				return true;
			}
		}
		// if customer is not logged in or logged in customer doesn't have rights to access this record, check if backend user has
		if(self::can_user_perform_model_action(get_class($model), $action)){
			switch(get_class($model)){
				case 'OsBookingModel':
					if(OsAuthHelper::get_current_user()->are_all_records_allowed('agent')) return true;
					$allowed_ids = OsAuthHelper::get_current_user()->get_allowed_records('agent');
					if($allowed_ids && in_array($model->agent_id, $allowed_ids)) return true;
					break;
				case 'OsCustomerModel':
					// check if this customer has any bookings with allowed agents
					if(OsAuthHelper::get_current_user()->are_all_records_allowed('agent')) return true;
					$allowed_ids = OsAuthHelper::get_current_user()->get_allowed_records('agent');
					$bookings = new OsBookingModel();
					if($allowed_ids){
						if($allowed_ids && in_array($model->agent_id, $allowed_ids)) return true;
						$has_bookings_with_allowed_agents = $bookings->select('id')->where(['agent_id' => $allowed_ids, 'customer_id' => $model->id])->set_limit(1)->get_results();
						if(!empty($has_bookings_with_allowed_agents)){
							return true;
						}
					}
					break;
			}
		}
		return false;
	}

	public static function can_user_perform_model_action(string $model_class, string $action): bool{
		$capability = self::build_capability_name_from_model_action($model_class, $action);
		$can = !empty($capability) ? self::can_user($capability) : true;
		return $can;
	}

	/**
	 *
	 * Get capabilities required to access specific action of the controller
	 *
	 * @param string $controller_name class name of the controller
	 * @param string $action action name
	 * @return array
	 */
	public static function get_capabilities_required_for_controller_action(string $controller_name, string $action): array{
		$required_capabilities = ['settings__edit']; // default capabilities, if not set on controller/action level

		$capabilities_for_controllers = self::get_capabilities_for_all_controllers();

		if(isset($capabilities_for_controllers[$controller_name])){
			// try to get capabilities that are specific for action, if not get default for controller
			if(isset($capabilities_for_controllers[$controller_name]['per_action'][$action])){
				$required_capabilities = $capabilities_for_controllers[$controller_name]['per_action'][$action];
			}elseif(isset($capabilities_for_controllers[$controller_name]['default'])){
				$required_capabilities = $capabilities_for_controllers[$controller_name]['default'];
			}
		}
		/**
		 * Get array of capabilities required to access controller's action
		 *
		 * @since 4.7.0
		 * @hook latepoint_get_capabilities_for_controller_action
		 *
		 * @param {array} $required_capabilities array of required capabilities
		 * @returns {array} The filtered array of required capabilities
		 */
		return apply_filters('latepoint_get_capabilities_for_controller_action', $required_capabilities, $controller_name, $action);
	}

	public static function save_capabilities_list_for_agent_role(array $capabilities){
		return OsSettingsHelper::save_setting_by_name('agent_role_capabilities', wp_json_encode($capabilities));
	}

	public static function get_capabilities_list_for_agent_role(){
		$capabilities = json_decode(OsSettingsHelper::get_settings_value('agent_role_capabilities', ''), true);
		if(empty($capabilities)) $capabilities = self::get_default_capabilities_list_for_agent_role();
		/**
		 * Get array of permitted actions for agent role
		 *
		 * @since 4.7.0
		 * @hook latepoint_get_capabilities_list_for_agent_role
		 *
		 * @param {array} $capabilities array of actions permitted to admin user type
		 * @returns {array} The filtered array of permitted actions
		 */
		return apply_filters('latepoint_get_capabilities_list_for_agent_role', $capabilities);
	}


	public static function get_all_available_actions_list_grouped(){
		$actions = self::get_all_available_actions_list();
		$groups = [];
		foreach($actions as $action){
			$object = explode('__', $action);
			$groups[$object[0]][] = $object[1];
		}
		return $groups;
	}

	public static function save_from_params(array $role_params){
		$role = \LatePoint\Misc\Role::generate_from_params($role_params);
		if(empty($role->wp_role)) return new WP_Error('invalid_wp_role', __('WP Role invalid', 'latepoint'));
		switch($role->user_type){
			case LATEPOINT_USER_TYPE_AGENT:
				if(self::save_capabilities_list_for_agent_role($role->get_capabilities())){
					return true;
				}else{
					return new WP_Error('error_saving_role', __('WP Agent role can not be saved', 'latepoint'));
				}
				break;
			case LATEPOINT_USER_TYPE_CUSTOM:
				$roles = self::get_custom_roles();
				$roles[$role->wp_role] = $role->as_array_to_save();
				if(OsSettingsHelper::save_setting_by_name('custom_roles', wp_json_encode($roles))){
					// register in WP (it's ok to register it on every save, we remove it first and then add, to make sure display name is updated)
					$role->register_in_wp();
					return true;
				}else{
					return new WP_Error('error_saving_role', __('WP Role can not be saved', 'latepoint'));
				}
				break;
			default:
				return new WP_Error('invalid_role_type', __('Invalid role type', 'latepoint'));
				break;
		}
	}

	public static function get_wp_roles_list($remove_admins = true) : array{
		// this makes sure that the default customer role is created, before showing a list of roles
		OsSettingsHelper::get_default_wp_role_for_new_customers();

	    global $wp_roles;



	    if (!isset($wp_roles)) {
	        $wp_roles = new WP_Roles();
	    }

	    $roles = $wp_roles->get_names();

		if($remove_admins){
			unset($roles['administrator']);
			unset($roles['editor']);
			unset($roles['shop_manager']);
			unset($roles['sc_shop_manager']);
		}

		/**
		 * List of WP roles that you can assign to a new customer
		 *
		 * @since 5.2.0
		 * @hook latepoint_wp_roles_list_for_customer
		 *
		 * @param {array} $roles list of roles in array
		 * @param {array} $remove_admins whether to remove admin roles or not
		 * @returns {array} The filtered list of roles
		 */
		return apply_filters('latepoint_wp_roles_list_for_customer', $roles, $remove_admins);

	}


	public static function register_customer_role(){
		// Register Customer Role
		add_role( LATEPOINT_WP_CUSTOMER_ROLE, __('LatePoint Customer', 'latepoint'), array( 'read' => true ) );
	}

	public static function register_roles_in_wp(){
		// Register Agent Role
		$role = \LatePoint\Misc\Role::get_from_wp_role(LATEPOINT_WP_AGENT_ROLE);
		$role->register_in_wp();

		self::register_customer_role();


		// Register Custom Roles
		$custom_roles = self::get_custom_roles(true);
		if($custom_roles){
			foreach($custom_roles as $custom_role){
				$custom_role->register_in_wp();
			}
		}
	}

	public static function get_model_types_for_allowed_records(){
		return ['agent', 'service', 'location'];
	}


  public static function generate_role_id(){
  	return 'role_'.OsUtilHelper::random_text('alnum', 8);
  }

	public static function get_custom_roles($as_objects = false): array{
		$roles = json_decode(\OsSettingsHelper::get_settings_value('custom_roles', ''), true) ?? [];
		if($as_objects){
			$roles_objects = [];
			foreach($roles as $role_params){
				$roles_objects[$role_params['wp_role']] = \LatePoint\Misc\Role::generate_from_params($role_params);
			}
			return $roles_objects;
		}else{
			return $roles;
		}
	}

	public static function save_custom_roles(array $roles){
		return \OsSettingsHelper::save_setting_by_name('custom_roles', wp_json_encode($roles));
	}

	public static function delete(string $wp_role): bool{
		// can't delete default roles (admin and agent)
		if(in_array($wp_role, [LATEPOINT_WP_ADMIN_ROLE, LATEPOINT_WP_AGENT_ROLE])) return false;
		// get all custom roles to make sure the one that needs to be deleted is custom role
		$custom_roles = self::get_custom_roles();
		if(isset($custom_roles[$wp_role])){
			unset($custom_roles[$wp_role]);
			self::save_custom_roles($custom_roles);
			remove_role($wp_role);
		}
		return true;
	}

	public static function get_default_roles(): array{
		$roles = [];

		$roles[] = new \LatePoint\Misc\Role(LATEPOINT_USER_TYPE_ADMIN);
		$roles[] = new \LatePoint\Misc\Role(LATEPOINT_USER_TYPE_AGENT);

		return $roles;
	}

	public static function description_for_action($action_code){
		$action_descriptions = [
			'resource_schedule' => __('Edit custom schedule of individual agent, location or service.', 'latepoint'),
			'settings' => __('Access to all settings pages, including general schedule and booking steps.', 'latepoint'),
			'connection' => __('Ability to connect agents to services and locations.', 'latepoint'),
			'chat' => __('Ability to send messages to customers (available with chat addon).', 'latepoint'),
		];

		/**
		 * List of detailed descriptions for available actions
		 *
		 * @since 4.7.2
		 * @hook latepoint_roles_action_descriptions
		 *
		 * @param {array} $action_descriptions Array of descriptions for available actions
		 * @param {string} $action_code Programmatic code of the action being filtered
		 * @returns {array} The filtered array of action descriptions
		 */
		$action_descriptions = apply_filters('latepoint_roles_action_descriptions', $action_descriptions, $action_code);
		return $action_descriptions[$action_code] ?? '';
	}

	public static function name_for_action($action_code){
		$action_names = [
			'chat' => __('Chat', 'latepoint'),
			'activity' => __('Activity Logs', 'latepoint'),
			'agent' => __('Agents', 'latepoint'),
			'service' => __('Services', 'latepoint'),
			'bundle' => __('Bundle', 'latepoint'),
			'location' => __('Locations', 'latepoint'),
			'booking' => __('Bookings & Orders', 'latepoint'),
			'customer' => __('Customers', 'latepoint'),
			'transaction' => __('Payments', 'latepoint'),
			'invoice' => __('Invoice', 'latepoint'),
			'order' => __('Order', 'latepoint'),
			'resource_schedule' => __('Resource Schedules', 'latepoint'),
			'settings' => __('Settings', 'latepoint'),
			'connection' => __('Connections', 'latepoint'),
			'edit' => __('Edit', 'latepoint'),
			'delete' => __('Delete', 'latepoint'),
			'view' => __('View', 'latepoint'),
			'create' => __('Create', 'latepoint'),
		];

		/**
		 * List of human-friendly names for available actions
		 *
		 * @since 4.7.2
		 * @hook latepoint_roles_action_names
		 *
		 * @param {array} $action_names Array of names for available actions
		 * @param {string} $action_code Programmatic code of the action being filtered
		 * @returns {array} The filtered array of action names
		 */
		$action_names = apply_filters('latepoint_roles_action_names', $action_names, $action_code);
		return $action_names[$action_code] ?? $action_code;
	}

	public static function get_default_capabilities_list_for_agent_role(){
		$capabilities = [
			'agent__view', 'agent__edit' ,
			'booking__view', 'booking__delete' ,'booking__create', 'booking__edit',
			'customer__view', 'customer__delete' ,'customer__create', 'customer__edit',
			'transaction__view', 'transaction__delete' ,'transaction__create', 'transaction__edit',
			'invoice__view', 'invoice__delete' ,'invoice__create', 'invoice__edit',
			'activity__view', 'activity__delete' ,'activity__create', 'activity__edit',
			'chat__edit', 'resource_schedule__edit'
		];

		/**
		 * Default list of permitted actions available for agent user type
		 *
		 * @since 4.7.0
		 * @hook latepoint_roles_get_default_capabilities_list_for_agent_role
		 *
		 * @param {array} $capabilities array of permitted actions available to agent user type by default
		 * @returns {array} The filtered array of permitted actions
		 */
		return apply_filters('latepoint_roles_get_default_capabilities_list_for_agent_role', $capabilities);
	}

	public static function get_all_available_actions_list(){
		$actions = [
			'agent__view', 'agent__delete' ,'agent__create', 'agent__edit' ,
			'service__view', 'service__delete' ,'service__create', 'service__edit' ,
			'bundle__view', 'bundle__delete' ,'bundle__create', 'bundle__edit' ,
			'location__view', 'location__delete' ,'location__create', 'location__edit' ,
			'service_extra__view', 'service_extra__delete' ,'service_extra__create', 'service_extra__edit' ,
			'booking__view', 'booking__delete' ,'booking__create', 'booking__edit' ,
			'customer__view', 'customer__delete' ,'customer__create', 'customer__edit' ,
			'transaction__view', 'transaction__delete' ,'transaction__create', 'transaction__edit' ,
			'invoice__view', 'invoice__delete' ,'invoice__create', 'invoice__edit' ,
			'activity__view', 'activity__delete' ,'activity__create', 'activity__edit' ,
			'chat__edit', 'resource_schedule__edit', 'settings__edit', 'connection__edit'
		];

		/**
		 * All available actions to be attached to a user role
		 *
		 * @since 4.7.0
		 * @hook latepoint_roles_get_all_available_actions_list
		 *
		 * @param {array} $actions array of actions that can be attached to a user role
		 * @returns {array} The filtered array of actions
		 */
		return apply_filters('latepoint_roles_get_all_available_actions_list', $actions);
	}

	/**
	 * @param \LatePoint\Misc\Role $role
	 * @return \LatePoint\Misc\User[]
	 */
	public static function get_users_for_role(\LatePoint\Misc\Role $role): array{
		$users = [];
		$wp_users = [];
		switch($role->user_type){
			case LATEPOINT_USER_TYPE_ADMIN:
				$wp_users = get_users(['role__in' => LATEPOINT_WP_ADMIN_ROLE]);
				break;
			case LATEPOINT_USER_TYPE_AGENT:
				$wp_users = get_users(['role' => LATEPOINT_WP_AGENT_ROLE]);
				break;
			case LATEPOINT_USER_TYPE_CUSTOM:
				if($role->wp_role) $wp_users = get_users(['role' => $role->wp_role]);
				break;
		}
		foreach($wp_users as $wp_user){
			$users[] = \LatePoint\Misc\User::load_from_wp_user($wp_user);
		}
		return $users;
	}

	public static function get_user_roles($user): array {
		return [];
	}

	/**
	 * Checks if currently logged in user has certain capabilities
	 *
	 * @param array|string $capabilities array or string of capabilities that you want to check if logged in user has or not
	 * @return bool
	 */
	public static function can_user($capabilities) :bool{
		if (OsAuthHelper::get_current_user()) {
			return OsAuthHelper::get_current_user()->has_capability( $capabilities );
		}

		return false;
	}

	public static function get_allowed_records(string $model_type, $load_from_db = false){
		return OsAuthHelper::get_current_user()->get_allowed_records($model_type, $load_from_db);
	}

	public static function are_all_records_allowed(string $model_type = '', $load_from_db = false): bool{
		return OsAuthHelper::get_current_user()->are_all_records_allowed($model_type, $load_from_db);
	}

	/*
	 * Pass filter object or an array of arguments for a query to be filtered based on what logged in user is allowed to access
	 */
	public static function filter_allowed_records_from_arguments_or_filter($args_or_filter){
		$model_types = ['agent', 'location', 'service'];
		foreach($model_types as $model_type){
			if(!OsAuthHelper::get_current_user()->are_all_records_allowed($model_type)){
				$prop = $model_type.'_id';

				// get value that needs to be filtered by allowed records
				$value = ($args_or_filter instanceof \LatePoint\Misc\Filter) ? $args_or_filter->$prop ?? [] : $args_or_filter[$prop] ?? [];

				if(empty($value)){
					// no value is set - limit it to allowed records
					$value = OsAuthHelper::get_current_user()->get_allowed_records($model_type);
				}else{
					// value is set - make sure it's in the allowed records list, if not - set to allowed records
					$allowed_from_set = array_intersect(is_array($value) ? $value : [$value], OsAuthHelper::get_current_user()->get_allowed_records($model_type));
					$value = empty($allowed_from_set) ? OsAuthHelper::get_current_user()->get_allowed_records($model_type) : $allowed_from_set;
				}
				if($args_or_filter instanceof \LatePoint\Misc\Filter){
					$args_or_filter->$prop = $value;
				}elseif(is_array($args_or_filter)){
					$args_or_filter[$prop] = $value;
				}
			}
		}
		return $args_or_filter;
	}
}