<?php
/**
 * Course Faq Repository class.
 *
 * @since 2.2.7
 *
 * @package Masteriyo\Repository;
 */

namespace Masteriyo\Addons\CourseFaq\Repository;

defined( 'ABSPATH' ) || exit;


use Masteriyo\Models\Faq;
use Masteriyo\Database\Model;
use Masteriyo\Enums\CommentStatus;
use Masteriyo\Repository\AbstractRepository;
use Masteriyo\Repository\RepositoryInterface;

/**
 * Course Faq Repository class.
 */
class CourseFaqRepository extends AbstractRepository implements RepositoryInterface {
	/**
	 * Meta type.
	 *
	 * @since 2.2.7
	 *
	 * @var string
	 */
	protected $meta_type = 'comment';

	/**
	 * Data stored in meta keys, but not considered "meta".
	 *
	 * @since 2.2.7
	 * @var array
	 */
	protected $internal_meta_keys = array(
		'title' => '_title',
	);

	/**
	 * Create a faq in the database.
	 *
	 * @since 2.2.7
	 *
	 * @param \Masteriyo\Models\Faq $faq Faq object.
	 */
	public function create( Model &$faq ) {
		if ( ! $faq->get_created_at( 'edit' ) ) {
			$faq->set_created_at( time() );
		}

		if ( ! $faq->get_ip_address( 'edit' ) ) {
			$faq->set_ip_address( masteriyo_get_current_ip_address() );
		}

		if ( ! $faq->get_agent( 'edit' ) ) {
			$faq->set_agent( masteriyo_get_user_agent() );
		}

		$current_user = wp_get_current_user();

		if ( ! empty( $current_user ) ) {
			if ( ! $faq->get_author_email( 'edit' ) ) {
				$faq->set_author_email( $current_user->user_email );
			}

			if ( ! $faq->get_author_id( 'edit' ) ) {
				$faq->set_author_id( $current_user->ID );
			}

			if ( ! $faq->get_author_name( 'edit' ) ) {
				$faq->set_author_name( $current_user->user_nicename );
			}

			if ( ! $faq->get_author_url( 'edit' ) ) {
				$faq->set_author_url( $current_user->user_url );
			}
		}

		$id = wp_insert_comment(
			/**
			 * Filters new FAQ data before creating.
			 *
			 * @since 2.2.7
			 *
			 * @param array $data New FAQ data.
			 * @param \Masteriyo\Addons\CourseFaq\Models\CourseFaq $faq FAQ object.
			 */
			apply_filters(
				'masteriyo_new_course_faq_data',
				array(
					'comment_post_ID'      => $faq->get_course_id( 'edit' ),
					'comment_author'       => $faq->get_author_name( 'edit' ),
					'comment_author_email' => $faq->get_author_email( 'edit' ),
					'comment_author_url'   => $faq->get_author_url( 'edit' ),
					'comment_author_IP'    => $faq->get_ip_address( 'edit' ),
					'comment_date'         => gmdate( 'Y-m-d H:i:s', $faq->get_created_at( 'edit' )->getOffsetTimestamp() ),
					'comment_date_gmt'     => gmdate( 'Y-m-d H:i:s', $faq->get_created_at( 'edit' )->getOffsetTimestamp() ),
					'comment_content'      => $faq->get_content( 'edit' ),
					'comment_karma'        => $faq->get_menu_order( 'edit' ),
					'comment_approved'     => $faq->get_status( 'edit' ),
					'comment_agent'        => $faq->get_agent( 'edit' ),
					'comment_type'         => $faq->get_type(),
					'comment_parent'       => $faq->get_parent_id( 'edit' ),
					'user_id'              => $faq->get_author_id( 'edit' ),
				),
				$faq
			),
			true
		);

		// Set comment status.
		wp_set_comment_status( $id, $faq->get_status() );

		if ( $id && ! is_wp_error( $id ) ) {
			$faq->set_id( $id );
			$this->update_comment_meta( $faq, true );

			$faq->save_meta_data();
			$faq->apply_changes();

			/**
			 * Fires after creating a FAQ.
			 *
			 * @since 2.2.7
			 *
			 * @param integer $id The FAQ ID.
			 * @param \Masteriyo\Addons\CourseFaq\Models\CourseFaqFaq $object The FAQ object.
			 */
			do_action( 'masteriyo_new_course_faq', $id, $faq );
		}
	}

	/**
	 * Read a Faq.
	 *
	 * @since 2.2.7
	 *
	 * @param \Masteriyo\Models\Faq $faq Faq object.
	 * @throws \Exception If invalid FAQ.
	 */
	public function read( Model &$faq ) {
		$course_faq_obj = get_comment( $faq->get_id() );

		if ( ! $faq->get_id() || ! $course_faq_obj || $faq->get_type() !== $course_faq_obj->comment_type ) {
			throw new \Exception( __( 'Invalid FAQ.', 'learning-management-system' ) );
		}

		// Map the comment status from numerical to word.
		$status = $course_faq_obj->comment_approved;
		if ( CommentStatus::APPROVE === $status ) {
			$status = CommentStatus::APPROVE_STR;
		} elseif ( CommentStatus::HOLD === $status ) {
			$status = CommentStatus::HOLD_STR;
		}

		$faq->set_props(
			array(
				'course_id'    => $course_faq_obj->comment_post_ID,
				'author_name'  => $course_faq_obj->comment_author,
				'author_email' => $course_faq_obj->comment_author_email,
				'author_url'   => $course_faq_obj->comment_author_url,
				'ip_address'   => $course_faq_obj->comment_author_IP,
				'created_at'   => $course_faq_obj->comment_date_gmt,
				'content'      => $course_faq_obj->comment_content,
				'menu_order'   => $course_faq_obj->comment_karma,
				'status'       => $status,
				'agent'        => $course_faq_obj->comment_agent,
				'type'         => $course_faq_obj->comment_type,
				'parent_id'    => $course_faq_obj->comment_parent,
				'author_id'    => $course_faq_obj->user_id,
			)
		);
		$this->read_comment_meta_data( $faq );
		$faq->set_object_read( true );

		/**
		 * Fires after reading a FAQ from database.
		 *
		 * @since 2.2.7
		 *
		 * @param integer $id The FAQ ID.
		 * @param \Masteriyo\Addons\CourseFaq\Models\CourseFaq $object The FAQ object.
		 */
		do_action( 'masteriyo_course_faq_read', $faq->get_id(), $faq );
	}

	/**
	 * Update a faq in the database.
	 *
	 * @since 2.2.7
	 *
	 * @param \Masteriyo\Addons\CourseFaq\Models\CourseFaq $faq Faq object.
	 */
	public function update( Model &$faq ) {
		$changes = $faq->get_changes();

		$faq_data_keys = array(
			'author_name',
			'author_email',
			'author_url',
			'ip_address',
			'created_at',
			'content',
			'course_id',
			'author_id',
			'agent',
			'menu_order',
			'parent_id',
			'status',
		);

		// Only update the course review when the course review data changes.
		if ( array_intersect( $faq_data_keys, array_keys( $changes ) ) ) {
			$faq_data = array(
				'comment_author'       => $faq->get_author_name( 'edit' ),
				'comment_author_email' => $faq->get_author_email( 'edit' ),
				'comment_author_url'   => $faq->get_author_url( 'edit' ),
				'comment_content'      => $faq->get_content( 'edit' ),
				'comment_date'         => gmdate( 'Y-m-d H:i:s', $faq->get_created_at( 'edit' )->getOffsetTimestamp() ),
				'comment_date_gmt'     => gmdate( 'Y-m-d H:i:s', $faq->get_created_at( 'edit' )->getOffsetTimestamp() ),
				'comment_post_ID'      => $faq->get_course_id( 'edit' ),
				'comment_type'         => $faq->get_type(),
				'user_id'              => $faq->get_author_id( 'edit' ),
				'comment_karma'        => $faq->get_menu_order( 'edit' ),
				'comment_parent'       => $faq->get_parent_id( 'edit' ),
				'comment_agent'        => $faq->get_agent( 'edit' ),
				'comment_author_IP'    => $faq->get_ip_address( 'edit' ),
			);

			wp_update_comment( array_merge( array( 'comment_ID' => $faq->get_id() ), $faq_data ) );
		}

		$this->update_comment_meta( $faq );
		$faq->apply_changes();

		/**
		 * Fires after updating a FAQ in database.
		 *
		 * @since 2.2.7
		 *
		 * @param integer $id The FAQ ID.
		 * @param \Masteriyo\Addons\CourseFaq\Models\CourseFaq $object The FAQ object.
		 */
		do_action( 'masteriyo_update_course_faq', $faq->get_id(), $faq );
	}

	/**
	 * Delete a Faq from the database.
	 *
	 * @since 2.2.7
	 *
	 * @param Model $faq Faq object.
	 * @param array $args   Array of args to pass.alert-danger.
	 */
	public function delete( Model &$faq, $args = array() ) {
		$id          = $faq->get_id();
		$object_type = $faq->get_object_type();
		$args        = array_merge(
			array(
				'force_delete' => false,
			),
			$args
		);

		if ( ! $id ) {
			return;
		}

		if ( $args['force_delete'] ) {
			/**
			 * Fires before course review is permanently deleted.
			 *
			 * @since 2.2.7
			 *
			 * @param int $id Course faq ID.
			 * @param \Masteriyo\Addons\CourseFaq\Models\CourseFaq  $faq Course faq object.
			 */
			do_action( 'masteriyo_before_delete_' . $object_type, $id, $faq );

			wp_delete_comment( $id, true );
			$faq->set_id( 0 );

			/**
			 * Fires after course faq is permanently deleted.
			 *
			 * @since 2.2.7
			 *
			 * @param int $id Course faq ID.
			 * @param \Masteriyo\Addons\CourseFaq\Models\CourseFaq  $faq Course faq object.
			 */
			do_action( 'masteriyo_after_delete_' . $object_type, $id, $faq );
		} else {
			/**
			 * Fires before course faq is trashed.
			 *
			 * @since 2.2.7
			 *
			 * @param int $id Course faq ID.
			 * @param \Masteriyo\Addons\CourseFaq\Models\CourseFaq  $faq Course faq object.
			 */
			do_action( 'masteriyo_before_trash_' . $object_type, $id, $faq );

			wp_trash_comment( $id );
			$faq->set_status( CommentStatus::TRASH );

			/**
			 * Fires after course faq is trashed.
			 *
			 * @since 2.2.7
			 *
			 * @param int $id Course faq ID.
			 * @param \Masteriyo\Addons\CourseFaq\Models\CourseFaq  $faq Course faq object.
			 */
			do_action( 'masteriyo_after_trash_' . $object_type, $id, $faq );
		}
	}

	/**
	 * Fetch faqs.
	 *
	 * @since 2.2.7
	 *
	 * @param array $query_vars Query vars.
	 * @return Faq[]
	 */
	public function query( $query_vars ) {
		$args = $this->get_wp_query_args( $query_vars );

		if ( isset( $query_vars['course_id'] ) ) {
			$args['post_id'] = $query_vars['course_id'];
		}
		if ( ! empty( $args['errors'] ) ) {
			$query = (object) array(
				'posts'         => array(),
				'found_posts'   => 0,
				'max_num_pages' => 0,
			);
		} else {
			$query = new \WP_Comment_Query( $args );
		}

		if ( isset( $query_vars['return'] ) && 'objects' === $query_vars['return'] && ! empty( $query->comments ) ) {
			// Prime caches before grabbing objects.
			update_comment_cache( $query->comments );
		}

		if ( isset( $query_vars['return'] ) && 'ids' === $query_vars['return'] ) {
			$faq = $query->comments;
		} else {
			$faq = array_filter( array_map( 'masteriyo_get_faq', $query->comments ) );
		}

		if ( isset( $query_vars['paginate'] ) && $query_vars['paginate'] ) {
			return (object) array(
				'faq'           => $faq,
				'total'         => $query->found_posts,
				'max_num_pages' => $query->max_num_pages,
			);
		}

		return $faq;
	}

	/**
	 * Get valid WP_Query args from a FaqQuery's query variables.
	 *
	 * @since 2.2.7
	 * @param array $query_vars Query vars from a FaqQuery.
	 * @return array
	 */
	protected function get_wp_query_args( $query_vars ) {
		// Map query vars to ones that get_wp_query_args or WP_Query recognize.
		$key_mapping = array(
			'page'      => 'paged',
			'parent_id' => 'comment_parent',
		);

		foreach ( $key_mapping as $query_key => $db_key ) {
			if ( isset( $query_vars[ $query_key ] ) ) {
				$query_vars[ $db_key ] = $query_vars[ $query_key ];
				unset( $query_vars[ $query_key ] );
			}
		}

		$wp_query_args = $this->get_wp_comment_query_args( $query_vars );

		$wp_query_args['type'] = 'mto_course_faq';

		if ( ! isset( $wp_query_args['date_query'] ) ) {
			$wp_query_args['date_query'] = array();
		}
		if ( ! isset( $wp_query_args['meta_query'] ) ) {
			$wp_query_args['meta_query'] = array(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
		}

		// Handle date queries.
		$date_queries = array(
			'created_at' => 'comment_date',
		);
		foreach ( $date_queries as $query_var_key => $db_key ) {
			if ( isset( $query_vars[ $query_var_key ] ) && '' !== $query_vars[ $query_var_key ] ) {

				// Remove any existing meta queries for the same keys to prevent conflicts.
				$existing_queries = wp_list_pluck( $wp_query_args['meta_query'], 'key', true );
				foreach ( $existing_queries as $query_index => $query_contents ) {
					unset( $wp_query_args['meta_query'][ $query_index ] );
				}

				$wp_query_args = $this->parse_date_for_wp_query( $query_vars[ $query_var_key ], $db_key, $wp_query_args );
			}
		}

		// Handle paginate.
		if ( ! isset( $query_vars['paginate'] ) || ! $query_vars['paginate'] ) {
			$wp_query_args['no_found_rows'] = true;
		}

		// Handle orderby.
		if ( isset( $query_vars['orderby'] ) && 'include' === $query_vars['orderby'] ) {
			$wp_query_args['orderby'] = 'comment__in';
		}

		/**
		 * Filters WP Query args for FAQ post type query.
		 *
		 * @since 2.2.7
		 *
		 * @param array $wp_query_args WP Query args.
		 * @param array $query_vars Query vars.
		 * @param \Masteriyo\Repository\FaqRepository $repository FAQ repository object.
		 */
		return apply_filters( 'masteriyo_course_faq_wp_query_args', $wp_query_args, $query_vars, $this );
	}

	/**
	 * Get valid WP_Query args from a ObjectQuery's query variables.
	 *
	 * @since 1.0.0
	 * @param array $query_vars query vars from a ObjectQuery.
	 * @return array
	 */
	protected function get_wp_comment_query_args( $query_vars ) {
		$skipped_values = array( '', array(), null );
		$wp_query_args  = array(
			'errors'     => array(),
			'meta_query' => array(), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
		);

		foreach ( $query_vars as $key => $value ) {
			if ( in_array( $value, $skipped_values, true ) || 'meta_query' === $key ) {
				continue;
			}

			// Build meta queries out of vars that are stored in internal meta keys.
			if ( in_array( '_' . $key, $this->internal_meta_keys, true ) ) {
				// Check for existing values if wildcard is used.
				if ( '*' === $value ) {
					$wp_query_args['meta_query'][] = array(
						array(
							'key'     => '_' . $key,
							'compare' => 'EXISTS',
						),
						array(
							'key'     => '_' . $key,
							'value'   => '',
							'compare' => '!=',
						),
					);
				} else {
					$wp_query_args['meta_query'][] = array(
						'key'     => '_' . $key,
						'value'   => $value,
						'compare' => is_array( $value ) ? 'IN' : '=',
					);
				}
			} else { // Other vars get mapped to wp_query args or just left alone.
				$key_mapping = array(
					'page'           => 'paged',
					'include'        => 'comment__in',
					'exclude'        => 'comment__not_in',
					'parent'         => 'parent__in',
					'parent_exclude' => 'parent__not_in',
					'limit'          => 'number',
					'return'         => 'fields',
				);

				if ( isset( $key_mapping[ $key ] ) ) {
					$wp_query_args[ $key_mapping[ $key ] ] = $value;
				} else {
					$wp_query_args[ $key ] = $value;
				}
			}
		}

		/**
		 * Filter WP query vars.
		 *
		 * @since 1.0.0
		 * @since 1.4.9 Added third parameter $repository.
		 *
		 * @param array $wp_query_args WP Query args.
		 * @param array $query_vars query vars from a ObjectQuery.
		 * @param Masteriyo\Repository\AbstractRepository $repository AbstractRepository object.
		 *
		 * @return array WP Query args.
		 */
		return apply_filters( 'masteriyo_get_wp_comment_query_args', $wp_query_args, $query_vars, $this );
	}

	/**
	 * Read comment meta data. Can be overridden by child classes to load other props.
	 *
	 * @since 2.2.7
	 *
	 * @param Faq $faq Course review object.
	 */
	protected function read_comment_meta_data( &$faq ) {
		$meta_values = $this->read_meta( $faq );
		$set_props   = array();
		$meta_values = array_reduce(
			$meta_values,
			function( $result, $meta_value ) {
				$result[ $meta_value->key ][] = $meta_value->value;
				return $result;
			},
			array()
		);

		foreach ( $this->internal_meta_keys as $prop => $meta_key ) {
			$meta_value         = isset( $meta_values[ $meta_key ][0] ) ? $meta_values[ $meta_key ][0] : null;
			$set_props[ $prop ] = maybe_unserialize( $meta_value ); // get_post_meta only unserializes single values.
		}

		$faq->set_props( $set_props );
	}
}
