<?php
/**
 * The automation class
 *
 * Used to define custom automation functions used for displaying and handling automation steps
 *
 *
 * @since      9.7
 * @package    CartBounty Pro - Save and recover abandoned carts for WooCommerce
 * @subpackage CartBounty Pro - Save and recover abandoned carts for WooCommerce/includes
 * @author     Streamline.lv
 */
class CartBounty_Pro_Automation{

	/**
	 * The admin handler that manages the plugin's settings, options, and backend functionality.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_Admin    $admin    Provides methods to control and extend the plugin's admin area.
	 */
	protected $admin = null;

	/**
	 * The BulkGate handler that manages the plugin's settings, options, and backend functionality.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_BulkGate    $bulkgate    Provides methods to control and extend the plugin's BulkGate area.
	 */
	protected $bulkgate = null;

	/**
	 * The Push Notification handler that manages the plugin's settings, options, and backend functionality.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_Push_Notification    $push_notification    Provides methods to control and extend the plugin's Push Notification area.
	 */
	protected $push_notification = null;

	/**
	 * The WordPress handler that manages the plugin's settings, options, and backend functionality.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_WordPress    		$wordpress    Provides methods to control and extend the plugin's WordPress area.
	 */
	protected $wordpress = null;

	/**
	 * Get the admin handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_Admin
	 */
	protected function admin(){
		
		if( $this->admin === null ){
			$this->admin = new CartBounty_Pro_Admin( CARTBOUNTY_PRO_PLUGIN_NAME_SLUG, CARTBOUNTY_PRO_VERSION_NUMBER );
		}

		return $this->admin;
	}

	/**
	 * Get the BulkGate handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_BulkGate
	 */
	protected function bulkgate(){
		
		if( $this->bulkgate === null ){
			$this->bulkgate = new CartBounty_Pro_BulkGate();
		}

		return $this->bulkgate;
	}

	/**
	 * Get the Push Notification handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_Push_Notification
	 */
	protected function push_notification(){
		
		if( $this->push_notification === null ){
			$this->push_notification = new CartBounty_Pro_Push_Notification();
		}

		return $this->push_notification;
	}

	/**
	 * Get the WordPress handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_WordPress
	 */
	protected function wordpress(){
		
		if( $this->wordpress === null ){
			$this->wordpress = new CartBounty_Pro_WordPress();
		}

		return $this->wordpress;
	}

	/**
	 * Check if current recovery is a multiple step automation like WordPress or Bulkgate or not
	 *
	 * @since    9.10
	 * @return   boolean
     * @param    string       $recovery				Recovery option
	 */
	public function multi_step_automation( $recovery ) {
		$result = false;
		$multi_step_automations = array(
			'wordpress',
			'bulkgate',
			'push_notification',
		);

		if( in_array( $recovery, $multi_step_automations ) ){
			$result = true;
		}

		return $result;
	}

	/**
	 * Checking if automation enabled. At least one automation template should be enabled
	 *
	 * @since    9.7
	 * @return   string
     * @param    boolean    $enabled    		  Whether automation has been enabled or not
	 */
	public function display_automation_status( $enabled ) {
		$status = sprintf( '<span class="status inactive">%s</span>', esc_html__( 'Disabled', 'woo-save-abandoned-carts' ) );

		if( $enabled ){
			$status = sprintf( '<span class="status active">%s</span>', esc_html__( 'Active', 'woo-save-abandoned-carts' ) );
		}

		return $status;
	}

	/**
	 * Check if a given automation step is enabled
	 * By default will return all active automation steps
	 *
	 * @since    9.7
	 * @return   array or boolean
	 * @param    string       $recovery				Recovery option
	 * @param    integer      $automation			Automation step. If not provided, an array of enabled automation steps returned
	 * @param    integer      $single				Weather we want to return single value or not
	 */
	public function get_active_steps( $recovery, $automation = false, $single = false ){
		$result = false;

		if( $recovery == 'wordpress' ){
			$option = 'cartbounty_pro_automation_steps';
			
		}elseif( $recovery == 'bulkgate' ){
			$option = 'cartbounty_pro_bulkgate_steps';
		
		}elseif( $recovery == 'push_notification' ){
			$option = 'cartbounty_pro_push_notification_steps';
		}

		$this->restore_steps( $recovery, $option );
		$automation_steps = get_option( $option );

		if( !empty( $automation_steps ) ){
			if( $single ){ //If we want to know if a specific automation step is active
				$active_steps = $automation_steps[$automation];
				if( isset( $active_steps['enabled'] ) ){
					$result = true;
				}

			}else{
				$result = array();
				foreach ( $automation_steps as $key => $step ) {
					if( isset( $step['enabled'])){
						$result[] = $key;
					}
				}
			}
		}

		return $result;
	}

	/**
	 * Checking if a specified automation is enabled or not.
	 * Return true if at least one automation step is active
	 *
	 * @since    9.10
	 * @return   boolean
	 * @param    string       $recovery				Recovery option
	 */
	public function automation_enabled( $recovery ) {
		$enabled = false;

		if( $recovery == 'bulkgate' ){ //If Bulkgate - must add extra validation step if it has been connected via API
			$bulkgate = $this->bulkgate();

			if( !$bulkgate->api_valid() ) return;
		}

		$active_steps = $this->get_active_steps( $recovery );

		if( is_array( $active_steps ) ){
			
			if( count( $active_steps ) > 0){
				$enabled = true;
			}
		}

		if( $recovery == 'push_notification' ){ //In case of Push notification recovery - check if test mode is not enabled. Required so that store administrators can test Push notifications without requirement to enable any of automation steps
			$push_notification = $this->push_notification();
			$test_mode_on = $push_notification->get_settings( 'test_mode' );

			if( $test_mode_on ){
				$enabled = true;
			}
		}

		return $enabled;
	}

	/**
	 * Checking total number of activated steps and determines which step should be accessible
	 * Returns a class if the step is should not be accessible
	 * First automation step is always accessible
	 *
	 * @since    9.7
	 * @return   string
	 * @param    string     $recovery				Recovery option
	 * @param    integer    $automation				Automation number
	 */
	public function check_if_accessible( $recovery, $automation = false ){
		$class = '';
		$active_steps = $this->get_active_steps( $recovery );
		
		if( $automation != 0 ){ //If we are not looking at first automation step
			if( $automation > count( $active_steps ) ){
				$class = " cartbounty-pro-step-disabled";
			}
		}

		echo $class;
	}

	/**
	* Method validates automation step data and disables steps which should not be active
	* Deactivating 2nd and 3rd automation steps in case 1st step disabled
	* Deactivating 3rd automation step in case 2nd step disabled etc.
	*
	* @since    9.7
	*/
	public function validate_automation_steps(){
		if( isset( $_POST['cartbounty_pro_automation_steps'] ) ){
			$steps = $_POST['cartbounty_pro_automation_steps'];
			$option = 'cartbounty_pro_automation_steps';

		}elseif( isset( $_POST['cartbounty_pro_bulkgate_steps'] ) ){
			$steps = $_POST['cartbounty_pro_bulkgate_steps'];
			$option = 'cartbounty_pro_bulkgate_steps';

		}elseif( isset( $_POST['cartbounty_pro_push_notification_steps'] ) ){
			$steps = $_POST['cartbounty_pro_push_notification_steps'];
			$option = 'cartbounty_pro_push_notification_steps';

		}else{ //Exit in case the automation step data is not present
			return;
		}

		$admin = $this->admin();
		$disabled_steps = array();

		foreach ( $steps as $key => $step ) {
			if( !isset( $step['enabled'] ) ){
				$disabled_steps[] = $key; //Add current step to disabled steps array
			}

			//Sanitizing Subject
			if( isset( $step['subject'] ) ){
				$steps[$key]['subject'] = sanitize_text_field( $step['subject'] );
			}
			//Sanitizing Heading
			if( isset( $step['heading'] ) ){
				$steps[$key]['heading'] = $admin->sanitize_field( $step['heading'] );
			}
			//Sanitizing Content
			if( isset( $step['content'] ) ){
				$steps[$key]['content'] = $admin->sanitize_field( $step['content'] );
			}
			//Sanitizing Coupon description
			if( isset( $step['coupon_description'] ) ){
				$steps[$key]['coupon_description'] = $admin->sanitize_field( $step['coupon_description'] );
			}
			//Sanitizing Coupon prefix
			if( isset( $step['coupon_prefix'] ) ){
				$steps[$key]['coupon_prefix'] = $admin->sanitize_field( $step['coupon_prefix'] );
			}
		}

		foreach ( $disabled_steps as $key => $disabled_step ) {
			if( $disabled_step == 0 ){ //If first step is disabled, deactivate 2nd, 3rd and 4th steps
				unset( $steps[1]['enabled'] );
				unset( $steps[2]['enabled'] );
				unset( $steps[3]['enabled'] );

			}elseif( $disabled_step == 1 ){ //If second step is disabled, deactivate 3rd and 4th steps
				unset( $steps[2]['enabled'] );
				unset( $steps[3]['enabled'] );
			
			}elseif( $disabled_step == 2 ){ //If third step is disabled, deactivate 4th steps
				unset( $steps[3]['enabled'] );
			}
		}

		update_option( $option, $steps );
	}

	/**
	* Method restores automation steps in case they are cleared, deleted or do not exist
	*
	* @since    9.7
	* @param    string		$recovery			Recovery option
	* @param    string		$option   		    Option name
	*/
	private function restore_steps( $recovery, $option ){
		$automation_steps = get_option( $option );
		
		if( empty( $automation_steps ) ){
			if( $recovery == 'wordpress' ){
				update_option( $option,
					array(
						array(
							'subject' 				=> '',
							'heading' 				=> '',
							'content' 				=> '',
							'coupon_description' 	=> '',
							'include_image' 		=> true,
							'coupon_expiry' 		=> 604800000
						),
						array(
							'subject' 				=> '',
							'heading' 				=> '',
							'content' 				=> '',
							'coupon_description' 	=> '',
							'include_image' 		=> true,
							'coupon_expiry' 		=> 604800000
						),
						array(
							'subject' 				=> '',
							'heading' 				=> '',
							'content' 				=> '',
							'coupon_description' 	=> '',
							'include_image' 		=> true,
							'coupon_expiry' 		=> 604800000
						),
						array(
							'subject' 				=> '',
							'heading' 				=> '',
							'content' 				=> '',
							'coupon_description' 	=> '',
							'include_image' 		=> true,
							'coupon_expiry' 		=> 604800000
						)
					)
				);

			}elseif( $recovery == 'bulkgate' ){
				$bulkgate = $this->bulkgate();
				update_option( $option,
					array(
						array(
							'content' 		=> $bulkgate->get_defaults( 'content', 0 ),
							'coupon_expiry' => 604800000
						),
						array(
							'content' 		=> $bulkgate->get_defaults( 'content', 1 ),
							'coupon_expiry' => 604800000
						),
						array(
							'content' 		=> $bulkgate->get_defaults( 'content', 2 ),
							'coupon_expiry' => 604800000
						),
						array(
							'content' 		=> $bulkgate->get_defaults( 'content', 3 ),
							'coupon_expiry' => 604800000
						)
					)
				);

			}elseif( $recovery == 'push_notification' ){
				update_option( $option,
					array(
						array(
							'heading' 		=> '',
							'content' 		=> '',
							'include_icon' 	=> false,
							'include_image' => false,
							'coupon_expiry' => 604800000
						),
						array(
							'heading' 		=> '',
							'content' 		=> '',
							'include_icon' 	=> false,
							'include_image' => false,
							'coupon_expiry' => 604800000
						),
						array(
							'heading' 		=> '',
							'content' 		=> '',
							'include_icon' 	=> false,
							'include_image' => false,
							'coupon_expiry' => 604800000
						),
						array(
							'heading' 		=> '',
							'content' 		=> '',
							'include_icon' 	=> false,
							'include_image' => false,
							'coupon_expiry' => 604800000
						)
					)
				);
			}

		}elseif( is_array( $automation_steps ) && count( $automation_steps ) != CARTBOUNTY_PRO_MAX_STEPS ){ //If available steps are not equal to maximum available step count. Temporary block since version 10.9. Will be removed in future versions
			$this->upgrade_available_steps( $recovery, $option );
		}
	}

	/**
     * Method increases available steps without owerwriting current recovery step settings
     * Temporary block since version 10.9. Will be removed in future versions. 
     *
     * @since    10.9
     * @param    string		$recovery			Recovery option
	 * @param    string		$option   		    Option name
     */
	public function upgrade_available_steps( $recovery, $option ){
		$steps = get_option( $option );

		if( $recovery == 'wordpress' ){
			$steps[] = array(
				'subject' 				=> '',
				'heading' 				=> '',
				'content' 				=> '',
				'coupon_description' 	=> '',
				'include_image' 		=> true,
				'coupon_expiry' 		=> 604800000
			);

		}elseif( $recovery == 'bulkgate' ){
			$bulkgate = $this->bulkgate();
			$steps[] = array(
				'content' 		=> $bulkgate->get_defaults( 'content', 3 ),
				'coupon_expiry' => 604800000
			);

		}elseif( $recovery == 'push_notification' ){
			$steps[] = array(
				'heading' 		=> '',
				'content' 		=> '',
				'include_icon' 	=> false,
				'include_image' => false,
				'coupon_expiry' => 604800000
			);
		}

		update_option( $option, $steps );
	}

	/**
     * Update abandoned cart last sent time, completed steps and if the automation is completed or not
     *
     * @since    9.7
     * @param    object     $cart   		    Cart data
     * @param    string     $current_time       Current time
     * @param    string     $recovery			Recovery option
     */
	public function update_cart( $cart, $current_time, $recovery ){
		global $wpdb;
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$complete = false;
		$active_steps = $this->get_active_steps( $recovery );

		if( $recovery == 'wordpress' ){
			$completed_steps = $cart->wp_steps_completed;
			$field_steps_completed = 'wp_steps_completed';
			$field_last_sent = 'wp_last_sent';
			$field_completed = 'wp_complete';

		}elseif( $recovery == 'bulkgate' ){
			$completed_steps = $cart->sms_steps_completed;
			$field_steps_completed = 'sms_steps_completed';
			$field_last_sent = 'sms_last_sent';
			$field_completed = 'sms_complete';
		
		}elseif( $recovery == 'push_notification' ){
			$completed_steps = $cart->pn_steps_completed;
			$field_steps_completed = 'pn_steps_completed';
			$field_last_sent = 'pn_last_sent';
			$field_completed = 'pn_complete';
		}

		if( $completed_steps >= ( count( $active_steps ) - 1 ) ){ //Checking if this is the last automation step to mark the automation as complete for the current cart
			$complete = true;
		}

		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$cart_table}
				SET $field_steps_completed = %d,
				$field_last_sent = %s,
				$field_completed = %s
				WHERE id = %d",
				$completed_steps + 1,
				$current_time,
				$complete,
				$cart->id
			)
		);
	}

	/**
     * Handle stats update according to the action that has been recorded.
     *
     * @since    9.7
     * @param    string     $action   			Action type that must be added
     * @param    integer    $cart_id   			Cart ID
     * @param    integer    $step_nr            Automation step number
     * @param    string     $recovery			Recovery option
     */
	public function handle_message_stats( $action, $cart_id, $step_nr, $recovery ){

		if( $recovery == 'wordpress' ){
			if( $action == 'open' ){
				$update = $this->update_message_stats( $action, $cart_id, $step_nr, $recovery );

				if( $update ){
					$this->increase_message_stats( $step_nr, $action, $recovery );
				}

			}elseif( $action == 'click' ){
				$update = $this->update_message_stats( $action, $cart_id, $step_nr, $recovery );

				if( $update ){
					$this->increase_message_stats( $step_nr, $action, $recovery );
				}

				$action = 'open';
				$update = $this->update_message_stats( $action, $cart_id, $step_nr, $recovery );

				if( $update ){
					$this->increase_message_stats( $step_nr, $action, $recovery );
				}

			}elseif( $action == 'unsubscribed' ){
				$update = $this->update_message_stats( $action, $cart_id, $step_nr, $recovery );

				if( $update ){
					$this->increase_message_stats( $step_nr, $action, $recovery );
				}

			}elseif( $action == 'recovered' ){

				$recovered_carts_by_step = $this->get_recovered_cart_count_for_steps( $recovery );
			
				if( $recovered_carts_by_step ){
					$this->increase_message_stats( $step_nr, $action, $recovery, $recovered_carts_by_step );
				}
			}

		}elseif( $recovery == 'bulkgate' || $recovery == 'push_notification' ){
			if( $action == 'click' ){
				$update = $this->update_message_stats( $action, $cart_id, $step_nr, $recovery );

				if( $update ){
					$this->increase_message_stats( $step_nr, $action, $recovery );
				}

			}elseif( $action == 'unsubscribed' ){
				$update = $this->update_message_stats( $action, $cart_id, $step_nr, $recovery );

				if( $update ){
					$this->increase_message_stats( $step_nr, $action, $recovery );
				}
			
			}elseif( $action == 'recovered' ){
				$recovered_carts_by_step = $this->get_recovered_cart_count_for_steps( $recovery );
			
				if( $recovered_carts_by_step ){
					$this->increase_message_stats( $step_nr, $action, $recovery, $recovered_carts_by_step );
				}
			}
		}
	}

	/**
     * Update message statistics in the database according to the action.
     * In case the message table has duplicate messages sent in the same step, we update only the last one not to disrupt any statistics
     *
     * @since    9.7
     * @return   boolean or integer             False or updated row count
     * @param    string     $action   			Action type that must be added (open, click)
     * @param    integer    $cart_id   			Cart ID
     * @param    integer    $step_nr            Automation step number
     * @param    string     $recovery			Recovery option
     */
	public function update_message_stats( $action, $cart_id, $step_nr, $recovery ){
		global $wpdb;
		$table_name = $this->get_message_table_name( $recovery );

		if( !$table_name ) return;

		$update = $wpdb->query(
			$wpdb->prepare(
				"UPDATE $table_name
				SET $action = %d
				WHERE cart = %d
				AND step = %d
				ORDER BY id DESC
				LIMIT %d",
				1,
				$cart_id,
				$step_nr,
				1
			)
		);

		return $update;
	}

	/**
     * Increase text message stats of specified automation (sends, opens, clicks)
     *
     * @since    9.7
     * @param    integer    $step_nr			Automation step number
     * @param    string     $action				Action type that needs to be updated
     * @param    string     $recovery			Recovery option
     * @param    array      $ready_data			Data ready to be saved in the database as is
     */
	public function increase_message_stats( $step_nr, $action, $recovery, $ready_data = array() ){

		if( $recovery == 'wordpress' ){

			switch ( $action ) {
				case 'send':
					$option = 'cartbounty_pro_automation_sends';
					break;

				case 'open':
					$option = 'cartbounty_pro_automation_opens';
					break;

				case 'click':
					$option = 'cartbounty_pro_automation_clicks';
					break;

				case 'recovered':
					$option = 'cartbounty_pro_automation_recovered';
					break;

				case 'unsubscribed':
					$option = 'cartbounty_pro_automation_unsubscribed';
					break;
			}

		}elseif( $recovery == 'bulkgate' ){

			switch ( $action ) {
				case 'send':
					$option = 'cartbounty_pro_bulkgate_sends';
					break;

				case 'click':
					$option = 'cartbounty_pro_bulkgate_clicks';
					break;

				case 'recovered':
					$option = 'cartbounty_pro_bulkgate_recovered';
					break;

				case 'unsubscribed':
					$option = 'cartbounty_pro_bulkgate_unsubscribed';
					break;
			}

		}elseif( $recovery == 'push_notification' ){

			switch ( $action ) {
				case 'send':
					$option = 'cartbounty_pro_push_notification_sends';
					break;

				case 'send_failed':
					$option = 'cartbounty_pro_push_notification_sends_failed';
					break;

				case 'click':
					$option = 'cartbounty_pro_push_notification_clicks';
					break;

				case 'recovered':
					$option = 'cartbounty_pro_push_notification_recovered';
					break;
			}
		}

		$message_stats = get_option( $option );

		if( !is_array( $message_stats ) ) {
			$message_stats = array();
		}

		if( isset( $message_stats[$step_nr] ) ){ //If we already have previous stats for the current step
			$message_stats[$step_nr] = $message_stats[$step_nr] + 1;

		}else{ //If this is the first time we track this
			$message_stats[$step_nr] = 1;
		}

		if( $ready_data ){
			$message_stats = $ready_data;
		}

		update_option( $option, $message_stats );
	}

	/**
     * Retrieve message table name
     *
     * @since    10.6
     * @return   string             			Table name
     * @param    string     $recovery			Recovery option
     */
	public function get_message_table_name( $recovery ){
		global $wpdb;
		$admin = $this->admin();
		$table_name = '';

		if( $recovery == 'wordpress' ){
			$wordpress = $this->wordpress();
			
			if( !$admin->table_exists( 'email_table_exists' ) ){ //Making sure that the email table exists or gets created
				$wordpress->create_email_table();
			}

			$table_name = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_EMAILS;

		}elseif( $recovery == 'bulkgate' ){
			$bulkgate = $this->bulkgate();
			
			if( !$admin->table_exists( 'message_table_exists' ) ){ //Making sure that message table exists or gets created
				$bulkgate->create_message_table();
			}

			$table_name = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_MESSAGES;
		
		}elseif( $recovery == 'push_notification' ){
			$push_notification = $this->push_notification();
			
			if( !$admin->table_exists( 'push_notification_table_exists' ) ){ //Making sure that the notification table exists or gets created
				$push_notification->create_notification_table();
			}

			$table_name = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_NOTIFICATIONS;
		}

		return $table_name;
	}

	/**
     * Retrieve sent message history of a given abandoned cart
     * If a specific step_nr is provided, will return carts that have been sent in the given step
     *
     * @since    9.7
     * @return   array
     * @param    string     $recovery			Recovery option
     * @param    integer    $cart_id   			Cart ID
     * @param    integer    $step_nr			Automation step number
     */
	public function get_message_history( $recovery, $cart_id = false, $step_nr = false ){
		global $wpdb;
		$admin = $this->admin();
		$table_name = $this->get_message_table_name( $recovery );
		$where_query = 'cart = %s';
		$args = array( $cart_id );
		
		if( !$table_name ) return;

		if( is_numeric( $step_nr ) ){
			$where_query .= ' AND step = %d';
			$args[] = $step_nr;
		}

		$messages = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT *
				FROM {$table_name}
				WHERE $where_query
				ORDER BY time ASC",
				$args
			)
		);

		return $messages;
	}

	/**
     * Output a list of sent messages for a given cart
     *
     * @since    9.7
     * @return   array
     * @param    string     $recovery			Recovery option
     * @param    integer    $cart_id   			Cart ID
     * @param    boolean    $cart_recovered   	Is the abandoned cart marked as recovered or not
     */
	public function display_message_history( $recovery, $cart_id = false, $cart_recovered = false ){
		$messages = $this->get_message_history( $recovery, $cart_id );
		$output = '';
		$has_extra_details = false;

		if( $messages ){
			$output .= '<em class="cartbounty-pro-message-history-list">';
			$carts_with_highest_step_number = array();

			//Additional loop for determining the highest step value that has recovered a cart
			//so we would display just a single message responsible for recovery in the message history
			foreach( $messages as $message ){
				$cart_id = $message->cart;
				$is_recovered = !empty( $message->recovered );

				if( !isset( $carts_with_highest_step_number[$cart_id] ) || ( $is_recovered && !$carts_with_highest_step_number[$cart_id]->recovered ) || ( $is_recovered == $carts_with_highest_step_number[$cart_id]->recovered && $message->step > $carts_with_highest_step_number[$cart_id]->step ) ){
					$carts_with_highest_step_number[$cart_id] = $message;
				}
			}
			
			foreach( $messages as $key => $message ){
				$step = $message->step + 1; //Adding + 1 as step numbering starts from 0
				$time = new DateTime( $message->time );
				$additional_data = array();
				$send_failed = '';

				if( isset( $message->failed ) && !empty( $message->failed ) ){
					$additional_data['send_failed'] = '<a href="#" title="'. esc_html__( 'Send failed', 'woo-save-abandoned-carts' ) .'" aria-label="'. esc_html__( 'Send failed', 'woo-save-abandoned-carts' ) .'" class="cartbounty-pro-history-additional-details cartbounty-pro-automation-send-failed">x</a>';
					$has_extra_details = true;
				}

				if( isset( $message->unsubscribed ) && !empty( $message->unsubscribed ) ){
					$additional_data['unsubscribed'] = '<a href="#" title="'. esc_html__( 'Unsubscribed', 'woo-save-abandoned-carts' ) .'" aria-label="'. esc_html__( 'Unsubscribed', 'woo-save-abandoned-carts' ) .'" class="cartbounty-pro-history-additional-details cartbounty-pro-automation-unsubscribed">u</a>';
					$has_extra_details = true;
				}

				if( isset( $message->recovered ) && !empty( $message->recovered ) && $cart_recovered ){
					if( $carts_with_highest_step_number[$message->cart] === $message ){
						$additional_data['recovered'] = '<a href="#" title="'. esc_html__( 'Recovered', 'woo-save-abandoned-carts' ) .'" aria-label="'. esc_html__( 'Recovered', 'woo-save-abandoned-carts' ) .'" class="cartbounty-pro-history-additional-details cartbounty-pro-automation-recovered">r</a>';
						$has_extra_details = true;
					}
				}

				$additional_data = implode( '', $additional_data );
				$output .= '<i class="cartbounty-pro-message-history-item"><i class="cartbounty-pro-automation-number">'. esc_html( $step ) .'</i><i class="cartbounty-pro-message-send-time">' . esc_html( wp_date( 'M d, Y H:i', $time->getTimestamp() ) ) . '</i>' . $additional_data . '</i>';
			}
			$output .= '</em>';
		}

		$result = array(
			'data' 					=> $output,
			'has_extra_details' 	=> $has_extra_details,
		);

		return $result;
	}

	/**
     * Detect if a specific link has been clicked in abandoned cart reminder message
     * In case multiple messages in the same step have been sent (cart has been restarted) - looking at the last sent message
     *
     * @since    10.9
     * @return   boolean
     * @param    string     $recovery			Recovery option
     * @param    integer    $cart_id   			Cart ID
     * @param    integer    $step_nr			Automation step number
     */
	public function message_recovery_link_clicked( $recovery, $cart_id = false, $step_nr = false ){
		$status = false;
		$message_history = $this->get_message_history( $recovery, $cart_id, $step_nr );

		if( !empty( $message_history ) ){
			$last_message = end( $message_history );

			if( $last_message->click ){
				$status = true;
			}
		}

		return $status;
	}

	/**
     * Check if message has been sent recently. By default 2 days
     * In case multiple messages in the same step have been sent (cart has been restarted) - looking at the last sent message
     *
     * @since    10.9
     * @return   boolean
     * @param    string     $recovery			Recovery option
     * @param    integer    $cart_id   			Cart ID
     * @param    integer    $step_nr			Automation step number
     */
	public function message_sent_recently( $recovery, $cart_id = false, $step_nr = false ){
		$status = false;
		$admin = $this->admin();
		$message_history = $this->get_message_history( $recovery, $cart_id, $step_nr );

		if( !empty( $message_history ) ){
			$last_message = end( $message_history );

			if( $last_message->time ){
				$interval_data = $admin->get_interval_data( 'cartbounty_pro_main_settings[magic_login_window]' );

				if( $interval_data['selected'] == 0 ){ //If unlimited time is set, exit with positive result
					$status = true;

				}else{
					$time = $admin->get_time_intervals( $interval_data['selected'] );

					if( $last_message->time > $time['magic_login_period'] ){ //Check if message is still fresh and time has not passed
						$status = true;
					}
				}
			}
		}

		return $status;
	}

	/**
	 * Return abandoned cart stats.
	 * If a specific step number is not requested - will return total stats for all of the steps.
	 *
	 * @since    9.7
	 * @return   integer or array
	 * @param    string     $recovery			Recovery option
	 * @param    string     $action				Action type that needs to be returned
	 * @param    integer    $step_nr			Automation step number
	 * @param    boolean    $single				Whether a value for a specified step must be returned
	 */
	public function get_stats( $recovery, $action, $step_nr = false, $single = false ){
		$count = 0;

		if( $recovery == 'wordpress' ){
			switch ( $action ) {
				case 'send':
					$option = 'cartbounty_pro_automation_sends';
					break;

				case 'open':
					$option = 'cartbounty_pro_automation_opens';
					break;

				case 'click':
					$option = 'cartbounty_pro_automation_clicks';
					break;

				case 'recovered':
					$option = 'cartbounty_pro_automation_recovered';
					break;

				case 'unsubscribed':
					$option = 'cartbounty_pro_automation_unsubscribed';
					break;
			}

		}elseif( $recovery == 'bulkgate' ){
			switch ( $action ) {
				case 'send':
					$option = 'cartbounty_pro_bulkgate_sends';
					break;

				case 'click':
					$option = 'cartbounty_pro_bulkgate_clicks';
					break;

				case 'recovered':
					$option = 'cartbounty_pro_bulkgate_recovered';
					break;

				case 'unsubscribed':
					$option = 'cartbounty_pro_bulkgate_unsubscribed';
					break;
			}
		
		}elseif( $recovery == 'push_notification' ){
			switch ( $action ) {
				case 'send':
					$option = 'cartbounty_pro_push_notification_sends';
					break;

				case 'send_failed':
					$option = 'cartbounty_pro_push_notification_sends_failed';
					break;

				case 'click':
					$option = 'cartbounty_pro_push_notification_clicks';
					break;

				case 'recovered':
					$option = 'cartbounty_pro_push_notification_recovered';
					break;
			}
		}

		$message_stats = get_option( $option );

		if( $single ){ //If stats for a specific step number is requested
			if( isset( $message_stats[$step_nr] ) ){
				$count = $message_stats[$step_nr];
			}

		}else{ //Counting message stats across all automation steps
			if( is_array( $message_stats ) ){
				foreach ( $message_stats as $key => $stat ) {
					$count = $count + $stat;
				}
			}
		}

		return $count;
	}

	/**
	 * Return abandoned cart message stats in percents
	 *
	 * @since    9.7
	 * @return   string
	 * @param    string     $recovery			Recovery option
	 * @param    string     $action				Action type that needs to be returned
	 * @param    integer    $step_nr			Automation step number
	 */
	public function get_stats_percentage( $recovery, $action, $step_nr ){
		$percents = $this->calculate_rate( $action, $step_nr, $recovery );
		return number_format( ( float )$percents, $decimals = 1, '.', '' ) . '%';
	}

	/**
	 * Return message rates (open, click-through, recovery etc.) for the given step
	 *
	 * @since    9.7
	 * @return   float
	 * @param    string     $action				Action type that needs to be returned
	 * @param    integer    $step_nr			Automation step number
	 * @param    string     $recovery			Recovery option
	 */
	public function calculate_rate( $action, $step_nr, $recovery ){
		$rate = 0;
		$total_actions = $this->get_stats( $recovery, $action, $step_nr, $single = true ); //Get total clicks for the step
		$total_sends = $this->get_stats( $recovery, 'send', $step_nr, $single = true ); //Get total sends for the step

		if( $total_sends != 0 ){ //Prevent division by zero
			if( $total_sends < $total_actions ){ //If actions exceed sent message amount. Prevents from displaying results like 300%
				$total_sends = $total_actions;
			}
			$rate = $total_actions / $total_sends * 100;
		}
		return $rate;
	}

	/**
	 * Reset stats by deleting stats options
	 *
	 * @since    9.7
	 * @return   string
	 * @param    string     $recovery			Recovery option
	 */
	public function reset_stats( $recovery ){

		if( $recovery == 'wordpress' ){
			delete_option( 'cartbounty_pro_automation_sends' );
			delete_option( 'cartbounty_pro_automation_opens' );
			delete_option( 'cartbounty_pro_automation_clicks' );
			delete_option( 'cartbounty_pro_automation_recovered' );

		}elseif( $recovery == 'bulkgate' ){
			delete_option( 'cartbounty_pro_bulkgate_sends' );
			delete_option( 'cartbounty_pro_bulkgate_clicks' );
			delete_option( 'cartbounty_pro_bulkgate_recovered' );
		
		}elseif( $recovery == 'push_notification' ){
			delete_option( 'cartbounty_pro_push_notification_sends' );
			delete_option( 'cartbounty_pro_push_notification_sends_failed' );
			delete_option( 'cartbounty_pro_push_notification_clicks' );
			delete_option( 'cartbounty_pro_push_notification_recovered' );
		}

		$this->reset_recovered_stats( $recovery );
	}

	/**
	 * Reset stats by setting all recovered messages to false
	 *
	 * @since    10.6
	 * @param    string     $recovery			Recovery option
	 */
	public function reset_recovered_stats( $recovery ){
		global $wpdb;
		$table_name = $this->get_message_table_name( $recovery );

		if( !$table_name ) return;

		$wpdb->query(
			$wpdb->prepare(
				"UPDATE $table_name
				SET recovered = %d",
				0
			)
		);
	}

	/**
	* Show if coupon is enabled for a specific step 
	*
	* @since    9.7
	* @return   HTML
	* @param    string     $recovery			Recovery option
	* @param    integer    $step_nr				Automation step number
	*/
	public function display_coupon_status( $recovery, $step_nr ){
		if( $recovery == 'wordpress' ){
			$name = 'cartbounty_pro_automation_steps';

		}elseif( $recovery == 'bulkgate' ){
			$name = 'cartbounty_pro_bulkgate_steps';

		}elseif( $recovery == 'push_notification' ){
			$name = 'cartbounty_pro_push_notification_steps';
		}

		$output = '';
		$coupons = new CartBounty_Pro_Coupons();
		$coupon_enabled = $coupons->coupon_enabled( $recovery, $name, $step_nr );
		
		if( $coupon_enabled ){ //If coupon enabled
			$output = sprintf( '<div class="status-item-container"><span class="cartbounty-pro-tooltip">%s</span><span class="status coupon-active">%s</span></div>', esc_html__( 'Coupon enabled', 'woo-save-abandoned-carts' ), 'CP' );

		}
		return $output;
	}

	/**
	 * Return abandoned carts waiting in the given automation step queue.
	 * Queue is counted for both enabled and disabled steps.
	 *
	 * @since    9.7
	 * @return   string
	 * @param    string     $recovery			Recovery option
	 * @param    integer    $step_nr			Automation step number
	 */
	public function get_queue( $recovery, $step_nr = false ) {
		global $wpdb;
		$admin = $this->admin();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$time = $admin->get_time_intervals();
		$count = 0;
		$consent_query = '';

		if( $recovery == 'wordpress' ){
			$automation_steps = get_option( 'cartbounty_pro_automation_steps' );
			$field_data_field = 'email';
			$field_unsubscribed = 'wp_unsubscribed';
			$field_excluded = "wp_excluded";
			$field_complete = 'wp_complete';
			$field_steps_completed = 'wp_steps_completed';

			if( $admin->get_consent_settings( 'email' ) ){ //Check if email consent enabled to query only carts with given email usage consent
				$consent_query = 'AND email_consent = 1';
			}

		}elseif( $recovery == 'bulkgate' ){
			$automation_steps = get_option( 'cartbounty_pro_bulkgate_steps' );
			$field_data_field = 'phone';
			$field_unsubscribed = 'sms_unsubscribed';
			$field_excluded = "sms_excluded";
			$field_complete = 'sms_complete';
			$field_steps_completed = 'sms_steps_completed';

			if( $admin->get_consent_settings( 'phone' ) ){ //Check if phone consent enabled to query only carts with given phone number usage consent
				$consent_query = 'AND phone_consent = 1';
			}

		}elseif( $recovery == 'push_notification' ){
			$automation_steps = get_option( 'cartbounty_pro_push_notification_steps' );
			$field_data_field = 'pn_subscription';
			$field_unsubscribed = 'pn_subscription'; //Since we do not have unsubscribe field for push notifications, we use a the sane pn_subscription field
			$field_excluded = "pn_excluded";
			$field_complete = 'pn_complete';
			$field_steps_completed = 'pn_steps_completed';
		}

		//Retrieving all abandoned carts that are eligible for recovery
		//Excluding finished automations, unsubscribed carts and excluded carts (ones that have been manually removed from automation)
		$carts = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id
				FROM {$cart_table}
				WHERE (type = %d OR type = %d) AND
				$field_data_field != '' AND
				cart_contents != '' AND
				$field_unsubscribed != 1 AND
				paused != 1 AND
				$field_excluded != 1 AND
				$field_complete != 1 AND
				$field_steps_completed = %d AND
				anonymized != 1 AND
				time < %s AND
				time > %s
				$consent_query",
				$admin->get_cart_type('abandoned'),
				$admin->get_cart_type('recovered_pending'),
				$step_nr,
				$time['cart_abandoned'],
				$time['maximum_sync_period']
			)
		);

		foreach ( $automation_steps as $key => $step ) { //Looping through automation steps
			if( $step_nr == $key ){ //If current step must be complete
				$count = count( $carts );
			}
		}
		return $count;
	}

	/**
	* Reset automation step stats
	*
	* @since    9.4.3
	*/
	public function handle_stats_reset(){
		$handle = 'cartbounty_pro_reset_stats';

		if( isset( $_GET[$handle] ) ){ //Check if we should reset stats
			check_admin_referer( 'cartbounty-pro-reset-stats-nonce', $handle ); //Exit in case security check is not passed

			if( isset( $_GET['section'] ) ){
				$this->reset_stats( $_GET['section'] );
				wp_redirect( '?page='. esc_attr( $_REQUEST['page'] ) .'&tab='. esc_attr( $_REQUEST['tab'] ) .'&section='. esc_attr( $_REQUEST['section'] ), '303' ); //Redirect to the same page without the get parameters in the link

			}
		}
	}

	/**
	* Add click-through rate tracking URL to existing recovery link
	*
	* @since    9.4.3
	* @return   string
	* @param    string    $url					Recovery link
	* @param    integer   $step_nr				Automation step number
	* @param    string    $recovery				Recovery option
	*/
	public function add_click_through_tracking( $url, $step_nr, $recovery ){
		if( $recovery == 'wordpress' || $recovery == 'push_notification' ){
			$url = $url . '&step=' . $step_nr;
			
		}elseif( $recovery == 'bulkgate' ){ //For the purposes of keeping the link short, the last symbol of the link determines automation step
			$url = $url . $step_nr;
		}

		return $url;
	}

	/**
	 * Get dynamic content input fields
	 *
	 * @since    9.7.3
	 * @return   array
	 * @param    integer    $step_nr			Automation step number
	 * @param    array      $step				Step data
	 * @param    string     $field_name			Name of the field to retrieve
	 * @param    string     $recovery			Recovery option
	 */
	function get_content_input_fields( $step_nr, $step, $field_name, $recovery ){
		$admin = $this->admin();
		$field = array();

		if( $recovery == 'wordpress' ){
			$wordpress = $this->wordpress();
			$field['name'] = $wordpress->get_defaults( $field_name . '_name', $step_nr ); //This is necessary for WPML
			$field['value'] = $wordpress->get_defaults( $field_name, $step_nr );

		}elseif( $recovery == 'bulkgate' ){
			$bulkgate = $this->bulkgate();
			$field['name'] = $bulkgate->get_defaults( $field_name . '_name', $step_nr ); //This is necessary for WPML
			$field['value'] = $bulkgate->get_defaults( $field_name, $step_nr );
		
		}elseif( $recovery == 'push_notification' ){
			$push_notification = $this->push_notification();
			$field['name'] = $push_notification->get_defaults( $field_name . '_name', $step_nr ); //This is necessary for WPML
			$field['value'] = $push_notification->get_defaults( $field_name, $step_nr );
		}

		if( isset( $step[$field_name] ) ){
			if( trim( $step[$field_name] ) != '' ){ //If the value is not empty and does not contain only whitespaces
				$field['value'] = $admin->sanitize_field( $step[$field_name] );
			}
		}
		return $field;
	}

	/**
	 * Retrieve count of recovered carts for each step
	 *
	 * @since    10.6
	 * @return   array
	 * @param    string     $recovery			Recovery option
	 */
	function get_recovered_cart_count_for_steps( $recovery ){
		global $wpdb;
		$data = array();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$message_table_name = $this->get_message_table_name( $recovery );

		if( !$message_table_name ) return;

		$result = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT step, COUNT(*) AS recovered_carts
				FROM (
					SELECT e.cart, MAX(e.step) AS step
					FROM $message_table_name e
					JOIN $cart_table p ON e.cart = p.id
					WHERE e.recovered = %d AND
					p.type = %d
					GROUP BY e.cart
				) AS subquery
				GROUP BY step
				ORDER BY step ASC",
				1,
				1
			),
			ARRAY_A
		);

		//Prepare data for saving in the databse
		if( is_array( $result ) ){
			foreach( $result as $key => $value ){
				$data[$value['step']] = $value['recovered_carts'];
			}
		}

		return $data;
	}
}