<?php
/**
 * This file is for performing the interdependant sync functionality.
 *
 * @package    ProductSync
 * @subpackage InterdependantSyncHandler
 * @author     miniOrange <info@miniorange.com>
 * @link       https://miniorange.com
 */

namespace MOWCPS\Common\Handlers\SyncHandler;

use MOWCPS\Common\Constants\Constants;
use MOWCPS\Common\Utils\Model_Utils;
use MOWCPS\Common\Handlers\SyncHandler\Abstract_Sync_Handler;
use MOWCPS\Common\Models\Sync_Details_DAO;
use MOWCPS\Common\Models\Product_Sync_Audit_Model;
use MOWCPS\Common\Constants\Plugin_Filters;
use MOWCPS\Common\Models\Setting_Model;
use MOWCPS\Common\Handlers\SyncAnalytics\Sync_Analytics;
use MOWCPS\Common\Utils\Utils;
use MOWCPS\Common\Services\Logging\Supplier_Logger;
/**
 * Handles interdependent product sync operations.
 *
 * @package    ProductSync
 * @subpackage SyncHandler
 */
class Interdependant_Sync_Handler extends Abstract_Sync_Handler {

	/**
	 * Product data.
	 *
	 * @var array
	 */
	public $product_data;

	/**
	 * Supplier details.
	 *
	 * @var array
	 */
	public $supplier_details;

	/**
	 * Constructor.
	 *
	 * @param int $supplier_id   The supplier ID.
	 * @param int $connection_id The connection ID.
	 */
	public function __construct( $supplier_id, $connection_id, $schedule_id = null, $batch_number = 1, $page_number = 1 ) {
		parent::__construct( $supplier_id, $connection_id, $schedule_id, $batch_number, $page_number );
		$this->supplier_details['sync_structure'] = 'simple_product_sync';
	}

	/**
	 * Handle the sync process.
	 *
	 * @param object $sync_operation The sync operation instance.
	 * @return string Sync end status.
	 */
	public function handle_sync( $sync_operation ) {
		ini_set( 'memory_limit', '1024M' ); //phpcs:ignore
		ini_set( 'max_execution_time', -1 ); //phpcs:ignore

		$sync_end_status = $sync_operation->set_product_data();

		return $sync_end_status;
	}

	/**
	 * Check sync process.
	 *
	 * @param array $cron_arguments Cron arguments including priority queue and sync types.
	 *
	 * @return string Sync end status or empty string.
	 */
	public function check_sync_process( $cron_arguments ): string {
		$batch_flag = Setting_Model::get_by_connection_and_operation( $this->connection_id, 'batch' );
		$batch_flag = ! empty( $batch_flag['is_active'] ) && '1' === (string) $batch_flag['is_active'];
		$page_flag  = Setting_Model::get_by_connection_and_operation( $this->connection_id, 'page' );
		$page_flag  = ! empty( $page_flag['is_active'] ) && '1' === (string) $page_flag['is_active'];

		if ( $this->is_sync_inactive() ) {
			return $this->handle_inactive_sync( $cron_arguments );
		}

		$sync_analytics = new Sync_Analytics( $this->supplier_id, $this->schedule_id, $this->connection_id );
		$sync_analytics->set_analytics_object( Product_Sync_Audit_Model::get_sync_audit( $this->supplier_id, $this->schedule_id, $this->connection_id ) );

		$sync_type = $cron_arguments['priority_queue'][ $this->connection_id ]['sync_type'];

		$last_product_number      = Model_Utils::get_option( $this->connection_id, false );
		$last_cron_triggered_time = Model_Utils::get_option( $sync_analytics->connection_id . '_cron_triggered_time', false );
		$sync_end_status          = '';
		$main_analytics           = Product_Sync_Audit_Model::get_sync_audit( $this->supplier_id, $this->schedule_id, $this->connection_id );
		Supplier_Logger::debug( $this->supplier_id, sprintf( 'main_analytics->status_message %s: %s', 'check_sync_process', $main_analytics ? $main_analytics->status_message : 'null' ) );
		Supplier_Logger::debug( $this->supplier_id, sprintf( 'sync_analytics->sync_status_message  %s: %s', 'check_sync_process', $sync_analytics->sync_status_message ) );

		if ( $main_analytics && Constants::COMPLETED_SYNC_RUNNING_STATUS !== $main_analytics->status_message && Constants::COMPLETED_SYNC_RUNNING_STATUS !== $sync_analytics->sync_status_message && 'Connection Sync Completed' !== $sync_analytics->sync_status_message ) {
			$max_execution_time = empty( ini_get( 'max_execution_time' ) ) ? 180 : ini_get( 'max_execution_time' );
			$max_execution_time = ( $max_execution_time < 180 || $max_execution_time > 600 ) ? ( 180 / 2 ) : ( $max_execution_time / 2 );
			Supplier_Logger::debug( $this->supplier_id, sprintf( 'max_execution_time  %s: %s', 'check_sync_process', $max_execution_time ) );
			Supplier_Logger::debug( $this->supplier_id, sprintf( 'last_cron_triggered_time  %s: %s', 'check_sync_process', $last_cron_triggered_time ) );
			Supplier_Logger::debug( $this->supplier_id, sprintf( 'diff of current time and last_cron_triggered_time  %s: %s', 'check_sync_process', time() - $last_cron_triggered_time ) );

			Supplier_Logger::debug( $this->supplier_id, sprintf( 'last_product_number  %s: %s', 'check_sync_process', $last_product_number ) );
			Supplier_Logger::debug( $this->supplier_id, sprintf( 'sync_analytics->total_product_parsed  %s: %s', 'check_sync_process', $sync_analytics->total_product_parsed ) );

			if ( time() - $last_cron_triggered_time >= $max_execution_time && false !== $last_product_number && (int) $last_product_number === (int) $sync_analytics->total_product_parsed ) {
				$is_cron_scheduled = $this->is_cron_scheduled_for_connection();
				Supplier_Logger::debug( $this->supplier_id, sprintf( 'is_cron_scheduled  %s: %s', 'check_sync_process', $is_cron_scheduled ) );

				if ( ! $is_cron_scheduled ) {
					$analytics_array = Product_Sync_Audit_Model::get_sync_audit( $this->supplier_id, $this->schedule_id, $this->connection_id, $this->batch_number, true );
					if ( ! empty( $analytics_array ) ) {
						$last_batch_analytics = end( $analytics_array );
						$next_process_flag    = $this->determine_next_process_flag( $last_batch_analytics, $batch_flag, $page_flag );
						Supplier_Logger::debug( $this->supplier_id, sprintf( 'Nice  %s: %s', 'check_sync_process', '----- New process initated from backup cron -----' ) );
						if ( Constants::OUTBOUND  === $sync_type  ) {
							return $this->handle_outbound_sync( $sync_analytics );
						}else {
							$this->initiate_next_process( $next_process_flag );
						}
						Model_Utils::update_option( $sync_analytics->connection_id, $sync_analytics->total_product_parsed );
						Model_Utils::update_option( $sync_analytics->connection_id . '_cron_triggered_time', time() );
						$sync_end_status = '';
					}
				} elseif ( Constants::SYNC_IN_PROGRESS === $sync_analytics->sync_status_message ) {
						\wp_clear_scheduled_hook( 'mowcps_product_sync_backup_cron', array( $cron_arguments ) );
						$sync_end_status = Constants::SYNC_ANALYTICS_CLEARED;
				} else {
					$sync_end_status = '';
				}
			} else {
				Supplier_Logger::debug( $this->supplier_id, sprintf( ' else block of backup %s: %s', 'check_sync_process', 'inside else block' ) );
				$last_cron_triggered_time = Model_Utils::get_option( $sync_analytics->connection_id . '_cron_triggered_time', false );
				if ( false === $last_cron_triggered_time ) {
					Model_Utils::update_option( $sync_analytics->connection_id . '_cron_triggered_time', time() );
				}
				Model_Utils::update_option( $sync_analytics->connection_id, $sync_analytics->total_product_parsed );
				\wp_clear_scheduled_hook( 'mowcps_product_sync_backup_cron', array( $cron_arguments ) );
				$next_cron = Utils::get_next_cron_frequency();
				Supplier_Logger::debug( $this->supplier_id, sprintf( 'backup next_cron  %s: %s', 'check_sync_process', $next_cron ) );
				Supplier_Logger::debug( $this->supplier_id, sprintf( 'single backup event cron time  %s: %s', 'check_sync_process', time() + ( $max_execution_time - ( time() - $last_cron_triggered_time ) ) ) );
				\wp_schedule_event( time() + $next_cron, 'mowcps_' . $next_cron . '_sec', 'mowcps_product_sync_backup_cron', array( $cron_arguments ) );
				\wp_schedule_single_event( time() + ( $max_execution_time - ( time() - $last_cron_triggered_time ) ), 'mowcps_product_sync_backup_cron', array( $cron_arguments ) );
				$sync_end_status = '';
			}
		} else {
			return $this->move_to_next_connection( $cron_arguments, $sync_analytics );
		}
		return $sync_end_status;
	}


	/**
	 * Check if sync status is inactive.
	 *
	 * @return bool True if sync is inactive, false otherwise.
	 */
	private function is_sync_inactive(): bool {
		$status = Product_Sync_Audit_Model::get_sync_running_status( $this->supplier_id, $this->schedule_id );
		return Constants::SYNC_RUNNING_STATUS_OFF === $status;
	}

	/**
	 * Handle sync when inactive.
	 *
	 * @param array $cron_arguments Cron arguments.
	 *
	 * @return string Sync end status message.
	 */
	private function handle_inactive_sync( array $cron_arguments ): string {
		\wp_clear_scheduled_hook( 'mowcps_product_sync_backup_cron', array( $cron_arguments ) );

		$analytics_data = Product_Sync_Audit_Model::get_sync_audit( $this->supplier_id, $this->schedule_id, $this->connection_id );

		if ( $analytics_data && ( Constants::SYNC_IN_PROGRESS === $analytics_data->status_message || Constants::SYNC_ABORTED === $analytics_data->status_message || Constants::SYNC_PAUSED === $analytics_data->status_message || 'Sync Resumed' === $analytics_data->status_message || 'Initiating API call' === $analytics_data->status_message ) ) {
			if ( Constants::PAUSED_SYNC_RUNNING_STATUS === Model_Utils::get_option( 'mowcps_' . $this->supplier_id . '_' . $this->schedule_id . '_sync_running_status' ) ) {
				$sync_end_status = Constants::SYNC_PAUSED;
			} else {
				$sync_end_status = Constants::SYNC_ABORTED;
				// set supplier status from here.
			}
		} elseif ( $analytics_data && Constants::COMPLETED_SYNC_RUNNING_STATUS !== $analytics_data->status_message ) {
			// TODO: Batch analytics.
			$sync_end_status = Constants::SYNC_YET_TO_BE_INITIATED;
			// set supplier status from here.
		} else {
			$sync_end_status = Constants::COMPLETED_SYNC_RUNNING_STATUS;
			// set supplier status from here.
		}
		return $sync_end_status;
	}

	/**
	 * Handle outbound sync process.
	 *
	 * @param object $sync_analytics Sync analytics object.
	 *
	 * @return string Empty string always.
	 */
	private function handle_outbound_sync( $sync_analytics ): string {
		$max_execution_time = empty( ini_get( 'max_execution_time' ) ) ? 180 : ini_get( 'max_execution_time' );
		Supplier_Logger::debug( $this->supplier_id, sprintf( 'handle_outbound_sync max_execution_time: %s', $max_execution_time ) );
		$last_cron_triggered_time          = Model_Utils::get_option( $sync_analytics->connection_id . '_cron_triggered_time', false );
		$outbound_last_cron_triggered_time = Model_Utils::get_option( $sync_analytics->connection_id . '_outbound_cron_triggered_time', false );
		$sync_end_status                   = '';

		$option_key    = 'outbound_total_products_parsed_' . $sync_analytics->connection_id;
		$current_value = Model_Utils::get_option( $option_key );
		Supplier_Logger::debug( $this->supplier_id, sprintf( 'handle_outbound_sync %s: %s', 'check_sync_process', 'inside handle_outbound_sync before condition' ) );
		Supplier_Logger::debug( $this->supplier_id, sprintf( 'handle_outbound_sync %s: %s', 'check_sync_process', "sync_analytics->total_product_parsed: {$sync_analytics->total_product_parsed}, current_value: {$current_value}, time_diff: " . ( time() - $last_cron_triggered_time ) ) );
		
		if ( ($sync_analytics->total_product_parsed >= $current_value  && time() - $last_cron_triggered_time >= $max_execution_time) && ( false !== $outbound_last_cron_triggered_time && time() - $outbound_last_cron_triggered_time >= $max_execution_time ) ) {
			Model_Utils::update_option( $option_key, $sync_analytics->total_product_parsed );
			Supplier_Logger::debug( $this->supplier_id, sprintf( 'handle_outbound_sync %s: %s', 'check_sync_process', 'inside handle_outbound_sync' ) );
			$current_status  = $sync_analytics->additional_sync_data;
			Supplier_Logger::debug( $this->supplier_id, sprintf( 'outbound_batch_number: %s, outbound_last_product_synced: %s', $current_status['outbound_batch_number'] ?? 'N/A', $current_status['outbound_last_product_synced'] ?? 'N/A' ) );
			if ( ! empty( $current_status['outbound_batch_number'] ) && $current_status['outbound_batch_number'] > $current_status['outbound_last_product_synced'] ) {
				$sync_analytics->additional_sync_data['outbound_last_product_synced'] = $current_status['outbound_batch_number'];
				Supplier_Logger::debug( $this->supplier_id, sprintf( 'outbound sync updating last_product_synced to batch_number %s', $current_status['outbound_batch_number'] ) );
				$sync_analytics->save();
			} elseif ( ! empty( $current_status['outbound_batch_number'] ) && $current_status['outbound_batch_number'] === $current_status['outbound_last_product_synced'] && 'Sync Completed' !== $sync_analytics->sync_status_message ) {
				Sync_Details_DAO::get_instance()->set_outbound_sync_status( $this->connection_id, true );
				if( !$this->is_cron_scheduled_for_connection() ) {
					Supplier_Logger::debug( $this->supplier_id, sprintf( 'outbound sync initiate next batch_number') );
					$this->initiate_next_process( 'outbound_sync' );
				}
			}
		}
		return $sync_end_status;
	}

	/**
	 * Check if a cron job is scheduled for the current connection.
	 *
	 * @return bool True if cron is scheduled, false otherwise.
	 */
	private function is_cron_scheduled_for_connection(): bool {
		$cron_jobs = _get_cron_array();
		foreach ( $cron_jobs as $timestamp => $cron ) {
			if ( isset( $cron['mowcps_product_batch_sync_cron'] ) ) {
				$cron_hash          = array_key_first( $cron['mowcps_product_batch_sync_cron'] );
				$cron_connection_id = array_key_first( $cron['mowcps_product_batch_sync_cron'][ $cron_hash ]['args'][0]['priority_queue'] ) ?? false;
				if ( $cron_connection_id === $this->connection_id ) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Move to the next connection in priority queue.
	 *
	 * @param array  $cron_arguments Cron arguments.
	 * @param object $sync_analytics Sync analytics object.
	 *
	 * @return string Empty string always.
	 */
	private function move_to_next_connection( $cron_arguments, $sync_analytics ): string {
		$main_analytics = Product_Sync_Audit_Model::get_sync_audit( $this->supplier_id, $this->schedule_id, $this->connection_id );
		// Get keys of the current priority queue.
		$queue_keys = array_keys( $cron_arguments['priority_queue'] );
		// Remove the first item.
		$first_key = $queue_keys[0];
		// Delete the previous sync entry.
		Model_Utils::delete_option( $sync_analytics->connection_id );
		\wp_clear_scheduled_hook( 'mowcps_product_sync_backup_cron', array( $cron_arguments ) );
		$next_connection_sync = false;
		if ( $main_analytics && Constants::COMPLETED_SYNC_RUNNING_STATUS === $main_analytics->sync_status_message ) {
			unset( $cron_arguments['priority_queue'][ $first_key ] );
			Sync_Details_DAO::get_instance()->set_priority_queue( $cron_arguments['priority_queue'] );
			$next_connection_sync = true;
		}

		// If priority queue is still not empty, re-schedule the cron with updated queue.
		if ( $next_connection_sync && ! empty( $cron_arguments['priority_queue'] ) ) {
			$next_cron = Utils::get_next_cron_frequency();
			\wp_schedule_event( time(), 'mowcps_' . $next_cron . '_sec', 'mowcps_product_sync_backup_cron', array( $cron_arguments ) );

			//ADD filter to UPDATE  manully created backup cron for backup-cron after updating backup cron.
			if ( has_filter( Plugin_Filters::FILTER_MANULLAY_CREATE_BACKUP_CRON ) ) {
				apply_filters(
					Plugin_Filters::FILTER_MANULLAY_CREATE_BACKUP_CRON,
					array(
						'event_specifier' => Plugin_Filters::EVENT_UPDATE_MANUAL_BACKUP_CRON,
						'supplier_id'     => $cron_arguments['supplier_id'],
						'connection_id'   => $cron_arguments['connection_id'],
						'additional_data'   => array(
							'updated_cron_arguments' => $cron_arguments,
						),
					)
				);
			}

			$sync_arguments = array(
				'batch_number'   => 1,
				'page_number'    => 1,
				'cron_initiator' => Constants::VENDOR_AVAILABLE_PRODUCTS,
				'sync_type'      => 'connection_sync',
			);

			$updated_cron_arguments = array(
				'supplier_id'    => $cron_arguments['supplier_id'],
				'schedule_id'    => $cron_arguments['schedule_id'],
				'vendor_name'    => $cron_arguments['vendor_name'],
				'connection_id'  => array_key_first( $cron_arguments['priority_queue'] ),
				'priority_queue' => $cron_arguments['priority_queue'],
				'sync_arguments' => $sync_arguments,
			);
			\wp_schedule_single_event( time() + 2, 'mowcps_product_batch_sync_cron', array( $updated_cron_arguments ) );
		}
		return '';
	}

	/**
	 * Determine the next process flag based on the last batch analytics.
	 *
	 * @param object $last_batch_analytics Last batch analytics object.
	 * @param string $batch_flag           Batch flag status.
	 * @param string $page_flag            Page flag status.
	 *
	 * @return string Next process flag constant.
	 */
	private function determine_next_process_flag( $last_batch_analytics, $batch_flag, $page_flag ): string {
		$last_batch_number = (int) $last_batch_analytics->batch_number;
		$last_batch_number = ! empty( $last_batch_number ) ? $last_batch_number : 1;
		$next_process_flag = '';

		if ( Constants::VENDOR_UNAVAILABLE_PRODUCTS === $last_batch_analytics->sync_type ) {
			$this->batch_number = $last_batch_number - 1;
			$this->page_number  = $last_batch_number - 1;
			$next_process_flag  = Constants::DISCARD_PRODUCTS;
		} elseif ( true === $batch_flag ) {
			$this->batch_number = $last_batch_number - 1;
			$next_process_flag  = Constants::INITIATE_BATCH_SYNC;
		} elseif ( true === $page_flag ) {
			$additional_sync_data = json_decode( $last_batch_analytics->additional_data, true );
			if ( (int) $additional_sync_data['product_per_page'] !== (int) $last_batch_analytics->total_parsed ) {
				$this->page_number = $last_batch_number - 1;
				$next_process_flag = Constants::INITIATE_PAGE_SYNC;
			} else {
				$this->page_number = $last_batch_number;
				$next_process_flag = Constants::INITIATE_PAGE_SYNC;
			}
		}
		return $next_process_flag;
	}
}
