<?php
/**
 * Paid Membership Pro Integration Addon.
 *
 * @since 2.6.9
 *
 * @package Masteriyo\Addons\PMPROIntegration
 */

namespace Masteriyo\Addons\PMPROIntegration;

defined( 'ABSPATH' ) || exit;

use Masteriyo\Constants;
use Masteriyo\Enums\PostStatus;
use Masteriyo\PostType\PostType;
use Masteriyo\Enums\UserCourseStatus;
use Masteriyo\Models\UserCourse;
use Masteriyo\Query\UserCourseQuery;
use Masteriyo\Enums\CourseAccessMode;

/**
 * Paid Membership Pro Integration Addon.
 *
 * @since 2.6.9
 */
class PMPROIntegrationAddon {

	/**
	 * The single instance of the class.
	 *
	 * @since 2.6.9
	 *
	 * @var self|null
	 */
	protected static $instance = null;

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

		return self::$instance;
	}

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

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

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

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

	/**
	 * Init hooks.
	 *
	 * @since 2.6.9
	 */
	private function init_hooks() {
		add_action( 'pmpro_membership_level_after_other_settings', array( $this, 'display_pmpro_settings' ) );
		add_action( 'pmpro_save_membership_level', array( $this, 'save_pmpro_settings' ) );
		add_action( 'pmpro_after_set_current_user', array( $this, 'add_student_role_to_pmpro_member' ) );
		add_action( 'pmpro_added_order', array( $this, 'create_user_course' ) );
		add_action( 'pmpro_updated_order', array( $this, 'create_user_course' ) );

		add_filter( 'masteriyo_rest_course_schema', array( $this, 'add_pmpro_schema' ) );
		add_action( 'masteriyo_new_course', array( $this, 'save_pmpro_course_data' ), 10, 2 );
		add_action( 'masteriyo_update_course', array( $this, 'save_pmpro_course_data' ), 10, 2 );
		add_filter( 'masteriyo_rest_response_course_data', array( $this, 'append_pmpro_data_in_response' ), 10, 4 );

		// Update the start course for course connected with membership Level.
		add_filter( 'masteriyo_can_start_course', array( $this, 'update_can_start_course' ), 10, 3 );
		add_filter( 'masteriyo_localized_admin_scripts', array( $this, 'localize_admin_scripts' ) );

		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_course_url' ), 10, 2 );
	}

	/**
	 * Modify start course URL for courses with memberships.
	 *
	 * @since 2.8.1
	 * @param string $url Add to cart URL.
	 * @param \Masteriyo\Models\Course $course Course object.
	 *
	 * @return string
	 */
	public function modify_start_course_url( $url, $course ) {
		$pmpro_levels = $course->get_meta( '_pmpro_levels' );

		if ( empty( $pmpro_levels ) ) {
			return $url;
		}

		$pmpro_url = $course->get_meta( '_pmpro_url' );
		$pmpro_url = empty( $pmpro_url ) ? pmpro_url( 'levels' ) : $pmpro_url;

		if ( ! is_user_logged_in() ) {
			return $pmpro_url;
		}

		$levels                = pmpro_getMembershipLevelsForUser( get_current_user_id() );
		$user_has_course_level = false;

		foreach ( $levels as $level ) {
			$course_ids = get_pmpro_membership_level_meta( $level->id, '_mas_courses', true );
			if ( ! empty( $course_ids ) && in_array( $course->get_id(), $course_ids ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
				$user_has_course_level = true;
				break;
			}
		}

		if ( ! $user_has_course_level ) {
			return $pmpro_url;
		}

		return $url;
	}

	/**
	 * Modify add to cart URL or courses with memberships.
	 *
	 * @since 2.6.9
	 * @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 ) {
		$pmpro_levels = $course->get_meta( '_pmpro_levels' );

		if ( empty( $pmpro_levels ) ) {
			return $url;
		}

		$pmpro_url = $course->get_meta( '_pmpro_url' );
		$url       = empty( $pmpro_url ) ? pmpro_url( 'levels' ) : $pmpro_url;

		return $url;
	}

	/**
	 * Add schema.
	 *
	 * @since 2.6.9
	 * @param array $schema Schema.
	 *
	 * @return array
	 */
	public function add_pmpro_schema( $schema ) {
		$schema = masteriyo_parse_args(
			$schema,
			array(
				'pmpro_levels' => array(
					'description' => __( 'PMPRO membership levels', 'learning-management-system' ),
					'type'        => 'array',
					'context'     => array( 'view', 'edit' ),
					'items'       => array(
						'type' => 'number',
					),
				),
				'pmpro_url'    => array(
					'description' => __( 'URL to redirect after course buy now button is clicked. Defaults to Membership Levels Page.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'default'     => '',
				),
			)
		);

		return $schema;
	}


	/**
	 * Save pmpro membership levels data.
	 *
	 * @since 2.6.9
	 *
	 * @param int $id The course ID.
	 * @param \Masteriyo\Models\Course $course The course object.
	 */
	public function save_pmpro_course_data( $course_id, $course ) {
		global $wpdb;
		$request = masteriyo_current_http_request();

		if ( null === $request || ! isset( $wpdb->pmpro_membership_levels ) ) {
			return;
		}

		$membership_levels     = $request->get_param( 'pmpro_levels' ) ?? array();
		$all_membership_levels = $wpdb->get_results( "SELECT * FROM $wpdb->pmpro_membership_levels", OBJECT );

		foreach ( $all_membership_levels as $membership_level ) {
			$membership_courses = get_pmpro_membership_level_meta( $membership_level->id, '_mas_courses', true );
			$membership_courses = array_map( 'intval', array_filter( masteriyo_array_wrap( $membership_courses ) ) );

			$membership_level->id = intval( $membership_level->id );

			if (
				! in_array( $membership_level->id, $membership_levels, true ) &&
				in_array( $course_id, $membership_courses, true )
			) {
				$membership_courses = array_diff( $membership_courses, array( $course_id ) );
			} elseif (
				in_array( $membership_level->id, $membership_levels, true ) &&
				! in_array( $course_id, $membership_courses, true )
			) {
				$membership_courses[] = $course_id;
			}

			update_pmpro_membership_level_meta( $membership_level->id, '_mas_courses', $membership_courses );
		}

		$membership_url = $request->get_param( 'pmpro_url' ) ?? '';

		$course->update_meta_data( '_pmpro_levels', $membership_levels );
		$course->update_meta_data( '_pmpro_url', $membership_url );
		$course->save_meta_data();
	}

	/**
	 * Append PMPRO membership levels to course response.
	 *
	 * @since 2.6.9
	 *
	 * @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.
	 */
	public function append_pmpro_data_in_response( $data, $course, $context, $controller ) {
		$levels = $course->get_meta( '_pmpro_levels', true );
		$levels = array_map( 'intval', array_filter( masteriyo_array_wrap( $levels ) ) );
		$url    = $course->get_meta( '_pmpro_url', true );

		$data['pmpro_levels'] = $levels;
		$data['pmpro_url']    = strval( $url );

		return $data;
	}

	/**
	 * Localize admin scripts.
	 *
	 * @since 2.6.9
	 *
	 * @param array $scripts Scripts.
	 * @return array
	 */
	public function localize_admin_scripts( $scripts ) {
		global $wpdb;

		if ( $wpdb->pmpro_membership_levels ) {
			$membership_levels = $wpdb->get_results( "SELECT * FROM $wpdb->pmpro_membership_levels" );
			$membership_levels = array_map(
				function ( $membership_level ) {
					$membership_level->id = intval( $membership_level->id );
					return $membership_level;
				},
				$membership_levels
			);

			$scripts['backend']['data']['membership_levels'] = $membership_levels;
		}

		return $scripts;
	}

	/**
	 * Save PMPRO membership level settings.
	 *
	 * @since 2.6.9
	 * @param int $level_id Level ID.
	 */
	public function save_pmpro_settings( $level_id ) {
		// Bail early if `update_pmpro_membership_level_meta` doesn't exist or if the nonce is invalid.
		if (
			! function_exists( 'update_pmpro_membership_level_meta' ) ||
			! isset( $_POST['_mas_pmpro_nonce'] ) ||
			! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_mas_pmpro_nonce'] ) ), 'mas_pmpro_settings' )
		) {
			return;
		}

		$courses     = isset( $_POST['_mas_courses'] ) ? array_map( 'intval', wp_unslash( $_POST['_mas_courses'] ) ) : array();
		$old_courses = get_pmpro_membership_level_meta( $level_id, '_mas_courses', true );
		$old_courses = array_map( 'intval', array_filter( masteriyo_array_wrap( $old_courses ) ) );

		if (
			empty( array_diff_assoc( $courses, $old_courses ) ) &&
			empty( array_diff_assoc( $old_courses, $courses ) )
		) {
			return;
		}

		$this->update_pmpro_levels( $courses, $old_courses, $level_id );
		update_pmpro_membership_level_meta( $level_id, '_mas_courses', $courses );
	}

	/**
	 * Membership level settings.
	 *
	 * @since 2.6.9
	 *
	 * @param \stdClass $level Level object.
	 * @return void
	 */
	public function display_pmpro_settings( $level ) {
		// Bail early if `get_pmpro_membership_level_meta` doesn't exist.
		if ( ! function_exists( 'get_pmpro_membership_level_meta' ) ) {
			return;
		}

		$membership_level_courses = get_pmpro_membership_level_meta( $level->id, '_mas_courses', true );
		$membership_level_courses = masteriyo_array_wrap( $membership_level_courses );

		$courses = get_posts(
			array(
				'posts_per_page' => -1,
				'post_status'    => PostStatus::PUBLISH,
				'post_type'      => PostType::COURSE,
			)
		);

		$courses = array_reduce(
			$courses,
			function ( $acc, $curr ) {
				$acc[ $curr->ID ] = $curr->post_title;
				return $acc;
			},
			array()
		);

		require trailingslashit( Constants::get( 'MASTERIYO_PMPRO_INTEGRATION_TEMPLATES' ) ) . 'membership-level-settings.php';
	}

	/**
	 * Update masteriyo_can_start_course() for course connected with membership levels.
	 *
	 * @since 2.6.9
	 *
	 * @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 ) {
		if (
			! function_exists( 'pmpro_getMembershipLevelsForUser' ) ||
			! is_user_logged_in() ||
			CourseAccessMode::OPEN === $course->get_access_mode()
		) {
			return $can_start_course;
		}

		$membership_levels = pmpro_getMembershipLevelsForUser( $user->get_id() );

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

		foreach ( $membership_levels as $level ) {
			$expiry_date   = $level->enddate;
			$current_date  = current_datetime()->getTimestamp();
			$courses_level = get_pmpro_membership_level_meta( $level->id, '_mas_courses', true );

			// Skip if the membership is expired or courses is not associated with membership level.
			if (
				( $expiry_date > 0 && $current_date > $expiry_date ) ||
				! $courses_level
			) {
				continue;
			}

			$courses_level = array_map( 'intval', $courses_level );

			if ( in_array( $course->get_id(), $courses_level, true ) ) {
				$can_start_course = true;
				break;
			}
		}

		return $can_start_course;
	}

	/**
	 * Update courses PMPRO levels meta.
	 *
	 * @since 2.6.9
	 *
	 * @param array $courses Courses.
	 * @param array $old_courses Old Courses.
	 * @param int $level_id Level ID.
	 */
	protected function update_pmpro_levels( $courses, $old_courses, $level_id ) {
		$all_courses = array_unique( array_merge( $courses, $old_courses ) );

		foreach ( $all_courses as $course ) {
			$course = masteriyo_get_course( $course );

			if ( ! $course ) {
				continue;
			}

			$levels = $course->get_meta( '_pmpro_levels', true );
			$levels = array_map( 'intval', array_filter( masteriyo_array_wrap( $levels ) ) );

			if (
				empty( $old_courses ) ||
				! in_array( $course, $courses, true )
			) {
				$levels[] = $level_id;
			} else {
				$found = array_search( $level_id, $levels, true );
				if ( $found ) {
					unset( $levels[ $found ] );
				}
			}

			$course->update_meta_data( '_pmpro_levels', $levels );
			$course->save_meta_data();
		}
	}

	/**
	 * Add student role to PMPRO members.
	 *
	 * @since 2.6.9
	 *
	 */
	public function add_student_role_to_pmpro_member() {
		if ( ! function_exists( 'pmpro_getMembershipLevelsForUser' ) ) {
			return;
		}
		$user_id          = get_current_user_id();
		$levels           = pmpro_getMembershipLevelsForUser( $user_id );
		$level_has_course = false;

		if ( $levels ) {
			foreach ( $levels as $level ) {
				$courses_level = get_pmpro_membership_level_meta( $level->id, '_mas_courses', true );
				if ( $courses_level ) {
					$level_has_course = true;
					break;
				}
			}
		}

		if ( ! $level_has_course ) {
			return;
		}

		remove_action( 'pmpro_after_set_current_user', array( $this, 'add_student_role_to_pmpro_member' ) );

		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() );
		}

		remove_action( 'pmpro_after_set_current_user', array( $this, 'add_student_role_to_pmpro_member' ) );
	}

	/**
	 * Create user course when PMPRO order is created.
	 *
	 * @since 2.6.9
	 *
	 * @param \MemberOrder $order PMPRO order.
	 */
	public function create_user_course( $order ) {
		if ( ! function_exists( 'get_pmpro_membership_level_meta' ) ) {
			return;
		}

		$membership_level   = $order->getMembershipLevel();
		$membership_courses = get_pmpro_membership_level_meta( $membership_level->id, '_mas_courses', true );
		$membership_courses = array_map( 'intval', array_filter( masteriyo_array_wrap( $membership_courses ) ) );

		if ( empty( $membership_courses ) ) {
			return;
		}

		foreach ( $membership_courses as $course_id ) {
			$course = masteriyo_get_course( $course_id );
			if ( ! $course ) {
				continue;
			}

			$query = new UserCourseQuery(
				array(
					'course_id' => $course->get_id(),
					'user_id'   => $order->user_id,
				)
			);

			$user_courses = $query->get_user_courses();
			/**
			 * @var UserCourse $user_course
			 */
			$user_course = empty( $user_courses ) ? masteriyo( 'user-course' ) : current( $user_courses );

			$user_course->set_course_id( $course->get_id() );
			$user_course->set_user_id( $order->getUser()->ID );
			$user_course->set_price( $course->get_price() );

			if ( 'success' === $order->original_status ) {
				$user_course->set_status( UserCourseStatus::ACTIVE );
				$user_course->set_date_start( current_time( 'mysql', true ) );
			} else {
				$user_course->set_status( UserCourseStatus::INACTIVE );
				$user_course->set_date_start( null );
				$user_course->set_date_modified( null );
				$user_course->set_date_end( null );
			}

			$user_course->save();
		}
	}
}
