<?php
/**
 * The data store that manage the dynamic rule data
 *
 * @package  YITH\DynamicPricing\And\Discounts\DataStores
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
//phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
//phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
//phpcs:disable WordPress.DB.PreparedSQL.NotPrepared

/**
 * The data store
 */
class YWDPD_Dynamic_Rules_Data_Store {
	use YWDPD_Cached_Request_Trait;

	/**
	 * The type allowed of dynamic rules
	 *
	 * @var string[]
	 */
	protected $allowed_type = array(
		'bogo',
		'special_offer',
		'discount_whole',
		'category_discount',
	);

	/**
	 * Constructor method
	 */
	public function __construct() {
		$this->cache_group = 'ywdpd_rules_data_store';
	}

	/**
	 * Create new rows in the table
	 *
	 * @param int $dynamic_rule_id The dynamic rule id.
	 *
	 * @return bool|int
	 */
	public function create( $dynamic_rule_id ) {
		$dynamic_rule = ywdpd_get_rule( $dynamic_rule_id );
		if ( ! $this->is_allowed_type( $dynamic_rule->get_discount_mode() ) ) {
			return false;
		}

		if ( 'special_offer' === $dynamic_rule->get_discount_mode() && $dynamic_rule->is_exclude_adjustment_to_enabled() ) {
			return false;
		}
		global $wpdb;
		$enabled = $dynamic_rule->is_enabled() ? 'yes' : 'no';
		$for_all = 'all_products' === $dynamic_rule->get_rule_for() ? 'yes' : 'no';
		$query   = "INSERT INTO {$wpdb->yith_ywdpd_rules} ( rule_id,enabled,type, all_product) VALUES ( %d, %s, %s, %s)";
		$res     = $wpdb->query( $wpdb->prepare( $query, $dynamic_rule->get_id(), $enabled, $dynamic_rule->get_discount_mode(), $for_all ) );

		$this->store_product_ids( $dynamic_rule, 'yes' === $for_all );

		$this->clear_cache( $dynamic_rule_id );

		return $res;
	}

	/**
	 * Update the table with right info.
	 *
	 * @param int $dynamic_rule_id The dynamic rule id.
	 *
	 * @return bool
	 * @throws Exception The exception.
	 */
	public function update( $dynamic_rule_id ) {
		$dynamic_rule = ywdpd_get_rule( $dynamic_rule_id );
		if ( ! $this->is_allowed_type( $dynamic_rule->get_discount_mode() ) ) {
			return false;
		}

		if ( 'special_offer' === $dynamic_rule->get_discount_mode() && $dynamic_rule->is_exclude_adjustment_to_enabled() ) {
			$this->delete( $dynamic_rule_id );

			return false;
		}

		global $wpdb;
		$query = "SELECT * FROM {$wpdb->yith_ywdpd_rules} WHERE rule_id = %d";
		$res   = $wpdb->get_results( $wpdb->prepare( $query, $dynamic_rule_id ), ARRAY_A );
		$res   = current( $res );

		$current_status = $dynamic_rule->is_enabled() ? 'yes' : 'no';
		if ( $res && $current_status !== $res['enabled'] ) {
			$query = "UPDATE {$wpdb->yith_ywdpd_rules} SET enabled = %s WHERE rule_id = %d";
			$wpdb->query( $wpdb->prepare( $query, $current_status, $dynamic_rule_id ) );
		}
		$for_all = 'all_products' === $dynamic_rule->get_rule_for() ? 'yes' : 'no';
		if ( $res && $for_all !== $res['all_product'] ) {
			$query = "UPDATE {$wpdb->yith_ywdpd_rules} SET all_product = %s WHERE rule_id = %d";
			$res   = $wpdb->query( $wpdb->prepare( $query, $for_all, $dynamic_rule_id ) );
		}
		$this->remove_product_ids( $dynamic_rule );
		$this->store_product_ids( $dynamic_rule, 'yes' === $for_all );
		$this->clear_cache( $dynamic_rule_id );

		return $res;
	}

	/**
	 * Delete the rule information from db
	 *
	 * @param int $dynamic_rule_id The rule id.
	 *
	 * @return void
	 */
	public function delete( $dynamic_rule_id ) {
		global $wpdb;
		$wpdb->delete( $wpdb->yith_ywdpd_rules, array( 'rule_id' => $dynamic_rule_id ) );
		$wpdb->delete( $wpdb->yith_ywdpd_rule_products, array( 'rule_id' => $dynamic_rule_id ) );
		$this->clear_cache( $dynamic_rule_id );
	}

	/**
	 * Return the query results
	 *
	 * @param array $args The args.
	 *
	 * @return array|void
	 */
	public function query( $args = array() ) {
		$defaults = array(
			'enabled'  => '',
			'type'     => '',
			'rule_id'  => '',
			'rule__in' => array(),
			'limit'    => 0,
			'offset'   => 0,
			'fields'   => '',
			'object'   => 'rule',
			// if rule, return the result from "rules table", if product return from rule product table.
		);

		$args = wp_parse_args( $args, $defaults );

		$results = $this->cache_get( $this->get_versioned_cache_key( 'query', $args ) );
		if ( false === $results ) {
			global $wpdb;

			$query = "SELECT * FROM {$wpdb->yith_ywdpd_rules} WHERE 1=1";
			if ( ! empty( $args['enabled'] ) ) {
				$query .= $wpdb->prepare( ' AND enabled = %s', $args['enabled'] );
			}

			if ( ! empty( $args['type'] ) ) {
				$query .= $wpdb->prepare( ' AND type = %s', $args['type'] );
			}

			if ( count( $args['rule__in'] ) > 0 ) {
				$query .= " AND rule_id IN ( '" . implode( "','", $args['rule__in'] ) . "' )";

			} elseif ( ! empty( $args['rule_id'] ) ) {
				$query .= $wpdb->prepare( ' AND rule_id = %d', $args['rule_id'] );
			}

			if ( 'rule' === $args['object'] && ! empty( $args['limit'] ) && 0 < (int) $args['limit'] ) {
				$query .= $wpdb->prepare( ' LIMIT %d, %d', ! empty( $args['offset'] ) ? $args['offset'] : 0, $args['limit'] );
			}

			$results = $wpdb->get_results( $query, ARRAY_A );

			if ( 'product' === $args['object'] ) {
				$results = $this->get_results_for_product_query( $results, $args );
			} else {
				if ( $results ) {
					if ( ! empty( $args['fields'] ) ) {
						$results = wp_list_pluck( $results, $args['fields'] );
					}
				}
			}

			$this->cache_set( $this->get_versioned_cache_key( 'query', $args ), $results );
		}

		return $results;
	}

	/**
	 * Process the query for products
	 *
	 * @param array $rule_results The query results for the rules.
	 * @param array $args         The query arg.
	 *
	 * @return array|int|stdClass
	 */
	protected function get_results_for_product_query( $rule_results, $args ) {
		global $wpdb;
		$global_rule = $this->get_global_rule( $rule_results );
		if ( $global_rule ) {
			$query         = $wpdb->prepare( "SELECT DISTINCT product_id FROM {$wpdb->yith_ywdpd_rule_products} WHERE rule_id = %d", $global_rule );
			$results       = $wpdb->get_results( $query, ARRAY_A );
			$excluded_ids  = $results ? array_map( 'intval', wp_list_pluck( $results, 'product_id' ) ) : array();
			$products_args = array(
				'status'  => 'publish',
				'exclude' => $excluded_ids,
			);
			if ( ! empty( $args['fields'] ) && 'count' === $args['fields'] ) {
				$products_args['return'] = 'ids';
				$products_args['limit']  = - 1;
				$results                 = count( wc_get_products( $products_args ) );
			} else {
				if ( ! empty( $args['limit'] ) && 0 < (int) $args['limit'] ) {
					$products_args['limit']  = $args['limit'];
					$products_args['offset'] = $args['offset'] ?? 0;
				}
				$results = wc_get_products( $products_args );
			}

			return $results;

		} else {
			$rule_ids = wp_list_pluck( $rule_results, 'rule_id' );
			$query    = "SELECT DISTINCT product_id FROM {$wpdb->yith_ywdpd_rule_products} WHERE rule_id IN ( '" . implode( "','", $rule_ids ) . "' )";
			if ( ! empty( $args['limit'] ) && 0 < (int) $args['limit'] ) {
				$query .= $wpdb->prepare( ' LIMIT %d, %d', ! empty( $args['offset'] ) ? $args['offset'] : 0, $args['limit'] );
			}
			$results = $wpdb->get_results( $query, ARRAY_A );

			$ids           = $results ? array_map( 'intval', wp_list_pluck( $results, 'product_id' ) ) : array();
			$products_args = array(
				'status'  => 'publish',
				'include' => $ids,
				'limit'   => - 1,
			);
			if ( ! empty( $args['fields'] ) && 'count' === $args['fields'] ) {
				$products_args['return'] = 'ids';
				$results                 = count( $ids ) > 0 ? count( wc_get_products( $products_args ) ) : 0;
			} else {
				$results = count( $ids ) > 0 ? wc_get_products( $products_args ) : array();
			}

			return $results;
		}
	}

	/**
	 * Check if exist a rule for all product
	 *
	 * @param array $rule_results The query results for the rules.
	 *
	 * @return false|int
	 */
	protected function get_global_rule( $rule_results ) {

		foreach ( $rule_results as $result ) {
			if ( 'yes' === $result['all_product'] ) {
				return $result['rule_id'];
			}
		}

		return false;
	}

	/**
	 * Check if the rule id is exists in the table
	 *
	 * @param int $rule_id The rule id.
	 *
	 * @return bool
	 */
	public function rule_exists( $rule_id ) {
		$res = $this->query(
			array(
				'rule_id' => $rule_id,
			),
		);

		return count( $res ) > 0;
	}

	/**
	 * Get all rule id for specific product id
	 *
	 * @param int $product_id The product id.
	 *
	 * @return array
	 */
	public function get_rule_ids_by_product_id( $product_id ) {
		global $wpdb;

		return $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT rule_id FROM $wpdb->yith_ywdpd_rule_products WHERE product_id = %d", $product_id ), ARRAY_A );
	}

	/**
	 * Check if the rule type is allowed
	 *
	 * @param string $type The type.
	 *
	 * @return bool
	 */
	protected function is_allowed_type( $type ) {

		return in_array( $type, $this->allowed_type, true );
	}

	/**
	 * Store the product id in the meta table
	 *
	 * @param YWDPD_BOGO|YWDPD_Special_Offer|YWDPD_Category_Discount|YWDPD_Discount_Whole $dynamic_rule   The rule.
	 * @param bool                                                                        $store_excluded If true add in the db only the excluded product ids.
	 *
	 * @return void
	 */
	protected function store_product_ids( $dynamic_rule, $store_excluded = false ) {
		$rule_for             = $dynamic_rule->get_rule_for();
		$product_ids          = array();
		$excluded_product_ids = array();
		if ( 'all_products' === $rule_for ) {
			$product_ids[] = - 1;
		} elseif ( 'specific_products' === $rule_for ) {
			$product_ids = $dynamic_rule->get_rule_for_product_list();
		} elseif ( 'specific_categories' === $rule_for ) {
			$cat_ids     = $dynamic_rule->get_rule_for_categories_list();
			$product_ids = $this->get_products_by_categories( $cat_ids );
		} elseif ( 'specific_tag' === $rule_for ) {
			$tag_ids     = $dynamic_rule->get_rule_for_tags_list();
			$product_ids = $this->get_products_by_tag( $tag_ids );
		} else {
			$product_ids = apply_filters( 'ywdpd_product_ids_included_data_store', array(), $dynamic_rule );
		}
		$global_product_excluded     = YWDPD_Exclusion_List_Data_Store::get_terms_by_type( 'product' );
		$global_product_cat_excluded = $this->get_products_by_categories( YWDPD_Exclusion_List_Data_Store::get_terms_by_type( 'product_cat' ) );
		$global_product_tag_excluded = $this->get_products_by_tag( YWDPD_Exclusion_List_Data_Store::get_terms_by_type( 'product_tag' ) );
		if ( $dynamic_rule->is_exclude_from_apply_enabled() ) {
			$exclude_for = $dynamic_rule->get_exclude_rule_for();
			if ( 'specific_products' === $exclude_for ) {
				$excluded_product_ids = $dynamic_rule->get_exclude_rule_for_products_list();
			} elseif ( 'specific_categories' === $exclude_for ) {
				$cat_ids              = $dynamic_rule->get_exclude_rule_for_categories_list();
				$excluded_product_ids = $this->get_products_by_categories( $cat_ids );
			} elseif ( 'specific_tag' === $exclude_for ) {
				$tag_ids              = $dynamic_rule->get_exclude_rule_for_tags_list();
				$excluded_product_ids = $this->get_products_by_tag( $tag_ids );
			} else {
				$excluded_product_ids = apply_filters( 'ywdpd_product_ids_excluded_data_store', array(), $dynamic_rule );
			}
		}
		$global_product_excluded = array_merge( $global_product_excluded, $global_product_tag_excluded, $global_product_cat_excluded );
		if ( $store_excluded ) {
			$product_ids = array_merge( $excluded_product_ids, $global_product_excluded );
		} else {
			$product_ids = array_diff( $product_ids, $excluded_product_ids, $global_product_excluded );
		}
		$product_ids = apply_filters( 'ywdpd_product_ids_data_store', array_unique( $product_ids ), $store_excluded, $dynamic_rule );

		if ( count( $product_ids ) > 0 ) {
			global $wpdb;

			$query = "INSERT INTO $wpdb->yith_ywdpd_rule_products (rule_id,product_id) VALUES (%d,%d)";
			foreach ( $product_ids as $product_id ) {
				$wpdb->query( $wpdb->prepare( $query, $dynamic_rule->get_id(), $product_id ) );
			}
		}
	}

	/**
	 * Delete the product ids in the meta table
	 *
	 * @param YWDPD_BOGO|YWDPD_Special_Offer|YWDPD_Category_Discount|YWDPD_Discount_Whole $dynamic_rule The rule.
	 *
	 * @return void
	 */
	protected function remove_product_ids( $dynamic_rule ) {
		global $wpdb;
		$wpdb->delete( $wpdb->yith_ywdpd_rule_products, array( 'rule_id' => $dynamic_rule->get_id() ) );
	}

	/**
	 * Get the product ids by category
	 *
	 * @param int[] $categories The category ids.
	 *
	 * @return int[]
	 */
	public function get_products_by_categories( $categories ) {
		if ( count( $categories ) === 0 ) {
			return array();
		}
		$terms = get_terms(
			array(
				'taxonomy' => 'product_cat',
				'include'  => $categories,
			)
		);

		if ( is_wp_error( $terms ) || empty( $terms ) ) {
			return array();
		}

		$args = array(
			'category' => wc_list_pluck( $terms, 'slug' ),
			'limit'    => - 1,
			'return'   => 'ids',
		);

		return wc_get_products( $args );
	}

	/**
	 * Get the product ids by tag
	 *
	 * @param int[] $tag The tag ids.
	 *
	 * @return int[]
	 */
	public function get_products_by_tag( $tag ) {
		if ( count( $tag ) === 0 ) {
			return array();
		}
		$terms = get_terms(
			array(
				'taxonomy' => 'product_tag',
				'include'  => $tag,
			)
		);

		if ( is_wp_error( $terms ) || empty( $terms ) ) {
			return array();
		}

		$args = array(
			'tag'    => wc_list_pluck( $terms, 'slug' ),
			'limit'  => - 1,
			'return' => 'ids',
		);

		return wc_get_products( $args );
	}

	/**
	 * Clear rule related caches
	 *
	 * @param int $rule_id The rule id.
	 *
	 * @return void
	 */
	public function clear_cache( $rule_id ) {
		$this->cache_delete( 'ywdpd_dynamic_rules_-' . $rule_id );
		$this->invalidate_versioned_cache();
	}
}
