<?php
declare( strict_types=1 );

namespace FiboFilters\Config;

use FiboFilters\Indexer\AsyncRebuildIndex;
use FiboFilters\Indexer\BackgroundProductUpdater;
use FiboFilters\Indexer\Builder;
use FiboFilters\Indexer\BuildInfo;
use FiboFilters\Indexer\Config;
use FiboFilters\Indexer\Logger;
use FiboFilters\Indexer\Scheduler;
use FiboFilters\Indexer\Updater;
use FiboFilters\Settings\Settings;
use FiboFilters\Util\Helpers;
use FiboFilters\WooCommerce\Helper as WooCommerceHelper;
use FiboFilters\WP\Helper as WPHelper;
use FiboFiltersVendor\Swaggest\JsonDiff\JsonDiff;

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

/**
 * Class Index
 */
class Index {

	/** @var AsyncRebuildIndex */
	private $async_rebuild_index;

	/** @var BackgroundProductUpdater */
	private $background_product_updater;

	/** @var Builder */
	private $builder;

	/** @var BuildInfo */
	private $build_info;

	/** @var Config */
	private $config;

	/** @var Logger */
	private $logger;

	/** @var Scheduler */
	private $scheduler;

	/** @var Updater */
	private $updater;

	/** @var WooCommerceHelper */
	private $woocommerce_helper;

	/** @var WPHelper */
	private $wp_helper;

	/** @var Settings */
	private $settings;

	public function __construct(
		AsyncRebuildIndex $async_rebuild_index,
		BackgroundProductUpdater $background_product_updater,
		Builder $builder,
		BuildInfo $build_info,
		Config $config,
		Logger $logger,
		Scheduler $scheduler,
		Updater $updater,
		WooCommerceHelper $woocommerce_helper,
		WPHelper $wp_helper,
		Settings $settings
	) {
		$this->async_rebuild_index        = $async_rebuild_index;
		$this->background_product_updater = $background_product_updater;
		$this->builder                    = $builder;
		$this->build_info                 = $build_info;
		$this->config                     = $config;
		$this->logger                     = $logger;
		$this->scheduler                  = $scheduler;
		$this->updater                    = $updater;
		$this->woocommerce_helper         = $woocommerce_helper;
		$this->wp_helper                  = $wp_helper;
		$this->settings                   = $settings;
	}

	public function init(): void {
		$this->builder->init();

		$this->async_rebuild_index->init_handler();
		$this->async_rebuild_index->init_cron();

		$this->background_product_updater->init();

		if ( $this->builder->is_index_valid() ) {
			$this->updater->init();
		}

		$this->scheduler->init();

		$this->background_processing_basic_auth_bypass();

		add_action( 'fibofilters/indexer/cancel', [ $this->builder, 'cancel_build_index' ], 10, 3 );

		add_action( 'admin_init', [ $this, 'maybe_build_on_settings_page' ] );
		add_action( 'admin_init', [ $this, 'fix_index_status' ] );

		add_action( 'fibofilters/indexer/build', [ $this, 'build' ] );
		add_action( 'fibofilters/indexer/delete_single', [ $this, 'delete_single' ] );
		add_action( 'fibofilters/indexer/update_single', [ $this, 'update_single' ] );

		add_action( 'fibofilters/storage/update/filters', [ $this, 'rebuild_after_settings_updated' ], 10, 2 );

		add_action(
			'fibofilters/indexer/status/error',
			function () {
				$this->logger->copy_last_logs();
			}
		);

		// TODO Remove in next releases.
		if ( ! defined( 'FIBOFILTERS_INDEXER_DEBUG' ) ) {
			define( 'FIBOFILTERS_INDEXER_DEBUG', true );
		}
		if ( ! defined( 'FIBOFILTERS_INDEXER_DEBUG_SCOPE' ) ) {
			define( 'FIBOFILTERS_INDEXER_DEBUG_SCOPE', 'all' );
		}
	}

	/**
	 * Build index
	 *
	 * @param string $mode Indexer mode: 'direct'|'async'.
	 */
	public function build( string $mode = '' ): void {
		if ( ! empty( $mode ) ) {
			$this->config->set_indexer_mode( $mode );
		} elseif ( $mode !== 'direct' ) {
			$count = apply_filters( 'fibofilters/indexer/max_number_of_products_for_direct_indexing', 500 );
			$total = $this->woocommerce_helper->get_total_products_count() + $this->woocommerce_helper->get_total_product_variations_count();
			if ( $total <= $count ) {
				$this->config->set_indexer_mode( 'direct' );
			}
		}

		if ( ! $this->builder->prepare_build_index() ) {
			return;
		}

		if ( $this->config->is_indexer_mode( 'async' ) ) {
			$this->async_rebuild_index->data( [ 'force' => true ] )->schedule_event()->dispatch();
		} else {
			// TODO Tutaj i w procesie asynchronicznym mozna by zrobic try/catch.
			$this->builder->build_index_process();
		}
	}

	public function cancel(): void {
		$this->builder->cancel_build_index( 'cancellation', 'Stop building the index. Starting the cancellation process.' );
	}

	/**
	 * Start build an index after first vising a settings page
	 */
	public function maybe_build_on_settings_page(): void {
		if ( ! Menu::is_on_settings_admin_page() ) {
			return;
		}

		// Skip index rebuilding when plugin version changes, because that will be done in \FiboFilters\Config\Install::maybe_install.
		if ( get_option( Env::$plugin_prefix . 'version_pro' ) !== Env::$version ) {
			return;
		}

		$build  = false;
		$status = $this->build_info->get_info( 'status', 'main' );

		if ( $this->config->is_parallel_building_enabled() ) {
			$status_tmp = $this->build_info->get_info( 'status', 'tmp' );
			if (
				( empty( $status ) || $status === 'not-exist' ) &&
				( empty( $status_tmp ) || $status_tmp === 'not-exist' )
			) {
				$build = true;
			}
		} else {
			if ( empty( $status ) || $status === 'not-exist' ) {
				$build = true;
			}
		}

		// Build index on start.
		if ( $build ) {
			$this->build();
		}
	}

	public function rebuild_after_settings_updated( $old_value, $value ): void {
		// Allow to disable rebuilding index after settings updated.
		if ( apply_filters( 'fibofilters/indexer/rebuild_after_settings_updated', true ) === false ) {
			return;
		}

		// Reset cache to avoid using old filters settings.
		$this->settings->reset_cache();

		// New added option.
		if ( empty( $old_value ) || $old_value === '[]' ) {
			$this->cancel();
			$this->build();
			return;
		}

		$old_value_decoded = json_decode( $old_value, true );
		$old_value_valid   = json_last_error() === JSON_ERROR_NONE && is_array( $old_value_decoded );
		$value_decoded     = json_decode( $value, true );
		$value_valid       = json_last_error() === JSON_ERROR_NONE && is_array( $value_decoded );

		// Break early and rebuild index if old or new value is not a valid JSON array.
		if ( ! $old_value_valid || ! $value_valid ) {
			$this->cancel();
			$this->build();
			return;
		}

		// Or process if both values are valid JSON arrays.
		$rebuild = true;

		try {
			$diff = new JsonDiff( json_decode( $old_value ), json_decode( $value ), JsonDiff::REARRANGE_ARRAYS + JsonDiff::SKIP_JSON_PATCH + JsonDiff::SKIP_JSON_MERGE_PATCH );

			$can_ignore_all_modifications = true;

			if ( $diff->getModifiedCnt() > 0 ) {
				// List of patterns that should be ignored.
				$ignored_patterns = [
					'/\/\d+\/attributes\/when_to_show/m',
					'/\/\d+\/attributes\/meta\/orderby/m',
					'/\/\d+\/attributes\/meta\/label_category/m',
					'/\/\d+\/attributes\/meta\/label_subcategory/m',
					'/\/\d+\/attributes\/meta\/label_in_applied_filters/m',
					'/\/\d+\/attributes\/meta\/show_slider/m',
					'/\/\d+\/attributes\/meta\/show_radio_ranges/m',
					'/\/\d+\/attributes\/meta\/show_inputs/m',
					'/\/\d+\/attributes\/meta\/show_counters/m', // Button type.
					'/\/\d+\/attributes\/meta\/any_label/m', // Radio type.
				];

				foreach ( $diff->getModifiedPaths() as $path ) {
					foreach ( $ignored_patterns as $ignored_pattern ) {
						if ( preg_match( $ignored_pattern, $path ) === 1 ) {
							continue 2;
						}
					}
					$can_ignore_all_modifications = false;
					break;
				}

				if ( $can_ignore_all_modifications ) {
					$rebuild = false;
				}
			} elseif ( $diff->getModifiedCnt() === 0 && $diff->getAddedCnt() === 0 && $diff->getRemovedCnt() === 0 && count( $diff->getRearranged() ) > 0 ) {
				// Only order of filters changed.
				$rebuild = false;
			}
		} catch ( \Exception $e ) {
			$rebuild = true;
		}

		if ( $rebuild ) {
			$this->cancel();
			$this->build();
		}
	}

	/**
	 * Make sure the index status has been copied from the "tmp" option to "main"
	 */
	public function fix_index_status(): void {
		if ( ! $this->config->is_parallel_building_enabled() ) {
			return;
		}

		$status_tmp = $this->build_info->get_info( 'status', 'tmp' );
		$status     = $this->build_info->get_info( 'status', 'main' );

		if ( $status_tmp === 'completed' && $status !== 'completed' ) {
			$build_id_tmp = $this->build_info->get_info( 'build_id', 'tmp' );
			$build_id     = $this->build_info->get_info( 'build_id', 'main' );
			if ( ! empty( $build_id_tmp ) && $build_id_tmp === $build_id ) {
				update_option( Builder::LAST_BUILD_OPTION_KEY . '_status', $status_tmp );
			}
		}
	}

	/**
	 * Remove index from database
	 *
	 * @param string $index_role Index role.
	 */
	public function remove_index( string $index_role = 'main' ): void {
		$this->builder->wipe_index_tables( $index_role );
		$this->build_info->add_info( 'status', 'not-exist', $index_role );
	}

	/**
	 * Delete single object in index
	 *
	 * @param int $post_id Post ID.
	 */
	public function delete_single( int $post_id ): void {
		$this->background_product_updater->handle( 'delete', $post_id );
	}

	/**
	 * Update single object in index
	 *
	 * @param int $post_id Post ID.
	 */
	public function update_single( int $post_id ): void {
		$this->background_product_updater->handle( 'update', $post_id );
	}

	/**
	 * Bypass Basic Auth for background processing and WP Cron requests
	 */
	public function background_processing_basic_auth_bypass() {
		add_filter(
			'http_request_args',
			function ( $request_args, $url ) {
				if ( strpos( $url, 'wp-cron.php' ) !== false || strpos( $url, 'admin-ajax.php' ) !== false ) {
					$authorization = Helpers::get_basic_auth_header();
					if ( ! empty( $authorization ) ) {
						$request_args['headers']['Authorization'] = $authorization;
					}
				}
				return $request_args;
			},
			10,
			2
		);
	}

	public function check_if_indexer_is_working_too_long(): void {
		$this->builder->check_if_indexer_is_working_too_long();
	}
}
