<?php
/**
 * GradeResultRepository class.
 *
 * @since 2.5.20
 *
 * @package Masteriyo\Addons\Gradebook\Repository
 */

namespace Masteriyo\Addons\Gradebook\Repository;

defined( 'ABSPATH' ) || exit;


use Colors\RandomColor;
use Masteriyo\Database\Model;
use Masteriyo\Enums\PostStatus;
use Masteriyo\PostType\PostType;
use Masteriyo\Repository\AbstractRepository;
use Masteriyo\Repository\RepositoryInterface;
use Masteriyo\Addons\Gradebook\Enums\GradeResultType;
use Masteriyo\Addons\Gradebook\Enums\GradeResultStatus;

/**
 * GradeResultRepository class.
 */
class GradeResultRepository extends AbstractRepository implements RepositoryInterface {
	/**
	 * Create a grade result in the database.
	 *
	 * @since 2.5.20
	 *
	 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $grade_result Grade object.
	 */
	public function create( Model &$grade_result ) {
		global $wpdb;

		if ( ! $grade_result->get_created_at( 'edit' ) ) {
			$grade_result->set_created_at( time() );
		}

		// Set the author of the grade to the current user id, if the grade doesn't have a author.
		if ( empty( $grade_result->get_user_id() ) ) {
			$grade_result->set_user_id( get_current_user_id() );
		}

		$wpdb->insert(
			$grade_result->get_table_name(),
			/**
			 * Filters new grade data before creating.
			 *
			 * @since 2.5.20
			 *
			 * @param array $data New grade data.
			 * @param Masteriyo\Models\Grade $grade_result Grade object.
			 */
			apply_filters(
				'masteriyo_new_grade_result_data',
				array(
					'user_id'            => $grade_result->get_user_id( 'edit' ),
					'parent_id'          => $grade_result->get_parent_id( 'edit' ),
					'item_id'            => $grade_result->get_item_id( 'edit' ),
					'grade_id'           => $grade_result->get_grade_id( 'edit' ),
					'item_type'          => $grade_result->get_item_type( 'edit' ),
					'item_name'          => $grade_result->get_item_name( 'edit' ),
					'status'             => $grade_result->get_status( 'edit' ) ? $grade_result->get_status( 'edit' ) : GradeResultStatus::COMPLETED,
					'grade_name'         => $grade_result->get_grade_name( 'edit' ),
					'grade_point'        => $grade_result->get_grade_point( 'edit' ),
					'earned_grade_point' => $grade_result->get_earned_grade_point( 'edit' ),
					'earned_percent'     => $grade_result->get_earned_percent( 'edit' ),
					'weight'             => $grade_result->get_weight( 'edit' ),
					'created_at'         => gmdate( 'Y-m-d H:i:s', $grade_result->get_created_at( 'edit' )->getTimestamp() ),
				),
				$grade_result
			),
			array(
				'%d',
				'%d',
				'%d',
				'%d',
				'%s',
				'%s',
				'%s',
				'%s',
				'%f',
				'%f',
				'%f',
				'%d',
				'%s',
			)
		);

		if ( $wpdb->insert_id ) {
			$grade_result->set_id( $wpdb->insert_id );
			// TODO Invalidate caches.

			$grade_result->apply_changes();

			/**
			 * Fires after creating a grade.
			 *
			 * @since 2.5.20
			 *
			 * @param integer $id The grade result ID.
			 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $object The grade result object.
			 */
			do_action( 'masteriyo_new_grade_result', $wpdb->insert_id, $grade_result );
		}
	}

	/**
	 * Read a grade result.
	 *
	 * @since 2.5.20
	 *
	 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $grade_result Grade object.
	 * @throws \Exception If invalid grade.
	 */
	public function read( Model &$grade_result ) {
		global $wpdb;

		$table_name = $grade_result->get_table_name();

		$grade_result_obj = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$table_name} LEFT JOIN {$wpdb->posts} ON {$table_name}.grade_id = {$wpdb->posts}.ID WHERE {$table_name}.id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$grade_result->get_id()
			)
		);

		if ( ! $grade_result->get_id() || ! $grade_result_obj ) {
			throw new \Exception( __( 'Invalid grade result.', 'learning-management-system' ) );
		}

		$grade_result->set_props(
			array(
				'user_id'            => $grade_result_obj->user_id,
				'parent_id'          => $grade_result_obj->parent_id,
				'item_id'            => $grade_result_obj->item_id,
				'item_name'          => $grade_result_obj->item_name,
				'item_type'          => $grade_result_obj->item_type,
				'grade_name'         => $grade_result_obj->grade_name,
				'status'             => $grade_result_obj->status,
				'grade_color'        => empty( trim( $grade_result_obj->post_content ?? '' ) ) ? RandomColor::one() : $grade_result_obj->post_content,
				'grade_point'        => $grade_result_obj->grade_point,
				'grade_id'           => $grade_result_obj->grade_id,
				'earned_grade_point' => $grade_result_obj->earned_grade_point,
				'earned_percent'     => $grade_result_obj->earned_percent,
				'weight'             => $grade_result_obj->weight,
				'created_at'         => $this->string_to_timestamp( $grade_result_obj->created_at ),
				'modified_at'        => $this->string_to_timestamp( $grade_result_obj->modified_at ),
			)
		);

		$grade_result->set_object_read( true );

		/**
		 * Fires after reading a grade from database.
		 *
		 * @since 2.5.20
		 *
		 * @param integer $id The grade ID.
		 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $object The grade object.
		 */
		do_action( 'masteriyo_grade_result_read', $grade_result->get_id(), $grade_result );
	}

	/**
	 * Update a grade in the database.
	 *
	 * @since 2.5.20
	 *
	 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $grade_result Grade object.
	 *
	 * @return void
	 */
	public function update( Model &$grade_result ) {
		global $wpdb;

		$grade_result->set_modified_at( time() );
		$changes = $grade_result->get_changes();

		$post_data_keys = array(
			'user_id',
			'parent_id',
			'item_id',
			'item_name',
			'status',
			'grade_name',
			'grade_point',
			'grade_id',
			'earned_grade_point',
			'earned_percent',
			'weight',
			'created_at',
		);

		// Only update the post when the post data changes.
		if ( array_intersect( $post_data_keys, array_keys( $changes ) ) ) {

			$wpdb->update(
				$grade_result->get_table_name(),
				array(
					'user_id'            => $grade_result->get_user_id( 'edit' ),
					'parent_id'          => $grade_result->get_parent_id( 'edit' ),
					'item_id'            => $grade_result->get_item_id( 'edit' ),
					'grade_id'           => $grade_result->get_grade_id( 'edit' ),
					'item_type'          => $grade_result->get_item_type( 'edit' ),
					'item_name'          => $grade_result->get_item_name( 'edit' ),
					'status'             => $grade_result->get_status( 'edit' ),
					'grade_name'         => $grade_result->get_grade_name( 'edit' ),
					'grade_point'        => $grade_result->get_grade_point( 'edit' ),
					'earned_grade_point' => $grade_result->get_earned_grade_point( 'edit' ),
					'earned_percent'     => $grade_result->get_earned_percent( 'edit' ),
					'weight'             => $grade_result->get_weight( 'edit' ),
					'modified_at'        => gmdate( 'Y-m-d H:i:s', $grade_result->get_modified_at( 'edit' )->getOffsetTimestamp() ),
				),
				array( 'id' => $grade_result->get_id() ),
				array(
					'%d',
					'%d',
					'%d',
					'%d',
					'%s',
					'%s',
					'%s',
					'%s',
					'%s',
					'%f',
					'%f',
					'%d',
					'%f',
				)
			);
		}

		$grade_result->apply_changes();

		/**
		 * Fires after updating a grade.
		 *
		 * @since 2.5.20
		 *
		 * @param integer $id The grade result ID.
		 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $object The grade object.
		 */
		do_action( 'masteriyo_update_grade_result', $grade_result->get_id(), $grade_result );
	}

	/**
	 * Delete a grade from the database.
	 *
	 * @since 2.5.20
	 *
	 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $grade_result Grade object.
	 * @param array $args   Array of args to pass.alert-danger.
	 */
	public function delete( Model &$grade_result, $args = array() ) {
		global $wpdb;
		$args = wp_parse_args(
			$args,
			array(
				'children' => true,
			)
		);

		$id          = $grade_result->get_id();
		$object_type = $grade_result->get_object_type();

		/**
		 * Filters whether to delete the grade result children or not.
		 *
		 * @since 2.5.20
		 *
		 * @param bool $children
		 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $grade_result
		 * @param \Masteriyo\Addons\Gradebook\Repository\GradeResultRepository $repository
			*/
		$delete_children = apply_filters( 'masteriyo_grade_result_delete_children', $args['children'], $grade_result, $this );

		if ( $delete_children ) {
			$this->delete_children( $grade_result );
		}

		/**
		 * Fires before deleting a grade.
		 *
		 * @since 2.5.20
		 *
		 * @param integer $id The grade ID.
		 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $object The grade object.
		 */
		do_action( 'masteriyo_before_delete_' . $object_type, $id, $grade_result );

		$wpdb->delete(
			$grade_result->get_table_name(),
			array( 'id' => $grade_result->get_id() ),
			array( '%d' )
		);

		$grade_result->set_id( 0 );

		/**
		 * Fires after deleting a grade.
		 *
		 * @since 2.5.20
		 *
		 * @param integer $id The grade ID.
		 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $object The grade object.
		 */
		do_action( 'masteriyo_after_delete_' . $object_type, $id, $grade_result );
	}

	/**
	 * Delete grade result children.
	 *
	 * @since 2.5.20
	 *
	 * @param \Masteriyo\Addons\Gradebook\Models\GradeResult $grade_result
	 */
	protected function delete_children( $grade_result ) {
		global $wpdb;

		$wpdb->delete(
			$grade_result->get_table_name(),
			array(
				'parent_id' => $grade_result->get_id(),
			),
			array(
				'%d',
			)
		);
	}

	/**
	 * Fetch grade result
	 *
	 * @since 2.5.20
	 *
	 * @param array $query_vars Query vars.
	 * @param \Masteriyo\Addons\Gradebook\Query\GradeResultQuery $query
	 *
	 * @return \Masteriyo\Addons\Gradebook\Models\GradeResult[]
	 */
	public function query( $query_vars, $query ) {
		global $wpdb;

		$search_criteria = array();
		$sql[]           = "SELECT * FROM {$wpdb->prefix}masteriyo_gradebook_results LEFT JOIN {$wpdb->posts} ON {$wpdb->prefix}masteriyo_gradebook_results.grade_id = {$wpdb->posts}.ID";

		// Construct where clause part.
		if ( ! empty( $query_vars['user'] ) ) {
			$search_criteria[] = $this->create_sql_in_query( 'user_id', $query_vars['user'] );
		}

		if ( ! empty( $query_vars['parent'] ) ) {
			$search_criteria[] = $this->create_sql_in_query( 'parent_id', $query_vars['parent'] );
		}

		if ( ! empty( $query_vars['item_type'] ) ) {
			$search_criteria[] = $this->create_sql_in_query( 'item_type', $query_vars['item_type'] );
		}

		if ( ! empty( $query_vars['include'] ) ) {
			$search_criteria[] = $this->create_sql_in_query( "{$wpdb->prefix}masteriyo_gradebook_results.id", $query_vars['include'] );
		}

		if ( ! empty( $query_vars['item'] ) ) {
			$search_criteria[] = $this->create_sql_in_query( 'item_id', $query_vars['item'] );
		}

		if ( 1 <= count( $search_criteria ) ) {
			$criteria = implode( ' AND ', $search_criteria );
			$sql[]    = 'WHERE ' . $criteria;
		}

		// Construct order and order by part.
		$sql[] = 'ORDER BY ' . sanitize_sql_orderby( $query_vars['orderby'] . ' ' . $query_vars['order'] );

		// Construct limit part.
		$per_page = $query_vars['per_page'];
		$page     = $query_vars['page'];

		if ( $page > 0 && $per_page > 0 ) {
			$count_sql         = $sql;
			$count_sql[0]      = "SELECT COUNT(*) FROM {$wpdb->prefix}masteriyo_gradebook_results";
			$count_sql         = implode( ' ', $count_sql ) . ';';
			$query->found_rows = absint( $wpdb->get_var( $count_sql ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

			$offset = ( $page - 1 ) * $per_page;
			$sql[]  = $wpdb->prepare( 'LIMIT %d, %d', $offset, $per_page );
		}

		// Generate SQL from the SQL parts.
		$sql = join( ' ', $sql ) . ';';

		// Fetch the results.
		$grade_results = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		$ids = wp_list_pluck( $grade_results, 'id' );

		$query->rows_count = count( $ids );

		$objects                = array_filter( array_map( 'masteriyo_get_grade_result', $ids ) );
		$grade_results_with_ids = array_combine( $ids, $grade_results );

		foreach ( $objects as $object ) {
			$color = RandomColor::one();

			if ( isset( $grade_results_with_ids[ $object->get_id() ] ) ) {
				$grade_result = $grade_results_with_ids[ $object->get_id() ];
				$color        = ! isset( $grade_result->post_content ) || empty( trim( $grade_result->post_content ) ) ? $color : $grade_result->post_content;
			}

			$object->set_grade_color( $color );
		}

		return $objects;
	}
}
