<?php
/**
 * Ajax sync request model.
 *
 * @package northmill-online-shared
 */

declare(strict_types=1);

namespace Northmill_Online_Shared\Abstracts\Hub_API\Sync\Ajax\Models;

use Northmill_Online_Shared\Abstract_Shared_Base;
use Northmill_Online_Shared\Modules\Ajax\Models\Request_Model;
use Northmill_Online_Shared\Abstracts\Hub_API\Sync\Abstract_Syncer;
use Northmill_Online_Shared\Abstracts\Hub_API\Sync\Abstract_Syncer_And_Cleaner;
use Northmill_Online_Shared\Helpers\Time_Helpers;
use Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;

defined( 'ABSPATH' ) || exit;

/**
 * Class Sync_Request_Model
 */
class Sync_Request_Model extends Request_Model {

	const TRANSIENT_PREFIX = 'northmill_online_shared_ajax_';

	/**
	 * Syncer instance.
	 *
	 * @var Abstract_Syncer $syncer
	 */
	public Abstract_Syncer $syncer;

	/**
	 * Contains status whether this is a request for cleanup rather than sync.
	 *
	 * @var bool $has_cleaner
	 */
	protected bool $has_cleaner = false;

	/**
	 * Number of Hub account to sync.
	 * 0 for first, 1 for second, etc.
	 *
	 * @var int $account
	 */
	private int $account;

	/**
	 * Page number.
	 *
	 * @var int $page
	 */
	private int $page;

	/**
	 * Whether this is a request to run a cleaner.
	 *
	 * @var bool $running_cleaner
	 */
	private bool $running_cleaner = false;

	/**
	 * Page size (number of items to get in each API request).
	 *
	 * @var int $page_size
	 */
	private int $page_size;

	/**
	 * Get all data or just data that has been updated since last sync.
	 *
	 * @var bool $get_all_data
	 */
	private bool $get_all_data = true;

	/**
	 * Total number of Hub accounts.
	 *
	 * @var int $total_accounts
	 */
	private int $total_accounts;

	/**
	 * Name of the option to store last sync time.
	 *
	 * @var string $last_updated_option_name
	 */
	private string $last_updated_option_name;

	/**
	 * Priority, determines the order in which the sync is run.
	 *
	 * @var int $priority
	 */
	private int $priority;

	/**
	 * Sync_Request_Model constructor.
	 *
	 * @param string          $key        Ajax action.
	 * @param Abstract_Syncer $syncer     Syncer instance.
	 * @param int             $priority   Priority.
	 * @param string|null     $nonce_name Nonce name.
	 */
	public function __construct(
		string $key,
		Abstract_Syncer $syncer,
		int $priority = 10,
		?string $nonce_name = null,
	) {
		parent::__construct(
			$key,
			array(),
			false,
			$nonce_name,
		);

		$this->has_cleaner = is_a( $syncer, Abstract_Syncer_And_Cleaner::class );
		$this->syncer      = $syncer;
		$this->priority    = $priority;
	}

	/**
	 * Get priority.
	 *
	 * @return int
	 */
	public function get_priority(): int {
		return $this->priority;
	}
	/**
	 * Ajax request handler function.
	 *
	 * @return void
	 */
	public function ajax_request(): void {
		$this->verify_request( $params );

		if ( ! $this->syncer->has_hub_accounts() ) {
			wp_send_json_error( 'No Hub account found' );
		}

		$this->setup_syncer();
		$this->check_if_sync_is_enabled();

		$this->maybe_run_cleaner();

		$this->maybe_update_updated_time_transient();
		$this->sync_inner_loop();
		$this->maybe_update_sync_updated_time();

		$response = array_merge(
			$this->syncer->get_status(),
			array(
				'has_cleaner' => $this->has_cleaner,
			)
		);

		$this->respond( $response );
	}

	/**
	 * Verify request page data.
	 *
	 * @param null|array $params API params.
	 *
	 * @phpcs:disable WordPress.Security.NonceVerification.Missing
	 */
	protected function get_post_data( ?array &$params ): void {
		if ( ! empty( $_POST['page'] ) ) {
			if ( 'cleaner' === $_POST['page'] ) {
				$this->running_cleaner = true;
			} else {
				$this->page = intval( $_POST['page'] );
			}
		} else {
			$this->page = 0;
		}
		$this->account   = ! empty( $_POST['account'] ) ? intval( $_POST['account'] ) : 0;
		$this->page_size = ! empty( $_POST['page_size'] ) ? intval( $_POST['page_size'] ) : 20;

		$this->get_all_data = ! empty( $_POST['get_all_data'] ) && 'false' !== $_POST['get_all_data'];
	}
	// phpcs:enable

	/**
	 * Set up the syncer.
	 *
	 * @return void
	 */
	private function setup_syncer(): void {
		$this->syncer->set_page_size( $this->page_size );

		$backoffice_accounts = $this->syncer->get_hub_accounts();
		$backoffice_account  = $backoffice_accounts[ array_keys( $backoffice_accounts )[ $this->account ] ];
		$this->syncer->set_current_hub_account( $backoffice_account );

		$this->total_accounts = count( $backoffice_accounts );

		$this->last_updated_option_name = $this->syncer->get_last_synced_wp_option_name();
	}

	/**
	 * Check if a specific sync is enabled.
	 */
	private function check_if_sync_is_enabled(): void {
		if ( $this->syncer->is_enabled() ) {
			return; // Resume sync.
		} else {
			$this->respond( $this->syncer->set_skip_status() ); // Skip sync.
		}
	}

	/**
	 * Maybe run cleaner instead of syncer
	 */
	private function maybe_run_cleaner(): void {
		if ( $this->has_cleaner && $this->running_cleaner ) {
			/**
			 * Checked by method has_cleaner().
			 *
			 * @noinspection PhpPossiblePolymorphicInvocationInspection
			 */
			$this->syncer->clean( $this->get_all_data );
			$this->respond( $this->syncer->set_cleaned_status() );
		}
	}

	/**
	 * Check if this is the first page of the first account.
	 *
	 * @return bool
	 */
	private function is_first_page(): bool {
		return 0 === $this->account && 0 === $this->page;
	}

	/**
	 * Maybe update updated time transient. For use when sync is finished.
	 *
	 * @return void
	 */
	private function maybe_update_updated_time_transient(): void {
		if ( $this->is_first_page() ) {
			set_transient(
				self::TRANSIENT_PREFIX . $this->last_updated_option_name,
				Time_Helpers::get_current_api_time()
			);
		}
	}

	/**
	 * Sync inner loop. Will be called once for each ajax request.
	 */
	private function sync_inner_loop(): void {
		$args = array(
			'page' => $this->page,
			// Page size is added in syncer.
		);

		if ( ! $this->get_all_data ) {
			$last_update_api_parameter = $this->syncer->get_last_synced_api_parameter();

			if ( ! empty( $last_update_api_parameter ) ) {
				$args[ $last_update_api_parameter ] = $this->syncer->get_last_synced_wp_option();
				if ( empty( $args[ $last_update_api_parameter ] ) ) {
					unset( $args[ $last_update_api_parameter ] );
				}
			}
		}

		$this->syncer->before_sync_loop( $args );
		$args   = $this->syncer->parse_api_params( $args );
		$synced = $this->syncer->sync(
			$args,
			$this->is_first_page()
		);
		$this->syncer->after_sync_loop();

		if ( ! $synced ) {
			Abstract_Shared_Base::base_instance()->logger->log(
				'sync',
				'Sync error in class',
				array(
					'class' => get_class( $this->syncer ),
				),
				Log_Level_Enum::ERROR
			);
			wp_send_json_error( 'Sync failed' );
		}

		$status = $this->syncer->get_status();
		$this->syncer->update_status( 'total_accounts', $this->total_accounts );
		$this->syncer->update_status( 'account', $this->account );
		$this->syncer->update_status(
			'total_pages',
			$status['total'] > 1 ?
				(int) ceil( $status['total'] / $this->syncer->get_page_size() ) :
				1
		);
	}

	/**
	 * Maybe update last updated time. For use when sync is finished.
	 *
	 * @return void
	 */
	private function maybe_update_sync_updated_time(): void {
		$status     = $this->syncer->get_status();
		$start_time = get_transient( self::TRANSIENT_PREFIX . $this->last_updated_option_name );
		if (
			$status['total_accounts'] === $this->account + 1 &&
			$status['total_pages'] === $this->page + 1 &&
			$start_time
		) {
			$this->syncer->set_last_synced_wp_option( $start_time, 'ajax', $this->get_all_data );
			delete_transient( self::TRANSIENT_PREFIX . $this->last_updated_option_name );
		}
	}
}
