<?php
/**
 * Analytics Course controller class.
 *
 * @since 2.8.3
 */

namespace Masteriyo\Pro\Controllers;

use Masteriyo\DateTime;
use Masteriyo\Enums\PostStatus;
use Masteriyo\Enums\CommentType;
use Masteriyo\Enums\OrderStatus;
use Masteriyo\Helper\Permission;
use Masteriyo\PostType\PostType;
use Masteriyo\Enums\CommentStatus;
use Masteriyo\Enums\UserCourseStatus;
use Masteriyo\Query\CourseProgressQuery;
use Masteriyo\Enums\CourseProgressStatus;
use Masteriyo\Enums\CourseProgressPostType;
use Masteriyo\Enums\SectionChildrenPostType;
use Masteriyo\Addons\Zoom\Enums\ZoomMeetingStatus;
use Masteriyo\Query\CourseProgressItemQuery;
use Masteriyo\Query\UserCourseQuery;
use WP_Error;

defined( 'ABSPATH' ) || exit;

class AnalyticsCourseController {
	/**
	 * Endpoint namespace.
	 *
	 * @since 2.8.3
	 *
	 * @var string
	 */
	protected $namespace = 'masteriyo/pro/v1';

	/**
	 * Permission class.
	 *
	 * @since 2.8.3
	 *
	 * @var Permission
	 */
	protected $permission;


	/**
	 * Object type.
	 *
	 * @since 2.8.3
	 *
	 * @var string
	 */
	protected $object_type = 'course-analytics';

	/**
	 * Constructor.
	 *
	 * @since 2.8.3
	 *
	 * @param Permission $permission
	 */
	public function __construct( Permission $permission ) {
		$this->permission = $permission;
	}

	/**
	 * Add REST API routes.
	 *
	 * @since 2.8.3
	 *
	 * @return void
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->object_type .
			'/(?P<id>[\d]+)',
			array(
				'args' => array(
					'id' => array(
						'description' => __( 'Unique identifier for the resource.', 'learning-management-system' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => array(
						'start_date' => array(
							'description'       => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'learning-management-system' ),
							'type'              => 'string',
							'format'            => 'date-time',
							'validate_callback' => 'rest_validate_request_arg',
						),
						'end_date'   => array(
							'description'       => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'learning-management-system' ),
							'type'              => 'string',
							'format'            => 'date-time',
							'validate_callback' => 'rest_validate_request_arg',
						),

					),
				),

			)
		);
		register_rest_route(
			$this->namespace,
			'/' . $this->object_type . '-students' .
			'/(?P<id>[\d]+)',
			array(
				'args' => array(
					'id' => array(
						'description' => __( 'Unique identifier for the resource.', 'learning-management-system' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_student_data' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => array(
						'per_page' => array(
							'description'       => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'learning-management-system' ),
							'type'              => 'number',
							'format'            => 'date-time',
							'validate_callback' => 'rest_validate_request_arg',
						),
						'page'     => array(
							'description'       => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'learning-management-system' ),
							'type'              => 'number',
							'format'            => 'date-time',
							'validate_callback' => 'rest_validate_request_arg',
						),

					),
				),

			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->object_type . '/(?P<id>[\d]+)/enrollment',
			array(
				array(
					'methods'             => \WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_enrollment' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => array(
						'id' => array(
							'required'    => true,
							'description' => __( 'Course ID.', 'learning-management-system' ),
							'type'        => 'integer',
						),
					),
				),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->object_type . '/(?P<cid>[\d]+)/enrollment',
			array(
				array(
					'methods'             => \WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_enrollment' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
					'args'                => array(
						'cid' => array(
							'required'    => true,
							'description' => __( 'Course ID.', 'learning-management-system' ),
							'type'        => 'integer',
						),
						'sid' => array(
							'required'    => true,
							'description' => __( 'Student ID.', 'learning-management-system' ),
							'type'        => 'integer',
						),
					),
				),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->object_type . '/(?P<cid>[\d]+)/progress',
			array(
				'args' => array(
					'cid' => array(
						'required'    => true,
						'description' => __( 'Course ID.', 'learning-management-system' ),
						'type'        => 'integer',
					),
					'sid' => array(
						'required'    => true,
						'description' => __( 'Student ID.', 'learning-management-system' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => \WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'reset_progress' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
				),
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_progress' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
				),
				array(
					'methods'             => \WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'complete_course' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => array(
						'completed'      => array(
							'required'    => true,
							'description' => __( 'Complete the course.', 'learning-management-system' ),
							'type'        => 'boolean',
						),
						'email_settings' => array(
							'required'    => false,
							'description' => __( 'Email settings.', 'learning-management-system' ),
							'type'        => 'object',
							'items'       => array(
								'type'  => 'boolean',
								'enums' => array( 'student', 'instructor', 'admin' ),
							),
						),
					),
				),
			)
		);
	}

	/**
	 * Checks if a given request has access to get a specific analytics item.
	 *
	 * @since 2.18.0
	 *
	 * @param  \WP_REST_Request $request Full details about the request.
	 * @return \WP_Error|boolean True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		if ( is_null( $this->permission ) ) {
			return new \WP_Error(
				'masteriyo_null_permission',
				__( 'Sorry, the permission object for this resource is null.', 'learning-management-system' )
			);
		}

		if ( masteriyo_is_current_user_admin() || masteriyo_is_current_user_manager() ) {
			return true;
		}

		if ( ! masteriyo_is_current_user_post_author( absint( $request['cid'] ) ) ) {
			return new \WP_Error(
				'masteriyo_rest_cannot_access',
				__( 'Sorry, you are not allowed to access this resource.', 'learning-management-system' ),
				array(
					'status' => rest_authorization_required_code(),
				)
			);
		}

		return true;
	}

	/**
	 * Check if a given request has access to read items.
	 *
	 * @since 2.8.3
	 *
	 * @param  \WP_REST_Request $request Full details about the request.
	 * @return \WP_Error|boolean
	 */
	public function get_items_permissions_check( $request ) {
		if ( is_null( $this->permission ) ) {
			return new \WP_Error(
				'masteriyo_null_permission',
				__( 'Sorry, the permission object for this resource is null.', 'learning-management-system' )
			);
		}

		if ( masteriyo_is_current_user_admin() || masteriyo_is_current_user_manager() ) {
			return true;
		}

		if ( ! masteriyo_is_current_user_post_author( absint( $request['id'] ) ) ) {
			return new \WP_Error(
				'masteriyo_rest_cannot_access',
				__( 'Sorry, you are not allowed to access resources.', 'learning-management-system' ),
				array(
					'status' => rest_authorization_required_code(),
				)
			);
		}

		return true;
	}

	/**
	 * Check if a given request has access to create an item.
	 *
	 * @since 1.10.0 [Free]
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return WP_Error|boolean
	 */
	public function create_item_permissions_check( $request ) {
		if ( is_null( $this->permission ) ) {
			return new \WP_Error(
				'masteriyo_null_permission',
				__( 'Sorry, the permission object for this resource is null.', 'learning-management-system' )
			);
		}

		if ( masteriyo_is_current_user_admin() || masteriyo_is_current_user_manager() ) {
			return true;
		}

		if ( ! masteriyo_is_current_user_post_author( absint( $request['id'] ) ) ) {
			return new \WP_Error(
				'masteriyo_rest_cannot_update',
				__( 'Sorry, you are not allowed to create this resource.', 'learning-management-system' ),
				array(
					'status' => rest_authorization_required_code(),
				)
			);
		}

		return true;
	}

	/**
	 * Check if a given request has access to delete an item.
	 *
	 * @since 2.18.0
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_Error|boolean
	 */
	public function update_item_permissions_check( $request ) {
		if ( is_null( $this->permission ) ) {
			return new \WP_Error(
				'masteriyo_null_permission',
				__( 'Sorry, the permission object for this resource is null.', 'learning-management-system' )
			);
		}

		if ( masteriyo_is_current_user_admin() || masteriyo_is_current_user_manager() ) {
			return true;
		}

		if ( ! masteriyo_is_current_user_post_author( absint( $request['cid'] ) ) ) {
			return new \WP_Error(
				'masteriyo_rest_cannot_update',
				__( 'Sorry, you are not allowed to update this resource.', 'learning-management-system' ),
				array(
					'status' => rest_authorization_required_code(),
				)
			);
		}

		return true;
	}

	/**
	 * Check if a given request has access to delete an item.
	 *
	 * @since 1.10.0 [Free]
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_Error|boolean
	 */
	public function delete_item_permissions_check( $request ) {
		if ( is_null( $this->permission ) ) {
			return new \WP_Error(
				'masteriyo_null_permission',
				__( 'Sorry, the permission object for this resource is null.', 'learning-management-system' )
			);
		}

		if ( masteriyo_is_current_user_admin() || masteriyo_is_current_user_manager() ) {
			return true;
		}

		if ( ! masteriyo_is_current_user_post_author( absint( $request['cid'] ) ) ) {
			return new \WP_Error(
				'masteriyo_rest_cannot_delete',
				__( 'Sorry, you are not allowed to delete this resource.', 'learning-management-system' ),
				array(
					'status' => rest_authorization_required_code(),
				)
			);
		}

		return true;
	}

	/**
	 * Creates an enrollment for a student in a course.
	 *
	 * @since 1.10.0 [Free]
	 *
	 * @param \WP_REST_Request $request The request object.
	 *
	 * @return \WP_REST_Response|\WP_Error The response object, or a WP_Error object on failure.
	 */
	public function create_enrollment( $request ) {
		$course_id   = absint( $request['id'] );
		$student_ids = $request->get_param( 'student_ids' );

		if ( empty( $course_id ) ) {
			return new \WP_Error( 'masteriyo_rest_invalid_course_id', __( 'Invalid course id.', 'learning-management-system' ) );
		}

		if ( empty( $student_ids ) || ! is_array( $student_ids ) ) {
			return new \WP_Error( 'masteriyo_rest_invalid_student_ids', __( 'Invalid student ids.', 'learning-management-system' ) );
		}

		$student_ids = array_map( 'absint', $student_ids );

		foreach ( $student_ids as $student_id ) {
			$user_course = masteriyo_get_user_course_by_user_and_course( $student_id, $course_id );

			if ( ! $user_course ) {
				/** @var \Masteriyo\Models\UserCourse $user_course */
				$user_course = masteriyo( 'user-course' );

				$user_course->set_course_id( $course_id );
				$user_course->set_user_id( $student_id );

				$user_course->save();

				/**
				 * Fires when a user is enrolled manually.
				 *
				 * @since 2.21.0
				 *
				 * @param \Masteriyo\Models\UserCourse $user_course
				 */
				do_action( 'masteriyo_user_enrolled_manually', $user_course );
			}
		}

		return rest_ensure_response( array( 'message' => __( 'Enrollment created successfully', 'learning-management-system' ) ) );
	}

	/**
	 * Deletes a user's enrollment in a course.
	 *
	 * @since 1.10.0 [Free]
	 *
	 * @param \WP_REST_Request $request The request object.
	 *
	 * @return \WP_Error|\WP_REST_Response The response object, or a WP_Error object on failure.
	 */
	public function delete_enrollment( $request ) {
		global $wpdb;

		$course_id  = absint( $request['cid'] );
		$student_id = absint( $request['sid'] );

		if ( empty( $course_id ) ) {
			return new \WP_Error( 'masteriyo_rest_invalid_course_id', __( 'Invalid course id.', 'learning-management-system' ) );
		}

		if ( empty( $student_id ) ) {
			return new \WP_Error( 'masteriyo_rest_invalid_student_id', __( 'Invalid student id.', 'learning-management-system' ) );
		}

		$user_course = masteriyo_get_user_course_by_user_and_course( $student_id, $course_id );

		if ( ! $user_course ) {
			return new \WP_Error( 'masteriyo_rest_invalid_user_course', __( 'User course not found.', 'learning-management-system' ) );
		}

		if ( $user_course->delete() ) {
			delete_course_progress_and_related_data( $student_id, $course_id );
		}

		return rest_ensure_response( array( 'message' => __( 'User\'s enrollment deleted successfully', 'learning-management-system' ) ) );
	}

	/**
	 * Resets the course progress for a student.
	 *
	 * @since 1.10.0 [Free]
	 *
	 * @param \WP_REST_Request $request Full details about the request.
	 *
	 * @return \WP_Error|\WP_REST_Response
	 */
	public function reset_progress( $request ) {
		$course_id  = absint( $request['cid'] );
		$student_id = absint( $request['sid'] );

		if ( empty( $course_id ) ) {
			return new \WP_Error( 'masteriyo_rest_invalid_course_id', __( 'Invalid course id.', 'learning-management-system' ) );
		}

		if ( empty( $student_id ) ) {
			return new \WP_Error( 'masteriyo_rest_invalid_student_id', __( 'Invalid student id.', 'learning-management-system' ) );
		}

		try {
			$progress = masteriyo_get_course_progress_by_user_and_course( $student_id, $course_id );

			if ( is_wp_error( $progress ) ) {
				return $progress;
			}

			if ( ! $progress ) {
				return new \WP_Error(
					'masteriyo_rest_invalid_course_progress',
					__( 'No course progress found for the student.', 'learning-management-system' ),
					array( 'status' => 404 )
				);
			}

			$course_progress_id = $progress->get_id();

			if ( $progress->delete() ) {
				global $wpdb;

				$user_activities_table = $wpdb->prefix . 'masteriyo_user_activities';

				if ( $wpdb->get_var( "SHOW TABLES LIKE '$user_activities_table'" ) === $user_activities_table ) {  // phpcs:ignore

					$wpdb->delete(
						$user_activities_table,
						array(
							'parent_id' => $course_progress_id,
						)
					);
				}

				$quiz_attempts_table_name = $wpdb->prefix . 'masteriyo_quiz_attempts';

				if ( $wpdb->get_var( "SHOW TABLES LIKE '$quiz_attempts_table_name'" ) === $quiz_attempts_table_name ) {  // phpcs:ignore
					$wpdb->delete(
						"{$wpdb->prefix}masteriyo_quiz_attempts",
						array(
							'course_id' => $course_id,
							'user_id'   => $student_id,
						)
					);
				}

				masteriyo_delete_user_course_assignment_submissions( $course_id, $student_id );
				masteriyo_delete_user_course_gradebooks( $course_id, $student_id );
			}
		} catch ( \Exception $e ) {
			return new \WP_Error( 'masteriyo_rest_reset_progress_error', $e->getMessage() );
		}

		return rest_ensure_response( array( 'message' => __( 'Progress reset successfully.', 'learning-management-system' ) ) );
	}

	/**
	 * Get the progress of a course for a specific student.
	 *
	 * @since 1.10.0 [Free]
	 *
	 * @param \WP_REST_Request $request Full details about the request.
	 *
	 * @return \WP_Error|\WP_REST_Response The course progress for the student.
	 */
	public function get_progress( $request ) {
		$course_id  = absint( $request['cid'] );
		$student_id = absint( $request['sid'] );
		$student    = masteriyo_get_user( $student_id );

		if ( ! $student ) {
			return new \WP_Error(
				'masteriyo_rest_invalid_student_id',
				__( 'Invalid student id.', 'learning-management-system' ),
				array( 'status' => 404 )
			);
		}

		$report = array();

		$report['student'] = array(
			'id'    => $student_id,
			'name'  => $name ?? $student->get_display_name(),
			'email' => $student->get_email(),
		);

		// For the manual course completion email.
		$email_settings = array(
			'admin'      => masteriyo_is_admin_manual_course_completion_email_enabled(),
			'instructor' => masteriyo_is_instructor_manual_course_completion_email_enabled(),
			'student'    => masteriyo_is_student_manual_course_completion_email_enabled(),
		);

		$not_found_response = array(
			'sections'       => array(),
			'course'         => array(),
			'student'        => $report['student'],
			'message'        => __( 'No course progress found for the student.', 'learning-management-system' ),
			'email_settings' => $email_settings,
		);

		if ( empty( $course_id ) ) {
			$not_found_response['message'] = __( 'Invalid course id.', 'learning-management-system' );
			return rest_ensure_response( $not_found_response );
		}

		if ( empty( $student_id ) ) {
			$not_found_response['message'] = __( 'Invalid student id.', 'learning-management-system' );
			return rest_ensure_response( $not_found_response );
		}

		$progress = masteriyo_get_course_progress_by_user_and_course( $student_id, $course_id );

		if ( is_wp_error( $progress ) || is_null( $progress ) || ! $progress ) {
			$not_found_response['course'] = array(
				'id'   => $course_id,
				'name' => get_the_title( $course_id ),
			);
			return rest_ensure_response( $not_found_response );
		}

		$progress_items = $progress->get_items();

		$sections = masteriyo_get_sections(
			array(
				'posts_per_page' => -1,
				'order'          => 'asc',
				'orderby'        => 'menu_order',
				'course_id'      => $course_id,
			)
		);

		$sections = array_filter(
			array_map(
				function( $section ) {
					if ( ! $section instanceof \Masteriyo\Models\Section ) {
						return null;
					}

					$all_children_query = new \WP_Query(
						array(
							'post_type'      => SectionChildrenPostType::all(),
							'post_status'    => PostStatus::PUBLISH || ZoomMeetingStatus::UPCOMING || ZoomMeetingStatus::ACTIVE,
							'posts_per_page' => -1,
							'post_parent'    => $section->get_id(),
							'orderby'        => 'menu_order',
							'order'          => 'asc',
						)
					);

					$all_children_posts = $all_children_query->posts;

					$post_types = CourseProgressPostType::all();

					$filter_children_posts = array_filter(
						$all_children_posts,
						function( $post ) use ( $section, $post_types ) {

							// This is for backward compatibility, because there was previously not force deletion for the lesson.
							if ( PostType::LESSON === $post->post_type && PostStatus::PUBLISH !== $post->post_status ) {
								return null;
							}

							if ( PostType::QUIZ === $post->post_type && PostStatus::PUBLISH !== $post->post_status ) {
								return null;
							}

							return in_array( $post->post_type, $post_types, true ) && $section->get_id() === $post->post_parent;
						}
					);

					$data = array(
						'id'    => $section->get_id(),
						'name'  => $section->get_name(),
						'items' => array(),
					);

					if ( ! empty( $filter_children_posts ) ) {
						foreach ( $filter_children_posts as $filter_children_post ) {
							if ( ! $filter_children_post instanceof \WP_Post ) {
								continue;
							}

							$data['items'][] = array(
								'id'   => $filter_children_post->ID,
								'type' => $filter_children_post->post_type,
								'name' => $filter_children_post->post_title,
							);
						}
					}

					return $data;
				},
				$sections
			)
		);

		if ( ! empty( $progress_items ) ) {
			foreach ( $progress_items as $progress_item ) {

				if ( ! $progress_item instanceof \Masteriyo\Models\CourseProgressItem ) {
					continue;
				}

				foreach ( $sections as &$section ) {
					foreach ( $section['items'] as &$item ) {
						if ( $item['id'] === $progress_item->get_item_id() ) {
							$item['progress'] = array(
								'item_type'    => $progress_item->get_item_type(),
								'completed'    => $progress_item->get_completed(),
								'started_at'   => masteriyo_rest_prepare_date_response( $progress_item->get_started_at() ),
								'modified_at'  => masteriyo_rest_prepare_date_response( $progress_item->get_modified_at() ),
								'completed_at' => masteriyo_rest_prepare_date_response( $progress_item->get_completed_at() ),
							);

							$item['activity_log'] = masteriyo_get_user_activity_meta( $student_id, $progress_item->get_item_id(), 'activity_log', $progress_item->get_item_type() );

							break;
						}
					}
				}
			}
		}

		$course_data = array(
			'id'              => $course_id,
			'name'            => html_entity_decode( get_the_title( $course_id ) ),
			'progress_status' => $progress->get_status(),
			'started_at'      => masteriyo_rest_prepare_date_response( $progress->get_started_at() ),
			'completed_at'    => masteriyo_rest_prepare_date_response( $progress->get_completed_at() ),
		);

		$name = trim( sprintf( '%s %s', $student->get_first_name(), $student->get_last_name() ) );

		$report['sections']       = $sections;
		$report['course']         = $course_data;
		$report['email_settings'] = $email_settings;

		return rest_ensure_response( $report );
	}

	/**
	 * Get a collection of students data.
	 *
	 * @since 2.8.3
	 *
	 * @param \WP_REST_Request $request Full details about the request.
	 *
	 * @return \WP_Error|\WP_REST_Response
	 */
	public function get_student_data( $request ) {
		$items    = $this->prepare_students_for_response( $request );
		$response = rest_ensure_response( $items );

		/**
		 * Filter the data for a response.
		 *
		 * The dynamic portion of the hook name, $this->object_type,
		 * refers to object type being prepared for the response.
		 *
		 * @since 2.8.3
		 *
		 * @param \WP_REST_Response $response The response object.
		 * @param array             $items Analytics data.
		 * @param \WP_REST_Request  $request  Request object.
		 */
		return apply_filters( "masteriyo_rest_prepare_{$this->object_type}_object", $response, $items, $request );
	}

	/**
	 * Get a collection of courses data.
	 *
	 * @since 2.8.3
	 *
	 * @param \WP_REST_Request $request Full details about the request.
	 *
	 * @return \WP_Error|\WP_REST_Response
	 */
	public function get_items( $request ) {
		$items    = $this->prepare_items_for_response( $request );
		$response = rest_ensure_response( $items );

		/**
		 * Filter the data for a response.
		 *
		 * The dynamic portion of the hook name, $this->object_type,
		 * refers to object type being prepared for the response.
		 *
		 * @since 2.8.3
		 *
		 * @param \WP_REST_Response $response The response object.
		 * @param array             $items Analytics data.
		 * @param \WP_REST_Request  $request  Request object.
		 */
		return apply_filters( "masteriyo_rest_prepare_{$this->object_type}_object", $response, $items, $request );
	}

	/**
	 * Prepare items for response.
	 *
	 * @since 2.8.3
	 *
	 * @param \WP_REST_Request $request Full data about the request.
	 *
	 * @return \WP_REST_Response
	 */
	protected function prepare_students_for_response( \WP_REST_Request $request ) {
		$per_page     = absint( $request->get_param( 'per_page' ) ?? 5 );
		$page         = absint( $request->get_param( 'page' ) ?? 1 );
		$search       = sanitize_text_field( $request->get_param( 'search' ) ?? '' );
		$items        = array();
		$courses_data = $request->get_url_params();
		$course_ids   = $courses_data['id'];

		$items['enrolled_students'] = $this->get_enrolled_students( $course_ids, $per_page, $page, $search );
		$items['meta_data']         = $this->get_meta_data( $course_ids, $per_page, $page, $search );

		/**
		 * Filters rest prepared analytics items.
		 *
		 * @since 2.8.3
		 *
		 * @param array $items Items data.
		 * @param \WP_REST_Request $request Request.
		 */
		return apply_filters( 'masteriyo_rest_prepared_analytics_items', $items, $request );
	}

	/**
	 * Prepare items for response.
	 *
	 * @since 2.8.3
	 *
	 * @param \WP_REST_Request $request Full data about the request.
	 *
	 * @return \WP_REST_Response
	 */
	protected function prepare_items_for_response( \WP_REST_Request $request ) {
		$start_date   = $request->get_param( 'start_date' );
		$end_date     = $request->get_param( 'end_date' );
		$items        = array();
		$courses_data = $request->get_url_params();
		$course_ids   = $courses_data['id'];
		$course       = masteriyo_get_course( $courses_data['id'] );

		$items['name']              = $course->get_title();
		$items['lessons']           = $this->get_lessons_data( $course_ids );
		$items['quizzes']           = $this->get_quizzes_data( $course_ids );
		$items['questions']         = $this->get_questions_data( $course_ids );
		$items['questions_answers'] = $this->get_questions_answers_data( $course_ids );
		$items['reviews']           = $this->get_reviews_data( $course_ids );
		$items['instructors']       = $this->get_instructors_data( $course_ids );
		$items['sales']             = $this->get_sales_data( $course_ids, $start_date, $end_date );
		$items['user_courses']      = $this->get_enrolled_courses_data( $course_ids, $start_date, $end_date );
		$items['authors']           = $this->get_authors_info( $course_ids );

		/**
		 * Filters rest prepared analytics items.
		 *
		 * @since 2.8.3
		 *
		 * @param array $items Items data.
		 * @param \WP_REST_Request $request Request.
		 */
		return apply_filters( 'masteriyo_rest_prepared_analytics_course_items', $items, $request );
	}

	/**
	 * Get courses data.
	 *
	 * @since 2.8.3
	 *
	 * @return array
	 */
	protected function get_courses_data() {
		$query = new \WP_Query(
			array(
				'post_status'    => PostStatus::PUBLISH,
				'post_type'      => PostType::COURSE,
				'posts_per_page' => -1,
				'author'         => masteriyo_is_current_user_admin() || masteriyo_is_current_user_manager() ? null : get_current_user_id(),
				'fields'         => 'ids',
			)
		);

		return array(
			'ids'   => $query->posts,
			'total' => $query->post_count,
		);
	}

	/**
	 * Get lessons count.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Course IDs.
	 *
	 * @return array
	 */
	protected function get_lessons_data( $course_ids ) {
		$data = array(
			'total' => 0,
		);

		if ( $course_ids ) {
			$query         = new \WP_Query(
				array(
					'post_status'    => PostStatus::PUBLISH,
					'post_type'      => PostType::LESSON,
					'posts_per_page' => 1,
					'meta_query'     => array(
						array(
							'key'     => '_course_id',
							'value'   => $course_ids,
							'compare' => 'IN',
						),
					),
					'fields'         => 'ids',
				)
			);
			$data['total'] = $query->found_posts;
		}

		return $data;
	}

	/**
	 * Get quizzes count.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Course IDs.
	 *
	 * @return array
	 */
	protected function get_quizzes_data( $course_ids ) {
		$data = array(
			'total' => 0,
		);

		if ( $course_ids ) {
			$query         = new \WP_Query(
				array(
					'post_status'    => PostStatus::PUBLISH,
					'post_type'      => PostType::QUIZ,
					'posts_per_page' => 1,
					'meta_query'     => array(
						array(
							'key'     => '_course_id',
							'value'   => $course_ids,
							'compare' => 'IN',
						),
					),
					'fields'         => 'ids',
				)
			);
			$data['total'] = $query->found_posts;
		}

		return $data;
	}

	/**
	 * Get questions count.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Course IDs.
	 *
	 * @return array
	 */
	protected function get_questions_data( $course_ids ) {
		$data = array(
			'total' => 0,
		);

		if ( $course_ids ) {
			$query         = new \WP_Query(
				array(
					'post_status'    => PostStatus::PUBLISH,
					'post_type'      => PostType::QUESTION,
					'posts_per_page' => 1,
					'meta_query'     => array(
						array(
							'key'     => '_course_id',
							'value'   => $course_ids,
							'compare' => 'IN',
						),
					),
					'fields'         => 'ids',
				)
			);
			$data['total'] = $query->found_posts;
		}

		return $data;
	}

	/**
	 * Get instructors count.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Course IDs.
	 *
	 * @return array
	 */
	protected function get_instructors_data( $course_ids ) {
		$course            = masteriyo_get_course( $course_ids );
		$author            = $course->get_author_id();
		$additional_author = (array) $course->get_meta( '_additional_authors', false );
		$authors           = array_unique( array_merge( $additional_author, array( $author ) ) );

		return array(
			'total' => count( $authors ),
		);
	}

	/**
	 * Get reviews count.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Course IDs.
	 *
	 * @return array
	 */
	protected function get_reviews_data( $course_ids ) {
		$data = array(
			'total' => 0,
		);

		if ( $course_ids ) {
			$query         = new \WP_Comment_Query(
				array(
					'type'     => CommentType::COURSE_REVIEW,
					'status'   => CommentStatus::APPROVE_STR,
					'post__in' => $course_ids,
					'count'    => true,
					'number'   => 1,
				)
			);
			$data['total'] = $query->get_comments();
		}

		return $data;
	}

	/**
	 * Get question/answers count.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Course IDs.
	 *
	 * @return array
	 */
	protected function get_questions_answers_data( $course_ids ) {
		$data = array(
			'total' => 0,
		);

		if ( $course_ids ) {
			$query         = new \WP_Comment_Query(
				array(
					'type'     => CommentType::COURSE_QA,
					'status'   => CommentStatus::APPROVE_STR,
					'count'    => true,
					'post__in' => $course_ids,
					'number'   => 1,
				)
			);
			$data['total'] = $query->get_comments();
		}

		return $data;
	}

	/**
	 * Get enrolled courses data.
	 *
	 * @since 2.8.3
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return array
	 */
	protected function get_enrolled_courses_data( $course_ids, $start_date, $end_date ) {
		global $wpdb;

		$data = array();

		$data['total']    = masteriyo_get_user_courses_count_by_course( $course_ids );
		$data['students'] = masteriyo_count_enrolled_users( $course_ids );

		if ( $course_ids ) {
			// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
			$data['data'] = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT DATE(date_start) as date, COUNT(*) as count
					FROM {$wpdb->prefix}masteriyo_user_items
					WHERE item_id IN (" . implode( ',', array( $course_ids ) ) . ')
					AND DATE(date_start) >= %s AND DATE(date_start) <= %s
					GROUP BY DATE(date_start)',
					$start_date,
					$end_date
				),
				ARRAY_A
			);
			// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
		}

		$data['data'] = $this->format_series_data( array_values( $data['data'] ?? array() ), $start_date, $end_date, '1 day' );
		return $data;
	}

	/**
	 * Get sales data.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Course IDs.
	 * @param string $start_date Start date.
	 * @param string $end_date End date.
	 *
	 * @return array
	 */
	protected function get_sales_data( $course_ids, $start_date, $end_date ) {
		global $wpdb;
		$data = array();

		if ( $course_ids ) {
			// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
			$sql = $wpdb->prepare(
				"SELECT
				DATE(p.post_modified) AS date,
				p.post_status AS status,
				COUNT(*) AS count,
				SUM(om.meta_value) AS amount
			FROM
				{$wpdb->prefix}masteriyo_order_items o
				INNER JOIN $wpdb->posts AS p ON p.ID = o.order_id
				INNER JOIN {$wpdb->prefix}masteriyo_order_itemmeta om ON om.order_item_id = o.order_item_id
			WHERE
				p.post_status IN (%s, %s)
				AND o.order_item_type IN (
					SELECT o2.order_item_type
					FROM {$wpdb->prefix}masteriyo_order_items o2
					INNER JOIN $wpdb->posts AS p2 ON p2.ID = o2.order_id
					WHERE p2.post_status IN (%s, %s)
				)
				AND p.post_modified >= %s AND p.post_modified <= %s
				AND om.meta_key = 'total'
				AND o.order_item_id IN (
					SELECT o3.order_item_id
					FROM {$wpdb->prefix}masteriyo_order_items o3
					INNER JOIN {$wpdb->prefix}masteriyo_order_itemmeta om2 ON om2.order_item_id = o3.order_item_id
					WHERE om2.meta_key = 'course_id'
					AND om2.meta_value IN (" . implode( ',', array( $course_ids ) ) . ')
				)
			GROUP BY DATE(p.post_modified), p.post_status',
				OrderStatus::COMPLETED,
				OrderStatus::REFUNDED,
				OrderStatus::COMPLETED,
				OrderStatus::REFUNDED,
				$start_date,
				$end_date
			);

			$results = $wpdb->get_results( $sql, ARRAY_A );
			// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared

			$data['earnings']['data'] = array_filter(
				$results,
				function( $result ) {
					return OrderStatus::COMPLETED === $result['status'];
				}
			);

			$data['refunds']['data'] = array_filter(
				$results,
				function( $result ) {
					return OrderStatus::REFUNDED === $result['status'];
				}
			);
		}

		$data['earnings']['data'] = $this->format_series_data( array_values( $data['earnings']['data'] ?? array() ), $start_date, $end_date, '1 day' );
		$data['refunds']['data']  = $this->format_series_data( array_values( $data['refunds']['data'] ?? array() ), $start_date, $end_date, '1 day' );

		return $data;
	}

	/**
	 * Format series data.
	 *
	 * Prefills empty data with 0.
	 *
	 * @since 2.8.3
	 *
	 * @param array $data Table name.
	 * @param DateTime $start Start date.
	 * @param DateTime $end End date.
	 * @param string $interval Interval.
	 */
	protected function format_series_data( $data, $start, $end, $interval ) {
		$start = new \DateTime( $start );
		$end   = new \DateTime( $end );

		$end->modify( '+1 day' );

		$interval       = \DateInterval::createFromDateString( $interval );
		$period         = new \DatePeriod( $start, $interval, $end );
		$formatted_data = array();

		foreach ( $period as $date ) {
			$date  = $date->format( 'Y-m-d' );
			$found = array_search( $date, array_column( $data, 'date' ), true );

			if ( false !== $found ) {
				$data[ $found ]['count'] = absint( $data[ $found ]['count'] );
			}

			$formatted_data[] = wp_parse_args(
				false !== $found ? $data[ $found ] : array(),
				array(
					'date'  => $date,
					'count' => 0,
				)
			);
		}

		return $formatted_data;
	}

	/**
	 * Retrieves metadata for the enrolled students.
	 *
	 * @since 2.8.3
	 *
	 * @param int $course_id The ID of the course.
	 * @param int $per_page The number of users to include per page. Default is 5.
	 * @param int $page The current page number. Default is 1.
	 * @param string $search Search string. Default is empty.
	 *
	 * @return array An array of metadata including total enrolled users, total pages, current page, and users per page.
	 */
	protected function get_meta_data( $course_id, $per_page = 5, $page = 1, $search = '' ) {
		global $wpdb;

		$course_id = absint( $course_id );

		$exclude_roles = array( 'administrator', 'masteriyo_manager' );

		$roles_condition = '';
		foreach ( $exclude_roles as $role ) {
			$roles_condition .= $wpdb->prepare(
				" AND NOT EXISTS (
                SELECT *
                FROM $wpdb->usermeta
                WHERE user_id = {$wpdb->prefix}masteriyo_user_items.user_id
                AND meta_key = '{$wpdb->prefix}capabilities'
                AND meta_value LIKE %s
            )",
				'%' . $wpdb->esc_like( '"' . $role . '"' ) . '%'
			);
		}

		$additional_authors = get_post_meta( $course_id, '_additional_authors', false );
		$additional_authors = array_map( 'absint', array_filter( $additional_authors ) );
		$exclude_authors    = array_unique( array_merge( $additional_authors, array( get_post_field( 'post_author', $course_id ) ) ) );

		$authors_condition = '';
		if ( ! empty( $exclude_authors ) ) {
			$placeholders      = implode( ',', array_fill( 0, count( $exclude_authors ), '%d' ) );
			$authors_condition = $wpdb->prepare( ' AND user_id NOT IN (' . $placeholders . ')', $exclude_authors ); // phpcs:ignore
		}

		$search_condition = '';
		if ( ! empty( $search ) ) {
			$search_condition = $wpdb->prepare( ' AND (u.user_login LIKE %s OR u.user_email LIKE %s)', '%' . $wpdb->esc_like( $search ) . '%', '%' . $wpdb->esc_like( $search ) . '%' );
		}

		// phpcs:disable
		$sql_prepared = $wpdb->prepare(
			"SELECT COUNT(user_id) as total
			FROM {$wpdb->prefix}masteriyo_user_items
      LEFT JOIN {$wpdb->users} u ON user_id = u.ID
			WHERE item_id = %d
			AND (status = %s OR status = %s)
			$roles_condition
			$authors_condition
      $search_condition",
			$course_id,
			UserCourseStatus::ACTIVE,
			UserCourseStatus::ENROLLED
		);

		$total_count_result = $wpdb->get_var( $sql_prepared );
		// phpcs:enable

		$total_count = intval( $total_count_result );
		$pages       = $total_count > 0 ? ceil( $total_count / $per_page ) : 1;

		$meta_data = array(
			'total'        => $total_count,
			'pages'        => $pages,
			'current_page' => $page,
			'per_page'     => $per_page,
		);

		return $meta_data;
	}

	/**
	 * Fetch enrolled users based on their role and provided criteria.
	 *
	 * @since 2.8.3
	 *
	 * @param mixed $course_id The course ID(s) to fetch users for. Can be a single ID or an array of IDs.
	 * @param int $per_page Number of users to fetch per page. Default is 5.
	 * @param int $page Page number. Default is 1.
	 * @param string $search Search string. Default is empty.
	 *
	 * @return array An array of enrolled users with their ID, username, email, full name, progress, and registered date.
	 */
	protected function get_enrolled_users( $course_id, $per_page = 5, $page = 1, $search = '' ) {
		global $wpdb;

		$course_ids = is_array( $course_id ) ? array_filter( array_map( 'absint', $course_id ) ) : absint( $course_id );

		$exclude_roles = array( 'administrator', 'masteriyo_manager' );

		$roles_condition = '';
		foreach ( $exclude_roles as $role ) {
			$roles_condition .= $wpdb->prepare(
				" AND NOT EXISTS (
                SELECT *
                FROM $wpdb->usermeta
                WHERE user_id = {$wpdb->prefix}masteriyo_user_items.user_id
                AND meta_key = '{$wpdb->prefix}capabilities'
                AND meta_value LIKE %s
            )",
				'%' . $wpdb->esc_like( '"' . $role . '"' ) . '%'
			);
		}

		$additional_authors = get_post_meta( $course_id, '_additional_authors', false );
		$additional_authors = array_map( 'absint', array_filter( $additional_authors ) );
		$exclude_authors    = array_unique( array_merge( $additional_authors, array( get_post_field( 'post_author', $course_id ) ) ) );

		$authors_condition = '';
		if ( ! empty( $exclude_authors ) ) {
			$placeholders      = implode( ',', array_fill( 0, count( $exclude_authors ), '%d' ) );
			$authors_condition = $wpdb->prepare( ' AND user_id NOT IN (' . $placeholders . ')', $exclude_authors ); // phpcs:ignore
		}

		$search_condition = '';
		if ( ! empty( $search ) ) {
			$search_condition = $wpdb->prepare( ' AND (u.user_login LIKE %s OR u.user_email LIKE %s)', '%' . $wpdb->esc_like( $search ) . '%', '%' . $wpdb->esc_like( $search ) . '%' );
		}

		$offset = ( $page - 1 ) * $per_page;

		// phpcs:disable
		$sql_prepared = $wpdb->prepare(
			"SELECT  user_id
			FROM {$wpdb->prefix}masteriyo_user_items
      LEFT JOIN {$wpdb->users} u ON user_id = u.ID
			WHERE item_id = %d
			AND (status = %s OR status = %s)
			$roles_condition
			$authors_condition
			$search_condition
			LIMIT %d OFFSET %d",
			$course_id,
			UserCourseStatus::ACTIVE,
			UserCourseStatus::ENROLLED,
			$per_page,
			$offset
		);

		$enrolled_user_ids = $wpdb->get_col( $sql_prepared );
		// phpcs:enable

		$users_data = array();
		foreach ( $enrolled_user_ids as $enrolled_user_id ) {
			$user = masteriyo_get_user( $enrolled_user_id );

			if ( is_wp_error( $user ) || is_null( $user ) ) {
				continue;
			}

			$progress = $this->get_user_course_progress( $user, $course_ids );

			$users_data[] = array(
				'id'        => $user->get_id(),
				'user_name' => $user->get_username(),
				'email'     => $user->get_email(),
				'progress'  => $progress,
			);
		}

		return $users_data;
	}

	/**
	 * Get the progress of a user in given courses.
	 *
	 * This method assumes the existence of a CourseProgressQuery class for fetching
	 * course progress data, and a masteriyo_get_course function for retrieving course
	 * details by ID.
	 *
	 * @since 2.8.3
	 *
	 * @param WP_User $user WordPress user object for which progress is calculated.
	 * @param array|int $course_ids Course ID or array of course IDs to calculate progress for.
	 * @return array Associative array of course progress for each provided course ID.
	 */
	protected function get_user_course_progress( $user, $course_ids ) {
		$course_ids    = (array) $course_ids;
		$user_progress = '0%';

		foreach ( $course_ids as $course_id ) {
			$query = new CourseProgressQuery(
				array(
					'user_id'   => $user->get_id(),
					'course_id' => $course_id,
					'status'    => array( CourseProgressStatus::STARTED, CourseProgressStatus::PROGRESS, CourseProgressStatus::COMPLETED ),
				)
			);

				$progresses = $query->get_course_progress();

			if ( ! empty( $progresses ) ) {
				$enrolled_courses = array_filter(
					array_map(
						function ( $progress ) {
							$course = masteriyo_get_course( $progress->get_course_id() );
							if ( is_null( $course ) ) {
									return null;
							}
							$course->progress = $progress;
							return $course;
						},
						$progresses
					)
				);

				if ( ! empty( $enrolled_courses ) ) {
					$progress_status = reset( $enrolled_courses )->get_progress_status( true, $user->get_id() );

					$user_progress = $progress_status;
				} else {
					$user_progress = '0%';
				}
			} else {
				$user_progress = '0%';
			}
		}

		return $user_progress;
	}


	/**
	 * Fetch enrolled students based on the provided criteria.
	 *
	 * @since 2.8.3
	 *
	 * @param int $course_id The course ID.
	 * @param int $count The number of students to fetch. Default is 5.
	 * @param int $page The page of students to fetch. Default is 1.
	 * @param string $search The search string. Default is empty.
	 *
	 * @return array An array of enrolled students.
	 */
	protected function get_enrolled_students( $course_id, $per_page = 5, $page = 1, $search = '' ) {
		return $this->get_enrolled_users( $course_id, $per_page, $page, $search );
	}

	/**
	 * Get the number of progress courses.
	 *
	 * @since 2.8.3
	 *
	 * @param Masteriyo\Models\User|int $user User.
	 *
	 * @return int
	 */
	public function masteriyo_get_progress_courses_count( $user ) {
		global $wpdb;

		$user_id = is_a( $user, 'Masteriyo\Models\User' ) ? $user->get_id() : $user;

		$count = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(*) FROM {$wpdb->prefix}masteriyo_user_activities
			WHERE user_id = %d AND activity_type = 'course_progress'
			AND ( activity_status = 'progress' )  AND parent_id = 0",
				$user_id
			)
		);

		return $count;
	}

	/**
	 * Fetch Authors of course based on the provided course id.
	 *
	 * @since 2.8.3
	 *
	 * @param int $count The number of students to fetch.
	 *
	 * @return array An array of enrolled students.
	 */
	protected function get_authors_info( $course_id ) {
		$course  = masteriyo_get_course( $course_id );
		$author  = masteriyo_get_user( $course->get_author_id() );
		$authors = array();

		if ( is_wp_error( $author ) || is_null( $author ) ) {
			$authors[] = null;
		} else {
			$authors[ $author->get_id() ] = array(
				'id'              => $author->get_id(),
				'display_name'    => $author->get_display_name(),
				'user_name'       => $author->get_username(),
				'avatar_url'      => $author->profile_image_url(),
				'registered_date' => masteriyo_rest_prepare_date_response( $author->get_date_created() ),
				'email'           => $author->get_email(),
			);
		}

		$additional_authors = (array) $course->get_meta( '_additional_authors', false );

		if ( ! empty( $additional_authors ) ) {
			foreach ( $additional_authors as $additional_author ) {
				$add_author                       = masteriyo_get_user( intval( $additional_author ) );
				$authors[ $add_author->get_id() ] = array(
					'id'              => $add_author->get_id(),
					'display_name'    => $add_author->get_display_name(),
					'user_name'       => $add_author->get_username(),
					'avatar_url'      => $add_author->profile_image_url(),
					'registered_date' => masteriyo_rest_prepare_date_response( $add_author->get_date_created() ),
					'email'           => $add_author->get_email(),
				);
			}
		}

		$authors = array_unique( $authors, SORT_REGULAR );

		return array_values( $authors );
	}

	/**
	 * Completes the course for a given student.
	 *
	 * Validates the course ID, student ID, and completion status from the request.
	 * Retrieves the course and student objects, and updates their progress.
	 * Completes course items and returns a success response if successful.
	 * Returns error responses for invalid input, missing course or student,
	 * or failure to complete course items.
	 *
	 * @param WP_REST_Request $request The request object containing parameters 'cid', 'sid', and 'completed'.
	 *
	 * @return WP_REST_Response|WP_Error The response object on success, or WP_Error object on failure.
	 */
	public function complete_course( $request ) {
		$course_id  = absint( $request->get_param( 'cid' ) );
		$student_id = absint( $request->get_param( 'sid' ) );
		$completed  = masteriyo_string_to_bool( $request->get_param( 'completed' ) );

		if ( ! $course_id || ! $student_id || ! $completed ) {
			return new WP_Error( 'masteriyo_invalid_request', __( 'Invalid course, student, or completed status.', 'learning-management-system' ), array( 'status' => 400 ) );
		}

		$email_settings = $request->get_param( 'email_settings' );

		$result = masteriyo_complete_specific_course_for_user( $course_id, $student_id, $email_settings );

		if ( is_wp_error( $result ) ) {
			return $result;
		}

		return rest_ensure_response(
			array(
				'message'            => __( 'Course completed successfully.', 'learning-management-system' ),
				'course_progress_id' => $result['course_progress']->get_id(),
				'completed_items'    => $result['completed_items'],
			)
		);
	}
}
