<?php
namespace Mewz\WCAS\Aspects\Workers;

use Mewz\Framework\Base\Aspect;
use Mewz\QueryBuilder\DB;
use Mewz\WCAS\Util\Matches;
use Mewz\WCAS\Util\Products;
use Mewz\WCAS\Util\Settings;

class OutOfStockProducts extends Aspect
{
	public $update = [];

	public function __hooks()
	{
		add_action('add_option_mewz_wcas_sync_product_visibility', [$this, 'add_option_sync_product_visibility'], 10, 2);
		add_action('update_option_mewz_wcas_sync_product_visibility', [$this, 'update_option_sync_product_visibility'], 10, 3);
		add_action('update_option_woocommerce_hide_out_of_stock_items', [$this, 'update_option_hide_out_of_stock_items'], 10, 3);

		add_action('shutdown', [$this, 'shutdown'], 20);
		add_action('mewz_wcas_task_update_outofstock', [$this, 'task_update_outofstock']);
		add_action('mewz_wcas_task_update_all_outofstock', [$this, 'task_update_all_outofstock']);
		add_action('mewz_wcas_task_update_products_outofstock', [$this, 'task_update_products_outofstock']);

		if (Settings::sync_product_visibility_bool()) {
			add_action('set_object_terms', [$this, 'set_object_terms'], 5, 6);
			add_action('woocommerce_update_product', [$this, 'woocommerce_update_product'], 10, 2);
			add_action('mewz_wcas_stock_affected', [$this, 'stock_affected']);
			add_action('mewz_wcas_match_rules_before_save', [$this, 'update_stock_match_rules']);
			add_action('mewz_wcas_deleting_stock_change', [$this, 'update_stock_match_rules']);
		}
	}

	public function add_option_sync_product_visibility($option, $value)
	{
		if (empty($this->update['all_products']) && Settings::sync_product_visibility_bool(false, $value)) {
			$this->update['all_products'] = true;
		}
	}

	public function update_option_sync_product_visibility($old_value, $value, $option)
	{
		if (empty($this->update['all_products']) && $old_value !== $value && Settings::sync_product_visibility_bool(false, $old_value) !== Settings::sync_product_visibility_bool(false, $value)) {
			$this->update['all_products'] = true;
		}
	}

	public function update_option_hide_out_of_stock_items($old_value, $value, $option)
	{
		if (empty($this->update['all_products']) && $old_value !== $value && Settings::sync_product_visibility() === 'auto') {
			$this->update['all_products'] = true;
		}
	}

	public function set_object_terms($object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids)
	{
		if (!$append && $taxonomy === 'product_visibility' && $terms && !in_array('outofstock', $terms)) {
			$this->update['product_ids'][$object_id] = true;
		}
	}

	public function woocommerce_update_product($product_id, $product)
	{
		if ($product->get_stock_status('edit') !== 'outofstock') {
			$this->update['product_ids'][$product_id] = true;
		}
	}

	public function stock_affected($stock_ids)
	{
		foreach ($stock_ids as $stock_id) {
			$this->update['stock_ids'][$stock_id] = true;
		}
	}

	public function update_stock_match_rules($stock_id)
	{
		$match_rules = Matches::get_rules($stock_id);

		foreach ($match_rules as $rule) {
			$this->update['match_rules'][] = $rule['attributes'];
		}
	}

	public function shutdown()
	{
		if (empty($this->update)) {
			return;
		}

		if (!empty($this->update['all_products'])) {
			$this->tasks->add('update_all_outofstock');
			return;
		}

		$data = [];

		if (!empty($this->update['product_ids'])) {
			$data['product_ids'] = implode(',', array_keys($this->update['product_ids']));
		}

		if (!empty($this->update['stock_ids'])) {
			$data['stock_ids'] = implode(',', array_keys($this->update['stock_ids']));
		}

		if (!empty($this->update['match_rules'])) {
			$data['match_rules'] = json_encode($this->update['match_rules']);
		}

		if ($data) {
			$this->tasks->add('update_outofstock', $data);
		}
	}

	public function task_update_outofstock($data)
	{
		if (!empty($data['product_ids'])) {
			$product_ids = explode(',', $data['product_ids']);
		} else {
			$product_ids = [];
		}

		$all_match_rules = [];

		if (!empty($data['match_rules'])) {
			$match_rules = json_decode($data['match_rules'], true);

			foreach ($match_rules as $match_rule) {
				$all_match_rules[]['attributes'] = $match_rule;
			}
		}

		if (!empty($data['stock_ids'])) {
			$stock_ids = explode(',', $data['stock_ids']);

			foreach ($stock_ids as $stock_id) {
				$match_rules = Matches::get_rules($stock_id);
				if (!$match_rules) continue;

				foreach ($match_rules as $match_rule) {
				    $all_match_rules[]['attributes'] = $match_rule['attributes'];
				}
			}
		}

		if ($all_match_rules) {
			$all_match_rules = array_unique($all_match_rules, SORT_REGULAR);
			$found_ids = $this->query_products_by_match_rules($all_match_rules, $product_ids);

			if ($found_ids) {
				$product_ids = array_merge($product_ids, $found_ids);
			}
		}

		$product_ids = array_keys(array_flip($product_ids));

		$this->update_products_outofstock($product_ids);
	}

	public function task_update_all_outofstock()
	{
		$product_ids = wc_get_products([
			'post_status' => ['publish', 'private', 'pending'],
			'return' => 'ids',
			'orderby' => ['ID' => 'ASC'],
			'limit' => -1,
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false,
			'no_found_rows' => true,
			'suppress_filters' => true, // TODO: Is it safe to use this?
		]);

		$this->update_products_outofstock($product_ids);
	}

	public function task_update_products_outofstock($data)
	{
		if (empty($data['product_ids'])) {
			return;
		}

		$product_ids = explode(',', $data['product_ids']);

		$this->update_products_outofstock($product_ids);
	}

	protected function update_products_outofstock($product_ids)
	{
	    if (!$product_ids) return;

		$sync_visibility = Settings::sync_product_visibility_bool(true);

		while ($product_id = array_shift($product_ids)) {
			$this->update_product_outofstock($product_id, $sync_visibility);

			if ($this->tasks->near_limits()) {
				$this->tasks->add('update_products_outofstock', ['product_ids' => implode(',', $product_ids)]);
				return;
			}
		}

		do_action('mewz_wcas_updated_products_outofstock', $sync_visibility);
	}

	protected function query_products_by_match_rules($match_rules, $exclude = null)
	{
		$tax_query = Matches::get_rules_tax_query($match_rules);
		if (!$tax_query) return false;

		$args = [
			'post_status' => ['publish', 'private', 'pending'],
			'tax_query' => [$tax_query],
			'return' => 'ids',
			'orderby' => ['ID' => 'ASC'],
			'limit' => -1,
			'exclude' => $exclude ?: '',
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false,
			'no_found_rows' => true,
			'suppress_filters' => true, // TODO: Is it safe to use this?
		];

		return wc_get_products($args);
	}

	protected function update_product_outofstock($product_id, $sync_visibility = true)
	{
		$product_id = (int)$product_id;
		$product = wc_get_product($product_id);

		if (!$product || $product instanceof \WC_Product_Variation) {
			return;
		}

		if ($sync_visibility) {
			Products::set_prop($product, 'bypass_limits_product_visibility', true);
			$is_outofstock = $product->get_stock_status() === 'outofstock';
			Products::set_prop($product, 'bypass_limits_product_visibility', false);
		} else {
			$is_outofstock = $product->get_stock_status('edit') === 'outofstock';
		}

		$has_outofstock = is_object_in_term($product_id, 'product_visibility', 'outofstock');

		if ($is_outofstock === $has_outofstock) {
			return;
		}

		if ($is_outofstock) {
			wp_set_post_terms($product_id, 'outofstock', 'product_visibility', true);
			$this->update_product_lookup_table($product_id, 'outofstock');
		} else {
			wp_remove_object_terms($product_id, 'outofstock', 'product_visibility');
			$this->update_product_lookup_table($product_id, $product->get_stock_status('edit'));
		}

		// indicate that product page cache should be cleared
		do_action('clean_post_cache', $product_id, get_post($product_id));

		do_action('mewz_wcas_updated_product_outofstock', $product_id, $product, $is_outofstock, $sync_visibility);
	}

	protected function update_product_lookup_table($product_id, $status)
	{
		return DB::table('wc_product_meta_lookup')
			->where('product_id', $product_id)
			->update(['stock_status' => $status]);
	}
}
