<?php

/**
 * Restrict Content Pro Integration Addon.
 *
 * @since 2.7.3
 *
 * @package Masteriyo\Addons\RCPIntegration
 */

namespace Masteriyo\Addons\RCPIntegration;

use Masteriyo\Enums\CourseAccessMode;
use Masteriyo\Enums\UserCourseStatus;
use Masteriyo\Query\UserCourseQuery;

defined( 'ABSPATH' ) || exit;

/**
 * Paid Membership Pro Integration Addon.
 *
 * @since 2.7.3
 */
class RCPIntegrationAddon {
	/**
	 * The single instance of the class.
	 *
	 * @since 2.7.3
	 *
	 * @var self|null
	 */
	protected static $instance = null;

	/**
	 * Get class instance.
	 *
	 * @since 2.7.3
	 *
	 * @return self Instance.
	 */
	final public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Prevent cloning.
	 *
	 * @since 2.7.3
	 */
	public function __clone() {     }

	/**
	 * Prevent unserializing.
	 *
	 * @since 2.7.3
	 */
	public function __wakeup() {    }

	/**
	 * Constructor.
	 *
	 * @since 2.7.3
	 */
	protected function __construct() {  }

	/**
	 * Init.
	 *
	 * @since 2.7.3
	 */
	public function init() {
		$this->init_hooks();
	}

	/**
	 * Init hooks.
	 *
	 * @since 2.7.3
	 */
	private function init_hooks() {
		$this->add_course_hooks();
		$this->add_restriction_hooks();
		$this->add_cart_hooks();
		$this->add_enrollment_hooks();
		$this->add_admin_hooks();
	}

	/**
	 * Adds hooks related to course operations.
	 *
	 * @since 2.7.3
	 */
	private function add_course_hooks() {
		add_filter( 'masteriyo_rest_course_schema', array( $this, 'add_rcp_schema' ) );
		add_action( 'masteriyo_new_course', array( $this, 'save_rcp_course_data' ), 10, 2 );
		add_action( 'masteriyo_update_course', array( $this, 'save_rcp_course_data' ), 10, 2 );
		add_filter( 'masteriyo_rest_response_course_data', array( $this, 'append_rcp_data_in_response' ), 10, 4 );
	}

	/**
	 * Adds hooks related to restricted content messages.
	 *
	 * @since 2.7.3
	 */
	private function add_restriction_hooks() {
		add_filter( 'rcp_restricted_message', array( $this, 'modify_restricted_content_message' ), 10, 1 );
	}

	/**
	 * Adds hooks related to cart operations.
	 *
	 * @since 2.7.3
	 */
	private function add_cart_hooks() {
		add_filter( 'masteriyo_course_add_to_cart_url', array( $this, 'modify_add_to_cart_url' ), 10, 2 );
		add_filter( 'masteriyo_start_course_url', array( $this, 'modify_start_url' ), 10, 3 );

		add_filter( 'masteriyo_add_to_cart_text', array( $this, 'modify_add_to_cart_text' ), 10, 1 );
		add_filter( 'masteriyo_single_course_start_text', array( $this, 'modify_add_to_cart_text' ), 10, 1 );
	}

	/**
	 * Adds hooks related to course enrollment.
	 *
	 * @since 2.7.3
	 */
	private function add_enrollment_hooks() {
		add_filter( 'masteriyo_can_start_course', array( $this, 'update_can_start_course' ), 10, 3 );
		add_action( 'masteriyo_enroll_member_in_course', array( $this, 'create_user_course' ), 10, 2 );
	}

	/**
	 * Adds hooks related to admin scripts.
	 *
	 * @since 2.7.3
	 */
	private function add_admin_hooks() {
		add_filter( 'masteriyo_localized_admin_scripts', array( $this, 'localize_admin_scripts' ) );
	}

	/**
	 * Remove the the restricted content message.
	 *
	 * @since 2.7.3
	 *
	 * @param string $message
	 *
	 * @return string
	 */
	public function modify_restricted_content_message( $message ) {
		return '';
	}

	/**
	 * Creates a user course when starting the course, but only if the user is eligible based on RCP membership status.
	 *
	 * This function first checks if the course and user are eligible for processing.
	 * It then prepares the user for course enrollment, retrieves or creates a user course record,
	 * and finally sets up and saves the user course details.
	 *
	 * @since 2.7.3
	 *
	 * @param int $course_id The ID of the course to enroll the user in.
	 * @param int $user_id The ID of the user to enroll in the course.
	 */
	public function create_user_course( $course_id, $user_id ) {
		if ( ! $this->can_process_course( $course_id, $user_id ) ) {
			return;
		}

		$this->prepare_user_for_course_enrollment( $user_id );

		$user_course = $this->get_user_course( $course_id, $user_id );
		$this->setup_user_course( $user_course, $course_id, $user_id );
		$user_course->save();
	}

	/**
	 * Checks if the course and user are eligible for processing.
	 *
	 * @since 2.7.3
	 *
	 * @param int $course_id Course ID.
	 * @param int $user_id User ID.
	 *
	 * @return bool True if the course and user can be processed, false otherwise.
	 */
	private function can_process_course( $course_id, $user_id ) {
		$course = masteriyo_get_course( $course_id );
		return ! is_null( $course ) && ! is_wp_error( $course ) && Helper::user_has_access( $user_id, $course_id );
	}

	/**
	 * Prepares the user for course enrollment by assigning the necessary roles.
	 *
	 * @since 2.7.3
	 *
	 * @param int $user_id User ID.
	 */
	private function prepare_user_for_course_enrollment( $user_id ) {
		try {
				$user  = masteriyo( 'user' );
				$store = masteriyo( 'user.store' );
				$user->set_id( $user_id );
				$store->read( $user );

			if ( empty( array_intersect( $user->get_roles(), array( 'administrator', 'masteriyo_manager', 'masteriyo_student', 'masteriyo_instructor' ) ) ) ) {
					$user->add_role( 'masteriyo_student' );
					$user->save();
			}
		} catch ( \Exception $e ) {
				error_log( $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
		}
	}

	/**
	 * Retrieves an existing user course or creates a new one.
	 *
	 * @since 2.7.3
	 *
	 * @param int $course_id Course ID.
	 * @param int $user_id User ID.
	 * @return UserCourse The UserCourse instance.
	 */
	private function get_user_course( $course_id, $user_id ) {
		$query        = new UserCourseQuery(
			array(
				'course_id' => $course_id,
				'user_id'   => $user_id,
			)
		);
		$user_courses = $query->get_user_courses();
		return empty( $user_courses ) ? masteriyo( 'user-course' ) : current( $user_courses );
	}

	/**
	 * Sets up the user course details.
	 *
	 * @since 2.7.3
	 *
	 * @param UserCourse $user_course The UserCourse instance.
	 * @param int $course_id Course ID.
	 * @param int $user_id User ID.
	 */
	private function setup_user_course( $user_course, $course_id, $user_id ) {
		$course = masteriyo_get_course( $course_id );

		$user_course->set_course_id( $course_id );
		$user_course->set_user_id( $user_id );
		$user_course->set_price( $course->get_price() );
		$user_course->set_status( UserCourseStatus::ACTIVE );
		$user_course->set_date_start( current_time( 'mysql', true ) );
	}

	/**
	 *  Modifies the 'Add to Cart' button text based on the RCP membership status of the current user.
	 *
	 * @since 2.7.3
	 *
	 * @param string $text The original text for the 'Add to Cart' button.
	 *
	 * @return string Modified 'Add to Cart' button text or original text based on user access.
	 */
	public function modify_add_to_cart_text( $text ) {
		if ( ! Helper::user_has_access( \get_current_user_id(), get_the_ID() ) ) {
			return __( 'Get Membership', 'learning-management-system' );
		}

		return $text;
	}

	/**
	 * Modify start URL or courses with memberships.
	 *
	 * @since 2.7.3
	 * @param string $url start URL.
	 * @param \Masteriyo\Models\Course $course Course object.
	 * @param boolean $append_first_lesson_or_quiz Whether to append first lesson or quiz or not.
	 *
	 * @return string
	 */
	public function modify_start_url( $url, $course, $append_first_lesson_or_quiz ) {

		if ( ! Helper::user_has_access( \get_current_user_id(), get_the_ID() ) ) {
			return \rcp_get_registration_page_url();
		}

		return $url;

	}

	/**
	 * Modify add to cart URL or courses with memberships.
	 *
	 * @since 2.7.3
	 * @param string $url Add to cart URL.
	 * @param \Masteriyo\Models\Course $course Course object.
	 *
	 * @return string
	 */
	public function modify_add_to_cart_url( $url, $course ) {

		if ( ! Helper::user_has_access( \get_current_user_id(), get_the_ID() ) ) {
			return \rcp_get_registration_page_url();
		}

		return $url;

	}

	/**
	 * Update masteriyo_can_start_course() for course connected with membership levels.
	 *
	 * @since 2.7.3
	 *
	 * @param bool $can_start_course Whether user can start the course.
	 * @param \Masteriyo\Models\Course $course Course object.
	 * @param \Masteriyo\Models\User $user User object.
	 *
	 * @return boolean
	 */
	public function update_can_start_course( $can_start_course, $course, $user ) {
		global $wpdb;

		if ( CourseAccessMode::OPEN === $course->get_access_mode() ) {
			return $can_start_course;
		}

		if ( is_wp_error( $user ) || is_null( $user ) ) {
			return $can_start_course;
		}

		$membership_levels = \rcp_get_membership_levels();

		// Skip if user doesn't have any membership.
		if ( ! $membership_levels ) {
			return $can_start_course;
		}

		$user_id   = $user->get_id();
		$course_id = $course->get_id();

		if ( ! Helper::user_has_access( $user_id, $course_id ) ) {
			$this->revoke_course_enrollment( $user_id, $course_id, $wpdb );
			return false;
		}

		return true;
	}

	/**
	 * Revokes the course enrollment for a specific user and course.
	 *
	 * @since 2.7.3
	 *
	 * @param int $user_id User ID.
	 * @param int $course_id Course ID.
	 * @param wpdb $wpdb WordPress database global object.
	 */
	private function revoke_course_enrollment( $user_id, $course_id, $wpdb ) {
		$wpdb->delete(
			"{$wpdb->prefix}masteriyo_user_items",
			array(
				'user_id'   => $user_id,
				'item_id'   => $course_id,
				'item_type' => 'user_course',
			),
			array(
				'%d',
				'%d',
				'%s',
			)
		);
	}

	/**
	 * Appends RCP membership levels and other related data to the course response.
	 *
	 * @since 2.7.3
	 *
	 * @param array $data Course data.
	 * @param \Masteriyo\Models\Course $course Course object.
	 * @param string $context What the value is for. Valid values are 'view' and 'edit'.
	 * @param \Masteriyo\RestApi\Controllers\Version1\CoursesController $controller REST courses controller object.
	 *
	 * @return array Modified course data array with appended RCP details.
	 */
	public function append_rcp_data_in_response( $data, $course, $context, $controller ) {
		$meta_keys = array(
			'rcp_levels'             => 'rcp_levels',
			'rcp_roles'              => 'rcp_user_level',
			'rcp_restrict_by'        => 'rcp_restrict_by',
			'rcp_subscription_level' => 'rcp_subscription_level_set',
			'rcp_access_level'       => 'rcp_access_level',
		);

		foreach ( $meta_keys as $key => $meta_key ) {
			$value = $course->get_meta( $meta_key, true );
			if ( 'rcp_levels' === $key ) {
					$value = array_map( 'intval', array_filter( masteriyo_array_wrap( $value ) ) );
			}
			$data[ $key ] = $value ?? ( 'rcp_access_level' === $key ? 0 : null );
		}

		return $data;
	}

	/**
	 * Localizes admin scripts by including RCP membership levels, user roles, and access levels.
	 *
	 * @since 2.7.3
	 *
	 * @param array $scripts Array of scripts that might be localized with additional data.
	 *
	 * @return array Modified scripts array including RCP membership levels, user roles, and access levels.
	 */
	public function localize_admin_scripts( $scripts ) {
		$membership_levels = \rcp_get_membership_levels();

		$membership_levels = wp_list_pluck( $membership_levels, 'name', 'id' );

		$roles = array( 'all' => 'Any' ) + array_map(
			function ( $role ) {
					return $role['name'];
			},
			\get_editable_roles()
		);

		$scripts['backend']['data']['rcp'] = array(
			'membership_levels' => $membership_levels,
			'user_roles'        => $roles,
			'access_levels'     => \rcp_get_access_levels(),
		);

		return $scripts;
	}

	/**
	 * Adds RCP related schema to the course.
	 *
	 * This method extends the course schema with RCP (Restrict Content Pro) related fields like
	 * subscription level, membership levels, access level, etc. It merges the provided schema
	 * with the RCP specific fields and returns the updated schema.
	 *
	 * @since 2.7.3
	 *
	 * @param array $schema Existing course schema.
	 *
	 * @return array Updated course schema with RCP related fields.
	 */
	public function add_rcp_schema( $schema ) {
		$rcp_fields = array(
			'rcp_restrict_by'            => array(
				'description' => __( 'Require member to have capabilities from this user role.', 'learning-management-system' ),
				'type'        => 'string',
				'context'     => array( 'view', 'edit' ),
			),
			'rcp_subscription_level_set' => array(
				'description' => __( 'Require member to have capabilities from this user role.', 'learning-management-system' ),
				'type'        => 'string',
				'context'     => array( 'view', 'edit' ),
			),
			'rcp_levels'                 => array(
				'description' => __( 'RCP membership levels', 'learning-management-system' ),
				'type'        => 'array',
				'context'     => array( 'view', 'edit' ),
				'items'       => array( 'type' => 'number' ),
			),
			'rcp_access_level'           => array(
				'description' => __( 'Require member to have capabilities from this user role.', 'learning-management-system' ),
				'type'        => 'int',
				'context'     => array( 'view', 'edit' ),
			),
			'rcp_user_level'             => array(
				'description' => __( 'Require member to have capabilities from this user role.', 'learning-management-system' ),
				'type'        => 'array',
				'context'     => array( 'view', 'edit' ),
				'items'       => array( 'type' => 'string' ),
			),
		);

		return masteriyo_parse_args( $schema, $rcp_fields );
	}

	/**
	 * Saves RCP membership levels and other related data for a course.
	 *
	 * @since 2.7.3
	 *
	 * @param int $id The course ID.
	 * @param \Masteriyo\Models\Course $course The course object.
	 */
	public function save_rcp_course_data( $course_id, $course ) {
		$request = masteriyo_current_http_request();

		if ( null === $request ) {
			return;
		}

		$membership_levels = array_map( 'intval', (array) $request->get_param( 'rcp_levels' ) );
		$roles             = array_map( 'sanitize_text_field', (array) $request->get_param( 'rcp_roles' ) );
		$restrict_by       = sanitize_text_field( $request->get_param( 'rcp_restrict_by' ) );
		$level_set         = sanitize_text_field( $request->get_param( 'rcp_subscription_level' ) );
		$access_level      = absint( $request->get_param( 'rcp_access_level' ) );
		$is_paid           = false;

		switch ( $restrict_by ) {
			case 'unrestricted':
				delete_post_meta( $course_id, 'rcp_subscription_level' );

				$course->delete_meta_data( 'rcp_levels' );
				$course->delete_meta_data( 'rcp_subscription_level_set' );
				$course->delete_meta_data( 'rcp_access_level' );
				$course->delete_meta_data( 'rcp_user_level' );
				break;
			case 'subscription-level':
				switch ( $level_set ) {
					case 'any':
						update_post_meta( $course_id, 'rcp_subscription_level', 'any' );
						$course->delete_meta_data( 'rcp_levels' );
						break;
					case 'any-paid':
						$is_paid = true;
						update_post_meta( $course_id, 'rcp_subscription_level', 'any-paid' );
						$course->delete_meta_data( 'rcp_levels' );
						break;
					case 'specific':
						$is_paid = true;
						if ( count( $membership_levels ) ) {
							foreach ( $membership_levels as $level ) {
								$price = \rcp_get_subscription_price( $level );
								if ( empty( $price ) ) {
									$is_paid = false;
									break;
								}
							}
						}
						update_post_meta( $course_id, 'rcp_subscription_level', $membership_levels );
						$course->update_meta_data( 'rcp_levels', $membership_levels );
						break;
				}
				$course->update_meta_data( 'rcp_subscription_level_set', $level_set );
				$course->delete_meta_data( 'rcp_access_level' );
				break;
			case 'access-level':
				update_post_meta( $course_id, 'rcp_access_level', $access_level );
				delete_post_meta( $course_id, 'rcp_subscription_level' );

				$course->delete_meta_data( 'rcp_levels' );
				$course->delete_meta_data( 'rcp_subscription_level_set' );
				break;
			case 'registered-users':
				$course->delete_meta_data( 'rcp_access_level' );
				delete_post_meta( $course_id, 'rcp_subscription_level' );

				$course->delete_meta_data( 'rcp_levels' );
				$course->delete_meta_data( 'rcp_subscription_level_set' );
				break;
		}

		if ( ! empty( $restrict_by ) && 'unrestricted' !== $restrict_by ) {
			$course->update_meta_data( 'rcp_user_level', count( $roles ) ? $roles : array( 'all' ) );
		}

		if ( $is_paid ) {
			update_post_meta( $course_id, '_is_paid', $is_paid );
		} else {
			delete_post_meta( $course_id, '_is_paid' );
		}

		$course->update_meta_data( 'rcp_restrict_by', $restrict_by );

		$course->save_meta_data();
	}
}
