<?php
/**
 * This class manage the scheduler actions
 *
 * @package YITH\DynamicPricingAndDiscounts\Classes
 * @since   4.0.0
 */

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

/**
 * The class
 */
class YWDPD_Scheduler {
	use YWDPD_Singleton_Trait;


	/**
	 * The construct
	 */
	public function __construct() {
		add_action( 'delete_product_cat', array( $this, 'schedule_after_deleted_taxonomy' ), 10, 4 );
		add_action( 'delete_product_tag', array( $this, 'schedule_after_deleted_taxonomy' ), 10, 4 );
		add_action( 'delete_yith_shop_vendor', array( $this, 'schedule_after_deleted_taxonomy' ), 10, 4 );
		add_action( 'delete_yith_product_brand', array( $this, 'schedule_after_deleted_taxonomy' ), 10, 4 );
		add_action( 'ywdpd_process_rule_group', array( $this, 'process_rule_group' ) );
		add_action( 'woocommerce_new_product', array( $this, 'process_new_product' ) );
		add_action( 'edit_post', array( $this, 'process_edit_product_object' ), 10, 2 );
		add_action( 'delete_post', array( $this, 'process_delete_product_object' ), 20, 2 );
		add_action( 'wp_trash_post', array( $this, 'process_trash_product_object' ) );
		add_action( 'untrashed_post', array( $this, 'process_untrash_product_object' ) );
		add_action( 'ywdpd_after_insert_in_exclusion', array( $this, 'process_after_global_changes' ) );
		add_action( 'ywdpd_after_delete_in_exclusion', array( $this, 'process_after_global_changes' ) );
	}

	/**
	 * Schedule the update db actions after deleting a term
	 *
	 * @param int $term Term ID.
	 * @param int $tt_id Term taxonomy ID.
	 * @param WP_Term $deleted_term Copy of the already-deleted term.
	 * @param array $object_ids List of term object IDs.
	 *
	 * @return void
	 */
	public function schedule_after_deleted_taxonomy( $term, $tt_id, $deleted_term, $object_ids ) {

		$count    = $deleted_term->count;
		$taxonomy = $deleted_term->taxonomy;
		$type     = false;
		if ( $count > 0 ) {
			if ( 'product_cat' === $taxonomy ) {
				$type = 'specific_categories';
			} elseif ( 'product_tag' === $taxonomy ) {
				$type = 'specific_tag';
			} elseif ( 'yith_shop_vendor' === $taxonomy ) {
				$type = 'vendor_list';
			} elseif ( 'yith_product_brand' === $taxonomy ) {
				$type = 'specific_brands';
			}

			if ( $type ) {
				$rule_ids = $this->get_rules_by_taxonomy( $term, $type );
				$this->schedule( $rule_ids, $type );
			}
		}
	}

	/**
	 * Schedule
	 *
	 * @param int[] $rule_ids The rules to schedule.
	 * @param string $type The type.
	 *
	 * @return void
	 */
	protected function schedule( $rule_ids, $type ) {
		$group_schedules = array_chunk( $rule_ids, 20 );
		$time            = 0;
		$i               = 0;
		foreach ( $group_schedules as $schedule ) {
			$args          = array(
				'rules' => $schedule,
			);
			$has_scheduled = WC()->queue()->get_next( 'ywdpd_process_rule_group', $args, 'ywdpd-db-update-process-' . $type );
			if ( $has_scheduled ) {
				WC()->queue()->cancel( 'ywdpd_process_rule_group', $args, 'ywdpd-db-update-process-' . $type );
			}
			WC()->queue()->schedule_single(
				time() + $time,
				'ywdpd_process_rule_group',
				$args,
				'ywdpd-db-update-process-' . $type
			);
			$i ++;
			$time += MINUTE_IN_SECONDS * 10;
		}
	}

	/**
	 * Get all rules by taxonomy
	 *
	 * @param int $taxonomy_id The taxonomy id.
	 * @param string $type The type.
	 *
	 * @return int[]
	 */
	private function get_rules_by_taxonomy( $taxonomy_id, $type ) {
		$args = array(
			'post_status'    => 'publish',
			'post_type'      => 'ywdpd_discount',
			'posts_per_page' => - 1,
			'meta_query'     => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
				'relation' => 'and',
				array(
					'key'     => '_discount_mode',
					'value'   => array( 'bogo, special_offer', 'discount_whole', 'category_discount' ),
					'compare' => 'IN',
				),
			),
			'fields'         => 'ids',
		);

		$ids = get_posts( $args );
		/**
		 * The list of rules
		 *
		 * @var YWDPD_Price_Rule[] $rules
		 */
		$rules = array_filter( array_map( 'ywdpd_get_rule', $ids ) );

		$rule_ids_to_update = array();

		foreach ( $rules as $rule ) {
			if ( 'special_offer' === $rule->get_discount_mode() && $rule->is_exclude_adjustment_to_enabled() ) {
				continue;
			}
			$list_ids = array();
			if ( $type === $rule->get_rule_for() ) {
				$list_ids = $this->get_rule_for_ids( $rule, $type );
			} elseif ( $rule->is_exclude_from_apply_enabled() ) {
				$list_ids = $this->get_rule_for_exclusion_ids( $rule, $type );
			}

			// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
			if ( in_array( $taxonomy_id, $list_ids ) ) {
				$rule_ids_to_update[] = $rule->get_id();
			}
		}

		return $rule_ids_to_update;
	}

	/**
	 * Return the ids for the current selections
	 *
	 * @param YWDPD_Price_Rule $rule The rule.
	 * @param string $type The type.
	 *
	 * @return int[]
	 */
	private function get_rule_for_ids( $rule, $type ) {
		$list_ids = array();
		if ( 'specific_products' === $type ) {
			$list_ids = $rule->get_rule_for_product_list();
		} elseif ( 'specific_categories' === $type ) {
			$list_ids = $rule->get_rule_for_categories_list();
		} elseif ( 'specific_tag' === $type ) {
			$list_ids = $rule->get_rule_for_tags_list();
		}

		return apply_filters( 'ywdpd_get_scheduled_ids', $list_ids, $rule, $type );
	}

	/**
	 * Return the ids for the current selections
	 *
	 * @param YWDPD_Price_Rule $rule The rule.
	 * @param string $type The type.
	 *
	 * @return int[]
	 */
	private function get_rule_for_exclusion_ids( $rule, $type ) {
		$list_ids = array();
		if ( 'specific_products' === $type ) {
			$list_ids = $rule->get_exclude_rule_for_products_list();
		} elseif ( 'specific_categories' === $type ) {
			$list_ids = $rule->get_exclude_rule_for_categories_list();
		} elseif ( 'specific_tag' === $type ) {
			$list_ids = $rule->get_exclude_rule_for_tags_list();
		}

		return apply_filters( 'ywdpd_get_scheduled_excluded_ids', $list_ids, $rule, $type );
	}

	/**
	 * Process the taxonomy rule group
	 *
	 * @param int[] $rules The rule ids.
	 *
	 * @return void
	 * @throws Exception The exception.
	 */
	public function process_rule_group( $rules ) {
		/**
		 * The data store
		 *
		 * @var YWDPD_Dynamic_Rules_Data_Store $data_store ;
		 */
		$data_store = WC_Data_Store::load( 'ywdpd_dynamic_rules' );
		foreach ( $rules as $rule ) {
			if ( $data_store->rule_exists( $rule ) ) {
				$data_store->update( $rule );
			}
		}
	}

	/**
	 * Process the schedule rule update for the product id.
	 *
	 * @param int $product_id The product id.
	 * @param string $type The type.
	 *
	 * @return void
	 * @throws Exception The exception.
	 */
	public function schedule_product_process( $product_id, $type ) {
		/**
		 * The data store
		 *
		 * @var YWDPD_Dynamic_Rules_Data_Store $data_store ;
		 */
		$data_store = WC_Data_Store::load( 'ywdpd_dynamic_rules' );
		$rules      = $data_store->get_rule_ids_by_product_id( $product_id );
		$rules      = $rules ? wp_list_pluck( $rules, 'rule_id' ) : array();

		$this->schedule( $rules, $type );
	}

	/**
	 * Schedule the db update after insert a product
	 *
	 * @param int $product_id The product id.
	 *
	 * @throws Exception The exception.
	 */
	public function process_new_product( $product_id ) {
		$this->schedule_product_process( $product_id, 'new_product' );
	}

	/**
	 * Update all rules for specific product.
	 *
	 * @param int $post_id The post id.
	 * @param WP_Post $post The post.
	 *
	 * @return void
	 * @throws Exception The exception.
	 */
	public function process_edit_product_object( $post_id, $post ) {
		// phpcs:disable WordPress.Security.NonceVerification.Missing
		// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
		// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated
		if ( 'product' === get_post_type( $post_id ) ) {
			$product = wc_get_product( $post_id );
			if ( isset( $_POST['tax_input'] ) ) {
				$post_cat_ids = $_POST['tax_input']['product_cat'] ?? array();
				$post_cat_ids = array_filter( $post_cat_ids );
				if ( $_POST['tax_input']['product_tag'] ) {
					if ( ! is_array( $_POST['tax_input']['product_tag'] ) ) {
						$post_tag_slug = array_map( 'trim', explode( ',', $_POST['tax_input']['product_tag'] ) );
					} else {
						$post_tag_slug = $_POST['tax_input']['product_tag'];
					}
				} else {
					$post_tag_slug = array();
				}

				$post_tag_ids = array();
				foreach ( $post_tag_slug as $tag_slug ) {
					$term           = get_term_by( 'slug', $tag_slug, 'product_tag' );
					$post_tag_ids[] = $term->term_id;
				}

				$product_cat_ids = $product->get_category_ids( 'edit' );
				$product_tag_ids = $product->get_tag_ids( 'edit' );
				$check_1         = array_diff( array_unique( array_merge( $product_cat_ids, $post_cat_ids ) ), array_intersect( $product_cat_ids, $post_cat_ids ) );
				$check_2         = array_diff( array_unique( array_merge( $product_tag_ids, $post_tag_ids ) ), array_intersect( $product_tag_ids, $post_tag_ids ) );
				$has_changes     = count( $check_1 ) > 0 || count( $check_2 ) > 0;
				if ( apply_filters( 'ywdpd_can_process_schedule_in_edit_product', $has_changes, $product ) ) {
					$this->schedule_product_process( $product->get_id(), 'edit_product' );
				}
			}
		}
		// phpcs:enable
	}


	/**
	 * Update all rules for specific deleted product id.
	 *
	 * @param int $post_id The post id.
	 * @param WP_Post $post The post.
	 *
	 * @return void
	 * @throws Exception The exception.
	 */
	public function process_delete_product_object( $post_id, $post ) {

		if ( 'product' === $post->post_type ) {
			$this->schedule_product_process( $post_id, 'delete_product' );
		}
	}

	/**
	 * Update all rules for specific trashed product id.
	 *
	 * @param int $post_id The post id.
	 *
	 * @return void
	 * @throws Exception The exception.
	 */
	public function process_trash_product_object( $post_id ) {

		if ( 'product' === get_post_type( $post_id ) ) {
			$this->schedule_product_process( $post_id, 'trash_product' );
		}
	}

	/**
	 * Update all rules for specific untrashed product id.
	 *
	 * @param int $post_id The post id.
	 *
	 * @return void
	 * @throws Exception The exception.
	 */
	public function process_untrash_product_object( $post_id ) {
		if ( 'product' === get_post_type( $post_id ) ) {
			$this->schedule_product_process( $post_id, 'untrash_product' );
		}
	}

	/**
	 * Process global changes
	 *
	 * @return void
	 * @throws Exception The exception.
	 */
	public function process_after_global_changes() {
		/**
		 * The data store
		 *
		 * @var YWDPD_Dynamic_Rules_Data_Store $data_store ;
		 */
		$data_store = WC_Data_Store::load( 'ywdpd_dynamic_rules' );
		$rule_ids   = $data_store->query(
			array(
				'fields' => 'rule_id',
			)
		);

		$this->schedule( $rule_ids, 'global_changes' );
	}
}

YWDPD_Scheduler::get_instance();
