<?php
/**
 * Calendar Controller for managing calendar-related functionalities in Masteriyo.
 *
 * Extends PostsController to provide methods for managing and retrieving calendar events
 * such as lessons, quizzes, assignments, and Zoom meetings.
 *
 *
 * @since 2.8.3
 *
 * @package Masteriyo\Addons\Calendar\RestApi
 */

namespace Masteriyo\Addons\Calendar\RestApi;

defined( 'ABSPATH' ) || exit;

use Exception;
use Masteriyo\Addons\ContentDrip\Enums\ContentDripType;
use Masteriyo\Addons\Zoom\Enums\ZoomMeetingStatus;
use Masteriyo\Enums\CourseProgressStatus;
use Masteriyo\Enums\PostStatus;
use Masteriyo\Helper\Permission;
use Masteriyo\PostType\PostType;
use Masteriyo\Pro\Addons;
use Masteriyo\Query\CourseProgressQuery;
use Masteriyo\RestApi\Controllers\Version1\PostsController;

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

	/**
	 * Route base.
	 *
	 * @since 2.8.3
	 *
	 * @var string
	 */
	protected $rest_base = 'calendar';

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

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

	/**
	 * Registers the routes for handling calendar events.
	 *
	 * @since 2.8.3
	 *
	 * @return void
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/mine',
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_events' ),
					'permission_callback' => 'is_user_logged_in',
				),
			)
		);
	}

	/**
	 * Retrieves lesson events for given courses and dates.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Array of course IDs.
	 * @param string $month The month for the events.
	 * @param string $year The year for the events.
	 *
	 * @return array List of lesson events.
	 */
	private function get_lesson_info( $course_ids, $month, $year ) {
		$lesson_events = array();

		foreach ( $course_ids as $course_id ) {
			$lessons = masteriyo_get_lessons( array( 'course_id' => $course_id ) );

			if ( empty( $lessons ) ) {
				continue;
			}

			foreach ( $lessons as $lesson ) {

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

				$drip_date = $lesson->get_meta( '_content_drip_date' ) ? $lesson->get_meta( '_content_drip_date' ) : null;
				$drip_days = $lesson->get_meta( '_content_drip_days' ) ? $lesson->get_meta( '_content_drip_days' ) : null;
				$drip_type = get_post_meta( $course_id, '_flow', true );

				if ( ! $this->is_content_available( $course_id, $drip_date, $drip_days, $month, $year ) ) {
					continue;
				}

				$unlock_date = null;

				if ( ContentDripType::DATE === $drip_type && $drip_date ) {
					$unlock_date = masteriyo_rest_prepare_date_response( $drip_date );
				}

				if ( ContentDripType::DAYS === $drip_type && $drip_days ) {
					$drip_days = $this->calculate_availability_date( $course_id, $drip_days );

					if ( $drip_days ) {
						$unlock_date = masteriyo_rest_prepare_date_response( $drip_days );
					}
				}

				$lesson_events[] = array(
					'id'          => $lesson->get_id(),
					'title'       => $lesson->get_title(),
					'date'        => masteriyo_rest_prepare_date_response( $drip_date ? $drip_date : $drip_days ),
					'unlock_date' => $unlock_date,
					'url'         => masteriyo_get_course_item_learn_page_url( masteriyo_get_course( $course_id ), $lesson ),
				);
			}
		}

		return $lesson_events;
	}

	/**
	 * Retrieves assignment events for given courses and dates.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Array of course IDs.
	 * @param string $month The month for the events.
	 * @param string $year The year for the events.
	 * @return array List of assignment events.
	 */
	private function get_assignment_info( $course_ids, $month, $year ) {
		$assignment_events = array();

		foreach ( $course_ids as $course_id ) {
			$assignments = masteriyo_get_assignments( array( 'course_id' => $course_id ) );

			if ( empty( $assignments ) ) {
				continue;
			}

			foreach ( $assignments as $assignment ) {

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

				$due_date = $assignment->get_due_date() ? $assignment->get_due_date() : null;

				if ( $due_date && ! $this->is_date_in_month_year( $due_date, $month, $year ) ) {
					continue;
				}

				$event = array(
					'id'       => $assignment->get_id(),
					'title'    => $assignment->get_title(),
					'due_date' => masteriyo_rest_prepare_date_response( $due_date ),
					'date'     => masteriyo_rest_prepare_date_response( $due_date ),
					'url'      => masteriyo_get_course_item_learn_page_url( masteriyo_get_course( $course_id ), $assignment ),
				);

				if ( ( new Addons() )->is_active( MASTERIYO_CONTENT_DRIP_ADDON_SLUG ) ) {

					$unlock_date = null;
					$drip_type   = get_post_meta( $course_id, '_flow', true );

					if ( ContentDripType::DATE === $drip_type ) {
						$drip_date = $assignment->get_meta( '_content_drip_date' ) ? $assignment->get_meta( '_content_drip_date' ) : null;

						if ( $drip_date ) {
							$drip_date   = masteriyo_rest_prepare_date_response( $drip_date );
							$unlock_date = $drip_date;
						}
					}

					if ( ContentDripType::DAYS === $drip_type ) {
						$drip_days = $assignment->get_meta( '_content_drip_days' ) ? $assignment->get_meta( '_content_drip_days' ) : null;

						if ( $drip_days ) {
							$drip_days = $this->calculate_availability_date( $course_id, $drip_days );

							if ( $drip_days ) {
								$drip_days   = masteriyo_rest_prepare_date_response( $drip_days );
								$unlock_date = $drip_days;
							}
						}
					}

					if ( $unlock_date ) {
						$event['unlock_date'] = $unlock_date;
					}
				}

				$assignment_events [] = $event;
			}
		}

		return $assignment_events;
	}

	/**
	 * Retrieves quiz events for given courses and dates.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Array of course IDs.
	 * @param string $month The month for the events.
	 * @param string $year The year for the events.
	 * @return array List of quiz events.
	 */
	private function get_quiz_info( $course_ids, $month, $year ) {
		$quiz_events = array();

		foreach ( $course_ids as $course_id ) {
			$quizzes = masteriyo_get_quizes( array( 'course_id' => $course_id ) );

			if ( empty( $quizzes ) ) {
				continue;
			}

			foreach ( $quizzes as $quiz ) {

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

				$drip_date = $quiz->get_meta( '_content_drip_date' ) ? $quiz->get_meta( '_content_drip_date' ) : null;
				$drip_days = $quiz->get_meta( '_content_drip_days' ) ? $quiz->get_meta( '_content_drip_days' ) : null;
				$drip_type = get_post_meta( $course_id, '_flow', true );

				if ( ! $this->is_content_available( $course_id, $drip_date, $drip_days, $month, $year ) ) {
					continue;
				}

				$unlock_date = null;

				if ( ContentDripType::DATE === $drip_type && $drip_date ) {
					$unlock_date = masteriyo_rest_prepare_date_response( $drip_date );
				}

				if ( ContentDripType::DAYS === $drip_type && $drip_days ) {
					$drip_days = $this->calculate_availability_date( $course_id, $drip_days );

					if ( $drip_days ) {
						$unlock_date = masteriyo_rest_prepare_date_response( $drip_days );
					}
				}

				$quiz_events[] = array(
					'id'          => $quiz->get_id(),
					'title'       => $quiz->get_title(),
					'date'        => masteriyo_rest_prepare_date_response( $drip_date ? $drip_date : $drip_days ),
					'unlock_date' => $unlock_date,
					'url'         => masteriyo_get_course_item_learn_page_url( masteriyo_get_course( $course_id ), $quiz ),
				);
			}
		}

		return $quiz_events;
	}

	/**
	 * Retrieves Zoom meeting events for given courses and dates.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids Array of course IDs.
	 * @param string $month The month for the events.
	 * @param string $year The year for the events.
	 * @return array List of zoom meeting events.
	 */
	private function get_zoom_meeting_info( $course_ids, $month, $year ) {
		$zoom_meetings = array();

		foreach ( $course_ids as $course_id ) {

			$date_start           = "{$year}-{$month}-01";
			$date_start_timestamp = strtotime( $date_start );
			$date_end_timestamp   = strtotime( date_i18n( 'Y-m-t', $date_start_timestamp ) );

			$query    = new \WP_Query(
				array(
					'post_type'      => PostType::ZOOM,
					'post_status'    => ZoomMeetingStatus::UPCOMING,
					'posts_per_page' => -1,
					'fields'         => 'ids',
					'meta_query'     => array(
						array(
							'key'   => '_course_id',
							'value' => absint( $course_id ),
							'type'  => 'NUMERIC',
						),
						array(
							'key'     => '_starts_at',
							'value'   => array( $date_start_timestamp, $date_end_timestamp ),
							'compare' => 'BETWEEN',
							'type'    => 'NUMERIC',
						),
					),
				)
			);
			$zoom_ids = $query->posts;

			if ( empty( $zoom_ids ) ) {
				continue;
			}

			foreach ( $zoom_ids as $zoom_id ) {

				$zoom = masteriyo_get_zoom( $zoom_id );

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

				$zoom_meetings[] = array(
					'id'         => $zoom->get_id(),
					'title'      => $zoom->get_title(),
					'date'       => masteriyo_rest_prepare_date_response( $zoom->get_starts_at() ),
					'starts_at'  => masteriyo_rest_prepare_date_response( $zoom->get_starts_at() ),
					'expires_at' => masteriyo_rest_prepare_date_response( $zoom->get_expires_at() ),
					'url'        => masteriyo_get_course_item_learn_page_url( masteriyo_get_course( $course_id ), $zoom ),
					'status'     => $zoom->get_status(),
				);
			}
		}

		return $zoom_meetings;
	}

	/**
	 * Retrieves Google meeting events for given courses and dates.
	 *
	 * @since 2.21.0
	 *
	 * @param array $course_ids Array of course IDs.
	 * @param string $month The month for the events.
	 * @param string $year The year for the events.
	 * @return array List of google meeting events.
	 */
	private function get_google_meeting_info( $course_ids, $month, $year ) {
		$google_meetings = array();

		$date_start = "{$year}-{$month}-01 00:00:00";
		$date_end   = date( 'Y-m-t 23:59:59', strtotime( $date_start ) );
		$now        = current_time( 'timestamp' );

		foreach ( $course_ids as $course_id ) {
			$query = new \WP_Query(
				array(
					'post_type'      => PostType::GOOGLEMEET,
					'post_status'    => PostStatus::PUBLISH,
					'meta_query'     => array(
						'relation' => 'AND',
						array(
							'key'     => '_ends_at',
							'value'   => $date_start,
							'compare' => '>=',
							'type'    => 'DATETIME',
						),
						array(
							'key'     => '_starts_at',
							'value'   => $date_end,
							'compare' => '<=',
							'type'    => 'DATETIME',
						),
						array(
							'key'     => '_course_id',
							'value'   => $course_id,
							'compare' => '=',
							'type'    => 'NUMERIC',
						),
					),
					'posts_per_page' => -1,
				)
			);

			if ( empty( $query->posts ) ) {
				continue;
			}

			foreach ( $query->posts as $post ) {
				/** @var Masteriyo\Addons\GoogleMeet\Models\GoogleMeet $meet */
				$meet = masteriyo( 'google-meet' );
				$meet->set_id( $post->ID );

				try {
					masteriyo( 'mto-google-meet.store' )->read( $meet );
					if ( is_wp_error( $meet ) ) {
						continue;
					}

					$starts_at_raw = $meet->get_starts_at();
					$ends_at_raw   = $meet->get_ends_at();

					$start_ts = strtotime( $starts_at_raw );
					$end_ts   = strtotime( $ends_at_raw );

					if ( $now < $start_ts ) {
						$computed_status = 'upcoming';
					} elseif ( $now > $end_ts ) {
						$computed_status = 'expired';
					} else {
						$computed_status = 'active';
					}

					$google_meetings[] = array(
						'id'         => $meet->get_id(),
						'title'      => $meet->get_title(),
						'starts_at'  => masteriyo_rest_prepare_date_response( $starts_at_raw ),
						'expires_at' => masteriyo_rest_prepare_date_response( $ends_at_raw ),
						'url'        => masteriyo_get_course_item_learn_page_url(
							masteriyo_get_course( $meet->get_course_id() ),
							$meet
						),
						'status'     => $computed_status,
						'date'       => masteriyo_rest_prepare_date_response( $starts_at_raw ),
					);
				} catch ( Exception $e ) {
					masteriyo_get_logger()->info(
						'Error reading Google Meet: ' . print_r( $e->getMessage(), true ),
						array( 'source' => 'google-meet' )
					);
					continue;
				}
			}
		}

		return $google_meetings;
	}


	/**
	 * Retrieves calendar events for the user's enrolled courses for a given month and year.
	 *
	 * This function handles a REST request to fetch calendar events associated with courses
	 * the user is enrolled in. It supports filtering events by month and year. The function
	 * gathers different types of events (lessons, quizzes, assignments, zoom meetings)
	 * based on active addons. If no month and year are provided, it defaults to the current month and year.
	 *
	 * @since 2.8.3
	 *
	 * @param WP_REST_Request $request The REST request instance containing query parameters.
	 *
	 * @return WP_REST_Response A response object containing the aggregated events data.
	 */
	public function get_events( \WP_REST_Request $request ) {
		$month      = $request->get_param( 'month' ) ?? date_i18n( 'm' );
		$year       = $request->get_param( 'year' ) ?? date_i18n( 'Y' );
		$course_ids = $this->get_valid_user_course_ids();

		$events = array();

		if ( ! empty( $course_ids ) ) {
			$events = $this->aggregate_events( $course_ids, $month, $year );
		}

		return rest_ensure_response( array( 'data' => $events ) );
	}

	/**
	 * Filters and returns valid course IDs for the current user.
	 *
	 * @since 2.8.3
	 *
	 * @return array An array of valid course IDs.
	 */
	private function get_valid_user_course_ids() {
		$course_ids = masteriyo_get_all_user_course_ids( get_current_user_id() );

		return array_filter(
			$course_ids,
			function( $course_id ) {
				$course = masteriyo_get_course( $course_id );
				return $course && ! is_wp_error( $course );
			}
		);
	}

	/**
	 * Aggregates events from different addons for the given course IDs, month, and year.
	 *
	 * @since 2.8.3
	 *
	 * @param array $course_ids An array of course IDs.
	 * @param string $month The month for which to retrieve events.
	 * @param string $year The year for which to retrieve events.
	 *
	 * @return array An associative array of aggregated events.
	 */
	private function aggregate_events( $course_ids, $month, $year ) {
		$events = array();
		$addons = new Addons();

		if ( $addons->is_active( MASTERIYO_CONTENT_DRIP_ADDON_SLUG ) ) {
			$events['lesson'] = $this->get_lesson_info( $course_ids, $month, $year );
			$events['quiz']   = $this->get_quiz_info( $course_ids, $month, $year );
		}

		if ( $addons->is_active( MASTERIYO_ASSIGNMENT_ADDON_SLUG ) ) {
			$events['assignment'] = $this->get_assignment_info( $course_ids, $month, $year );
		}
		if ( $addons->is_active( MASTERIYO_ZOOM_ADDON_SLUG ) ) {
			$events['zoom_meeting'] = $this->get_zoom_meeting_info( $course_ids, $month, $year );
		}
		if ( $addons->is_active( MASTERIYO_GOOGLE_MEET_INTEGRATION_SLUG ) ) {
			$events['google_meet'] = $this->get_google_meeting_info( $course_ids, $month, $year );
		}

		return $events;
	}

	/**
	 * Retrieves the enrolled date for a given course.
	 *
	 * @since 2.8.3
	 *
	 * @param int $course_id The ID of the course.
	 *
	 * @return null|\DateTime Enrolled date or null if not enrolled.
	 */
	private function get_enrolled_date( $course_id ) {
		$course = masteriyo_get_course( $course_id );

		if ( ! $course ) {
			return null;
		}

		$query = new CourseProgressQuery(
			array(
				'user_id'   => get_current_user_id(),
				'course_id' => $course_id,
				'status'    => array( CourseProgressStatus::STARTED, CourseProgressStatus::PROGRESS ),
				'per_page'  => 1,
			)
		);

		$progress = $query->get_course_progress();

		return $progress ? $progress[0]->get_started_at() : null;
	}

	/**
	 * Determines if content is available in a specified month and year.
	 *
	 * @since 2.8.3
	 *
	 * @param int $course_id The course ID.
	 * @param string|null $drip_date The content drip date.
	 * @param string|null $drip_days The number of days after enrollment when the content becomes available.
	 * @param string $month The month for the events.
	 * @param string $year The year for the events.
	 *
	 * @return bool True if content is available in the specified month and year, false otherwise.
	 */
	public function is_content_available( $course_id, $drip_date, $drip_days, $month, $year ) {
		$drip_type = get_post_meta( $course_id, '_flow', true );

		if ( ContentDripType::DATE === $drip_type && $drip_date ) {
			$drip_date = new \DateTime( $drip_date );
			return $this->is_date_in_month_year( $drip_date, $month, $year );
		}

		if ( ContentDripType::DAYS === $drip_type && null !== $drip_days ) {
			$availability_date = $this->calculate_availability_date( $course_id, $drip_days );

			if ( ! $availability_date ) {
				return false;
			}

			return $this->is_date_in_month_year( $availability_date, $month, $year );
		}

		return false;
	}

	/**
	 * Checks if a given date falls in a specific month and year.
	 *
	 * @since 2.8.3
	 *
	 * @param \DateTime $date The date to check.
	 * @param string $month The month to compare against.
	 * @param string $year The year to compare against.
	 *
	 * @return bool True if the date is in the specified month and year, false otherwise.
	 */
	private function is_date_in_month_year( $date, $month, $year ) {
		$formatted_month = sprintf( '%02d', $month );

		return $date->format( 'Y-m' ) === "{$year}-{$formatted_month}";
	}

	/**
	 * Calculates the availability date based on enrollment date and drip days.
	 *
	 * @since 2.8.3
	 *
	 * @param int $course_id The course ID.
	 * @param int $drip_days The number of days after enrollment when the content becomes available.
	 *
	 * @return \DateTime|null The calculated availability date or null if not applicable.
	 */
	private function calculate_availability_date( $course_id, $drip_days ) {
		$enrollment_date = $this->get_enrolled_date( $course_id );

		if ( ! $enrollment_date ) {
			return null;
		}

		$drip_days         = absint( $drip_days );
		$availability_date = clone $enrollment_date;
		$availability_date->modify( "+{$drip_days} days" );

		return $availability_date;
	}
}
