<?php
declare( strict_types=1 );

namespace FiboFilters\Indexer;

use FiboFilters\Config\Menu;
use FiboFilters\Settings\Settings;
use FiboFilters\WooCommerce\Helper as WooCommerceHelper;
use FiboFilters\WP\Helper as WPHelper;

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

/**
 * Class Scheduler
 */
class Scheduler {
	const HOOK                   = 'fibofilters_recurring_indexing';
	const LAST_DATA_SETTINGS_KEY = 'fibofilters_schedule_last_data';

	/** @var \WC_Queue_Interface */
	private $queue;

	/** @var array */
	private $data;

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

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

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

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

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

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

	public function __construct(
		BuildInfo $build_info,
		Config $config,
		Logger $logger,
		Settings $settings,
		WooCommerceHelper $woocommerce_helper,
		WPHelper $wp_helper
	) {
		$this->build_info         = $build_info;
		$this->config             = $config;
		$this->logger             = $logger;
		$this->settings           = $settings;
		$this->woocommerce_helper = $woocommerce_helper;
		$this->wp_helper          = $wp_helper;
	}

	public function init() {
		add_action( self::HOOK, [ $this, 'recurring_task' ] );
		add_action( 'admin_init', [ $this, 'maybe_run_on_admin_init' ] );
		add_action( 'rest_api_init', [ $this, 'maybe_run_on_rest_api_init' ] );

		add_filter( 'fibofilters/settings/section=scheduler', [ $this, 'next_task_description' ] );
	}

	public function maybe_run_on_admin_init() {
		if ( ! defined( 'DOING_AJAX' ) ) {
			$this->process();
		}
	}
	public function maybe_run_on_rest_api_init() {
		$uri = esc_url( $_SERVER['REQUEST_URI'] );
		if (
			strpos( $uri, 'fibofilters/v1/index' ) !== false ||
			strpos( $uri, 'fibofilters/v1/settings/fields' ) !== false ||
			strpos( $uri, 'wp/v2/settings' ) !== false
		) {
			$this->process();
		}
	}

	private function process(): void {
		$this->maybe_init_queue();
		if ( ! empty( $this->queue ) ) {
			$this->set_data();
			$this->register_schedule();
		}
	}

	/**
	 * Schedule a recurring indexing
	 *
	 * @param bool $force_reschedule Force to reschedule task.
	 */
	public function register_schedule( bool $force_reschedule = false ): void {
		$interval         = $this->get_interval_name();
		$init_hour        = $this->get_init_hour();
		$init_day         = $this->get_init_day();
		$old_interval     = $this->get_latest_interval_name();
		$old_init_hour    = $this->get_latest_init_hour();
		$old_init_day     = $this->get_latest_init_day();
		$schedule_enabled = $this->is_enabled();
		$is_running       = $this->is_running();

		if ( $force_reschedule ) {
			$this->queue->cancel_all( self::HOOK );
		}

		// If scheduler was enabled.
		if ( $schedule_enabled && ! $is_running ) {
			$first_run_ts = $this->get_first_run_timestamp();
			$this->queue->schedule_single( $first_run_ts, self::HOOK );

			$this->update_data( 'last_interval', $interval, 'string' );
			$this->update_data( 'last_init_hour', $init_hour, 'int' );
			$this->update_data( 'last_init_day', $init_day, 'int' );

			$this->settings->reset_cache();
		}

		// When the interval, init hour or init day was changed.
		if ( $schedule_enabled
			&& (
				( ! empty( $interval ) && $interval !== $old_interval ) ||
				( $init_hour >= 0 && $init_hour !== $old_init_hour ) ||
				( $init_day >= 0 && $init_day !== $old_init_day )
			)
		) {
			$first_run_ts = $this->get_first_run_timestamp();
			$this->queue->cancel_all( self::HOOK );
			$this->queue->schedule_single( $first_run_ts, self::HOOK );

			$this->update_data( 'last_interval', $interval, 'string' );
			$this->update_data( 'last_init_hour', $init_hour, 'int' );
			$this->update_data( 'last_init_day', $init_day, 'int' );

			$this->settings->reset_cache();
		}

		// When scheduler was disabled.
		if ( ! $schedule_enabled && $is_running ) {
			$this->queue->cancel_all( self::HOOK );

			$this->settings->reset_cache();
		}
	}

	/**
	 * Check if scheduler is enabled
	 */
	public function is_enabled(): bool {
		return $this->settings->get( 'scheduler_indexer_schedule' );
	}

	/**
	 * Check if scheduler is running
	 */
	public function is_running(): bool {
		return ! empty( $this->get_next_task() );
	}

	/**
	 * Get the date of next task
	 */
	public function get_next_task() {
		return $this->queue->get_next( self::HOOK );
	}

	/**
	 * Get name of interval
	 */
	public function get_interval_name(): string {
		$value = $this->settings->get( 'scheduler_schedule_interval', 'weekly' );
		if ( in_array( $value, [ 'daily', 'weekly' ], true ) ) {
			return $value;
		}

		return 'weekly';
	}

	/**
	 * Get latest scheduler interval in seconds
	 */
	public function get_latest_interval_name(): string {
		$interval = '';

		if ( ! empty( $this->data['last_interval'] ) ) {
			$interval = $this->data['last_interval'];
		}

		return $interval;
	}


	/**
	 * Get latest init hour
	 */
	public function get_latest_init_hour(): int {
		$hour = 3;

		if ( ! empty( $this->data['last_init_hour'] ) && is_numeric( $this->data['last_init_hour'] ) && $this->data['last_init_hour'] > 0 ) {
			$hour = (int) $this->data['last_init_hour'];
		}

		return $hour;
	}

	/**
	 * Get latest init day
	 */
	public function get_latest_init_day(): int {
		$day = 1;

		if ( ! empty( $this->data['last_init_day'] ) && is_numeric( $this->data['last_init_day'] ) && $this->data['last_init_day'] > 0 ) {
			$day = (int) $this->data['last_init_day'];
		}

		return $day;
	}

	/**
	 * Update latest scheduler interval
	 *
	 * @param string $key Key.
	 * @param mixed  $value Value.
	 * @param string $type Data type.
	 */
	public function update_data( string $key, $value, string $type ): bool {
		$updated = false;

		if ( $type === 'string' && ! empty( $value ) ) {
			$this->data[ $key ] = $value;
			$updated            = update_option( self::LAST_DATA_SETTINGS_KEY, $this->data );
		} elseif ( $type === 'int' && ! empty( $value ) && is_numeric( $value ) && $value > 0 ) {
			$this->data[ $key ] = (int) $value;
			$updated            = update_option( self::LAST_DATA_SETTINGS_KEY, $this->data );
		}

		return $updated;
	}

	/**
	 * Get hour to start the task
	 */
	public function get_init_hour(): int {
		$init_hour = 3;

		$hour = $this->settings->get( 'scheduler_schedule_start_time', 3 );
		if ( $hour !== '' && is_numeric( $hour ) && $hour >= 0 && $hour < 24 ) {
			$init_hour = (int) $hour;
		}

		return $init_hour;
	}

	/**
	 * Get day to start the task
	 */
	public function get_init_day(): int {
		$init_day = 1;

		$day = $this->settings->get( 'scheduler_schedule_start_day', 1 );
		if ( is_numeric( $day ) && (int) $day >= 1 && (int) $day <= 7 ) {
			$init_day = (int) $day;
		}

		return $init_day;
	}

	/**
	 * Get timestamp for first task run
	 */
	public function get_first_run_timestamp(): int {
		$interval     = $this->get_interval_name();
		$hours_offset = $this->get_init_hour() * 60 * 60;
		if ( $interval === 'weekly' ) {
			$day_name  = $this->get_day_name( $this->get_init_day() );
			$base_time = new \WC_DateTime( 'this ' . $day_name );
		} else {
			$base_time = new \WC_DateTime( 'today' );
		}
		$now          = new \WC_DateTime();
		$tmp_time     = new \WC_DateTime( 'today', new \DateTimeZone( wc_timezone_string() ) );
		$hours_offset = $hours_offset - $tmp_time->getOffset();

		$ts = $base_time->getTimestamp() + $hours_offset;
		if ( $ts < $now->getTimestamp() ) {
			if ( $interval === 'weekly' ) {
				$ts = $ts + 7 * DAY_IN_SECONDS;
			} else {
				$ts = $ts + ( $now->getTimestamp() - $ts > DAY_IN_SECONDS ? 2 * DAY_IN_SECONDS : DAY_IN_SECONDS );
			}
		}

		return $ts;
	}

	/**
	 * Print data for debugging
	 */
	public function debug(): void {
		// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export, WordPress.Security.EscapeOutput.OutputNotEscaped
		echo 'Scheduler enabled: ' . var_export( $this->is_enabled(), true ) . '<br />';
		echo 'Is running: ' . var_export( $this->is_running(), true ) . '<br /><br />';
		echo 'Interval name: ' . var_export( $this->get_interval_name(), true ) . '<br />';
		echo 'Old interval name: ' . var_export( $this->get_latest_interval_name(), true ) . '<br /><br />';
		echo 'Init hour: ' . var_export( $this->get_init_hour(), true ) . '<br />';
		echo 'Old init hour: ' . var_export( $this->get_latest_init_hour(), true ) . '<br /><br />';
		echo 'Init day: ' . var_export( $this->get_init_day(), true ) . '<br />';
		echo 'Old init day: ' . var_export( $this->get_latest_init_day(), true ) . '<br />';
		echo 'First run date: ' . var_export( $this->get_first_run_timestamp(), true ) . '<br /><br />';
		echo 'Next Task: ' . var_export( $this->get_next_task(), true ) . '<br />';
		// phpcs:enable WordPress.PHP.DevelopmentFunctions.error_log_var_export, WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * Set data
	 */
	public function set_data(): void {
		$this->data = get_option( self::LAST_DATA_SETTINGS_KEY );
	}

	public function next_task_description( array $settings ) {
		if ( empty( $settings ) ) {
			return $settings;
		}

		if (
			Menu::is_on_settings_admin_page() ||
			(
				did_action( 'rest_api_init' ) &&
				strpos( esc_url( $_SERVER['REQUEST_URI'] ), 'fibofilters/v1/settings/fields' ) !== false
			)
		) {
			$this->maybe_init_queue();

			$date = null;

			if ( ! empty( $this->queue ) ) {
				$next_task = $this->queue->get_next( self::HOOK );
				if ( ! empty( $next_task ) && is_object( $next_task ) && is_a( $next_task, 'WC_DateTime' ) ) {
					$date = $this->wp_helper->local_date( $next_task->getTimestamp() );
				}
			}
			if ( $date ) {
				/* translators: 1: Planned date of rebuilding the index */
				$settings[200]['description'] = sprintf( _x( 'The next index rebuild: %s', '[admin]', 'fibofilters' ), $date );
			}
		}

		return $settings;
	}

	/**
	 * Rebuild index in recurring task
	 */
	public function recurring_task(): void {
		$this->maybe_init_queue();

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

		if ( $status === 'completed' ) {
			$build = true;
		} elseif ( $status === 'error' ) {
			list( $last_error_code ) = $this->logger->get_last_emergency_log();
			if ( in_array( $last_error_code, [ '001', '002' ], true ) ) {
				$build = true;
			}
		}

		if ( $build ) {
			do_action( 'fibofilters/indexer/build' );
		}

		$this->register_schedule( true );
	}

	private function maybe_init_queue() {
		if ( empty( $this->queue ) ) {
			$this->queue = $this->woocommerce_helper->get_queue();
		}
	}

	private function get_day_name( $day ): string {
		switch ( $day ) {
			case 1:
				return 'mon';
			case 2:
				return 'tue';
			case 3:
				return 'wed';
			case 4:
				return 'thu';
			case 5:
				return 'fri';
			case 6:
				return 'sat';
			case 7:
				return 'sun';
		}

		return 'mon';
	}
}
