<?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 strtolower( '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;
	}
}
