<?php
/**
 * Quiz Builder
 *
 * @package Tutor\Classes
 * @author Themeum <support@themeum.com>
 * @link https://themeum.com
 * @since 3.0.0
 */

namespace TUTOR;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Tutor\Helpers\HttpHelper;
use Tutor\Helpers\QueryHelper;
use Tutor\Helpers\ValidationHelper;
use Tutor\Models\QuizModel;
use Tutor\Traits\JsonResponse;

/**
 * Class QuizBuilder
 *
 * @since 1.0.0
 */
class QuizBuilder {
	use JsonResponse;

	const TRACKING_KEY   = '_data_status';
	const FLAG_NEW       = 'new';
	const FLAG_UPDATE    = 'update';
	const FLAG_NO_CHANGE = 'no_change';

	/**
	 * Register hooks and dependencies.
	 *
	 * @param boolean $register_hooks register hooks or not.
	 */
	public function __construct( $register_hooks = true ) {
		if ( ! $register_hooks ) {
			return;
		}

		add_action( 'wp_ajax_tutor_quiz_builder_save', array( $this, 'ajax_quiz_builder_save' ) );
	}


	/**
	 * Prepare question data.
	 *
	 * @since 3.0.0
	 *
	 * @param int   $quiz_id quiz id.
	 * @param array $input question data.
	 *
	 * @return array
	 */
	public function prepare_question_data( $quiz_id, $input ) {
		$question_title       = Input::sanitize( wp_slash( $input['question_title'] ), '' );
		$question_description = Input::sanitize( wp_slash( $input['question_description'] ) ?? '', '', Input::TYPE_KSES_POST );
		$question_type        = Input::sanitize( $input['question_type'], '' );
		$question_mark        = Input::sanitize( $input['question_mark'], 1, Input::TYPE_INT );
		$question_settings    = Input::sanitize_array( $input['question_settings'] );

		$data = array(
			'quiz_id'              => $quiz_id,
			'question_title'       => $question_title,
			'question_description' => $question_description,
			'question_type'        => $question_type,
			'question_mark'        => $question_mark,
			'question_settings'    => maybe_serialize( $question_settings ),
		);

		return apply_filters( 'tutor_quiz_question_data', $data, $input );
	}

	/**
	 * Prepare answer data.
	 *
	 * @param int    $question_id question id.
	 * @param string $question_type question type.
	 * @param array  $input answer data.
	 *
	 * @return array
	 */
	public function prepare_answer_data( $question_id, $question_type, $input ) {
		$answer_title         = Input::sanitize( wp_slash( $input['answer_title'] ) ?? '', '' );
		$is_correct           = Input::sanitize( $input['is_correct'] ?? 0, 0, Input::TYPE_INT );
		$image_id             = Input::sanitize( $input['image_id'] ?? null );
		$answer_two_gap_match = Input::sanitize( $input['answer_two_gap_match'] ?? '' );
		$answer_view_format   = Input::sanitize( $input['answer_view_format'] ?? '' );
		$answer_settings      = null;

		$answer_data = array(
			'belongs_question_id'   => $question_id,
			'belongs_question_type' => $question_type,
			'answer_title'          => $answer_title,
			'is_correct'            => $is_correct,
			'image_id'              => $image_id,
			'answer_two_gap_match'  => $answer_two_gap_match,
			'answer_view_format'    => $answer_view_format,
			'answer_settings'       => $answer_settings,
		);

		return $answer_data;
	}

	/**
	 * Save question answers.
	 *
	 * @since 3.7.0
	 *
	 * @param int    $question_id question id.
	 * @param string $question_type question type.
	 * @param array  $question_answers question answers.
	 *
	 * @return void
	 */
	public function save_question_answers( $question_id, $question_type, $question_answers ) {
		global $wpdb;
		$answers_table = $wpdb->prefix . 'tutor_quiz_question_answers';

		$answer_order = 0;
		foreach ( $question_answers as $answer ) {
			$data_status = isset( $answer[ self::TRACKING_KEY ] ) ? $answer[ self::TRACKING_KEY ] : self::FLAG_NO_CHANGE;
			$answer_data = $this->prepare_answer_data( $question_id, $question_type, $answer );

			// New answer.
			if ( self::FLAG_NEW === $data_status ) {
				$wpdb->insert( $answers_table, $answer_data );
				$answer_id = $wpdb->insert_id;
			}

			// Update answer.
			if ( self::FLAG_UPDATE === $data_status ) {
				$answer_id = $answer['answer_id'];
				$wpdb->update(
					$answers_table,
					$answer_data,
					array( 'answer_id' => $answer_id )
				);
			}

			if ( self::FLAG_NO_CHANGE === $data_status ) {
				$answer_id = $answer['answer_id'];
			}

			// Save sort order.
			$answer_order++;
			$wpdb->update(
				$answers_table,
				array( 'answer_order' => $answer_order ),
				array( 'answer_id' => $answer_id )
			);
		}
	}

	/**
	 * Save quiz questions.
	 *
	 * @since 3.0.0
	 *
	 * @param int   $quiz_id quiz id.
	 * @param array $questions questions data.
	 *
	 * @return void
	 */
	public function save_questions( $quiz_id, $questions ) {
		global $wpdb;
		$questions_table = $wpdb->prefix . 'tutor_quiz_questions';

		$question_order = 0;
		foreach ( $questions as $question ) {
			$data_status = isset( $question[ self::TRACKING_KEY ] ) ? $question[ self::TRACKING_KEY ] : self::FLAG_NO_CHANGE;
			$question_order++;
			if ( isset( $question['is_cb_question'], $question['cb_action'] ) && 'link' === $question['cb_action'] ) {
				$question['question_order'] = $question_order;
				do_action( 'tutor_content_bank_question_linked_to_quiz', $quiz_id, (object) $question );
				continue;
			}

			$question_type    = Input::sanitize( $question['question_type'] );
			$question_data    = $this->prepare_question_data( $quiz_id, $question );
			$question_answers = isset( $question['question_answers'] ) ? $question['question_answers'] : array();

			// New question.
			if ( self::FLAG_NEW === $data_status ) {
				$wpdb->insert( $questions_table, $question_data );
				$question_id = $wpdb->insert_id;

				if ( isset( $question['is_cb_question'] ) ) {
					$question['question_order']  = $question_order;
					$question['new_question_id'] = $question_id;
					do_action( 'tutor_content_bank_question_added_to_quiz', $quiz_id, (object) $question );
				}
			}

			// Update question.
			if ( self::FLAG_UPDATE === $data_status ) {
				$question_id = (int) $question['question_id'];
				$wpdb->update(
					$questions_table,
					$question_data,
					array( 'question_id' => $question_id )
				);
			}

			if ( self::FLAG_NO_CHANGE === $data_status ) {
				$question_id = $question['question_id'];
			}

			// Save sort order.
			$wpdb->update(
				$questions_table,
				array( 'question_order' => $question_order ),
				array( 'question_id' => $question_id )
			);

			// Save question's answers.
			$this->save_question_answers( $question_id, $question_type, $question_answers );
		}
	}

	/**
	 * Validate payload.
	 *
	 * @since 3.0.0
	 *
	 * @param array $payload payload.
	 *
	 * @return object consist success, errors.
	 */
	public function validate_payload( $payload ) {
		$errors  = array();
		$success = true;

		if ( ! is_array( $payload ) ) {
			$success           = false;
			$errors['payload'] = __( 'Invalid payload', 'tutor' );
		}

		$rules = array(
			'post_title'  => 'required',
			'quiz_option' => 'required|is_array',
			'questions'   => 'required|is_array',
		);

		$validation = ValidationHelper::validate(
			$rules,
			$payload
		);

		if ( ! $validation->success ) {
			$success = false;
			$errors  = array_merge( $errors, $validation->errors );
		}

		foreach ( $payload['questions'] as $question ) {
			if ( ! isset( $question[ self::TRACKING_KEY ] ) ) {
				$success = false;
				// translators: %s is the tracking key required for each question.
				$errors[ self::TRACKING_KEY ][] = sprintf( __( '%s is required for each question', 'tutor' ), self::TRACKING_KEY );
				break;
			}

			if ( ! in_array( $question[ self::TRACKING_KEY ], array( self::FLAG_NEW, self::FLAG_UPDATE, self::FLAG_NO_CHANGE ), true ) ) {
				$success = false;
				// translators: %s is the tracking key for which the value is invalid.
				$errors[ self::TRACKING_KEY ][] = sprintf( __( 'Invalid value for %s', 'tutor' ), self::TRACKING_KEY );
				break;
			}

			if ( ! isset( $question['question_settings'] ) || ! is_array( $question['question_settings'] ) ) {
				$success                       = false;
				$errors['question_settings'][] = __( 'Question settings is required with array data', 'tutor' );
				break;
			}
		}

		return (object) array(
			'success' => $success,
			'errors'  => $errors,
		);
	}

	/**
	 * Handle delete questions and answers.
	 *
	 * @since 3.0.0
	 *
	 * @param array $deleted_question_ids question ids.
	 * @param array $deleted_answer_ids answer ids.
	 *
	 * @return void
	 */
	public function handle_delete( $deleted_question_ids = array(), $deleted_answer_ids = array() ) {
		global $wpdb;
		$deleted_question_ids = array_filter( $deleted_question_ids, 'is_numeric' );
		$deleted_answer_ids   = array_filter( $deleted_answer_ids, 'is_numeric' );

		if ( count( $deleted_question_ids ) ) {
			$id_str = QueryHelper::prepare_in_clause( $deleted_question_ids );
            //phpcs:ignore -- sanitized $id_str.
            $wpdb->query( "DELETE FROM {$wpdb->prefix}tutor_quiz_questions WHERE content_id IS NULL AND question_id IN (" . $id_str . ')' );
			do_action( 'tutor_deleted_quiz_question_ids', $deleted_question_ids );
		}

		if ( count( $deleted_answer_ids ) ) {
			$id_str = QueryHelper::prepare_in_clause( $deleted_answer_ids );
            //phpcs:ignore -- sanitized $id_str.
            $wpdb->query( "DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE answer_id IN (" . $id_str . ')' );
		}
	}

	/**
	 * Create or update quiz.
	 *
	 * @since 3.0.0
	 *
	 * @param int   $topic_id topic id.
	 * @param array $payload payload.
	 *
	 * @return object consist success, errors.
	 */
	public function save_quiz( $topic_id, $payload ) {
		$success = true;
		$data    = null;
		$errors  = array();

		$validation = $this->validate_payload( $payload );

		if ( ! $validation->success ) {
			return (object) array(
				'success' => false,
				'errors'  => $validation->errors,
			);
		}

		$is_update = isset( $payload['ID'] );
		$quiz_id   = $is_update ? $payload['ID'] : null;
		$questions = isset( $payload['questions'] ) ? $payload['questions'] : array();

		$menu_order = (int) ( isset( $payload['menu_order'] )
						? $payload['menu_order']
						: tutor_utils()->get_next_course_content_order_id( $topic_id, $quiz_id ) );

		$quiz_data = array(
			'post_type'    => tutor()->quiz_post_type,
			'post_title'   => Input::sanitize( wp_slash( $payload['post_title'] ?? '' ) ),
			'post_content' => Input::sanitize( wp_slash( $payload['post_content'] ?? '' ) ),
			'post_status'  => 'publish',
			'post_author'  => get_current_user_id(),
			'post_parent'  => $topic_id,
			'menu_order'   => $menu_order,
		);

		global $wpdb;
		$wpdb->query( 'START TRANSACTION' );

		try {
			// Add or update the quiz.
			if ( $is_update ) {
				$quiz_data['ID'] = $quiz_id;
			}

			$quiz_id = wp_insert_post( $quiz_data );
			do_action( ( $is_update ? 'tutor_quiz_updated' : 'tutor_initial_quiz_created' ), $quiz_id );

			// Save quiz settings.
			$quiz_option = Input::sanitize_array( $payload['quiz_option'] ?? array() ); //phpcs:ignore
			update_post_meta( $quiz_id, Quiz::META_QUIZ_OPTION, $quiz_option );
			do_action( 'tutor_quiz_settings_updated', $quiz_id );

			// Save quiz questions.
			if ( count( $questions ) ) {
				$this->save_questions( $quiz_id, $questions );
			}

			// Delete questions and answers.
			$deleted_question_ids = Input::post( 'deleted_question_ids', array(), Input::TYPE_ARRAY );
			$deleted_answer_ids   = Input::post( 'deleted_answer_ids', array(), Input::TYPE_ARRAY );
			$this->handle_delete( $deleted_question_ids, $deleted_answer_ids );

			$wpdb->query( 'COMMIT' );

			$data = $quiz_id;

		} catch ( \Throwable $th ) {
			$wpdb->query( 'ROLLBACK' );

			$success         = false;
			$errors['500'][] = $th->getMessage();
		}

		return (object) array(
			'success' => $success,
			'data'    => $data,
			'errors'  => $errors,
		);
	}

	/**
	 * Create or update quiz from new course builder.
	 *
	 * @since 3.0.0
	 *
	 * @return void json response.
	 */
	public function ajax_quiz_builder_save() {
		tutor_utils()->check_nonce();

		$payload    = $_POST['payload'] ?? array(); //phpcs:ignore
		if ( is_string( $payload ) ) {
			$payload = json_decode( wp_unslash( $payload ), true );
		}

		$course_id  = Input::post( 'course_id', 0, Input::TYPE_INT );
		$topic_id   = Input::post( 'topic_id', 0, Input::TYPE_INT );
		$course_cls = new Course( false );

		$course_cls->check_access( $course_id );

		$result = $this->save_quiz( $topic_id, wp_slash( $payload ) );
		if ( $result->success ) {
			$quiz_id      = $result->data;
			$quiz_details = QuizModel::get_quiz_details( $quiz_id );
			$this->json_response( __( 'Quiz saved successfully', 'tutor' ), $quiz_details );
		} else {
			$this->json_response( __( 'Error', 'tutor' ), $result->errors, HttpHelper::STATUS_BAD_REQUEST );
		}

	}
}
