<?php
/**
 * Broadcast Processing Controller Class
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

#[AllowDynamicProperties]
final class BWFCRM_Broadcast_Processing {
	/** Constants */
	public static $CAMPAIGN_DRAFT = 1;
	public static $CAMPAIGN_SCHEDULED = 2;
	public static $CAMPAIGN_RUNNING = 3;
	public static $CAMPAIGN_COMPLETED = 4;
	public static $CAMPAIGN_HALT = 5;
	public static $CAMPAIGN_PAUSED = 6;

	public static $CAMPAIGN_EMAIL = 1;
	public static $CAMPAIGN_SMS = 2;
	public static $CAMPAIGN_WHATSAPP = 3;

	public $CONTACT_BATCH_COUNT = 75;
	public $AS_CALL_TIME;

	public $SMS_PER_SECOND_LIMIT = 1;
	public $EMAIL_PER_SECOND_LIMIT = 10000;

	/** Class Properties */
	/** @var BWFCRM_Broadcast_Processing current_broadcast */
	private static $ins = null;

	private $started_send_mail_at = null;
	private $records_processed_in_one_sec = 0;

	private $last_engagement_sent = null;

	private $start_time = 0;

	/** @var array current_broadcast */
	private $current_broadcast = null;

	private $offset = 0;
	private $processed = 0;
	private $count = 0;
	private $daily_message_limit = 0;
	private $daily_achieved_limit = 0;
	private $contact_ids = array();
	public $stop_broadcast_required = false;
	public $stop_broadcast_for_shorten_url = false;

	public $default_email_settings = array();

	/** Email is supposed to utilise Daily Limit feature for now */
	public $supported_daily_limit_broadcast_types = array( 1 );

	private $logs = array();

	public static function get_instance() {
		if ( null === self::$ins ) {
			self::$ins = new self();
		}

		return self::$ins;
	}

	public function __construct() {
		/** Scheduler per Run time in seconds */
		$this->AS_CALL_TIME = $this->get_per_call_time();

		add_action( 'bwfcrm_broadcast_run_queue', array( $this, 'do_scheduler_work' ) );
	}

	public function do_scheduler_work() {
		/** If Sandbox mode active */
		if ( true === BWFAN_Common::is_sandbox_mode_active() ) {
			return;
		}

		$this->default_email_settings = BWFAN_Common::get_global_settings();

		/** SMS & EMail limit per second */
		$this->EMAIL_PER_SECOND_LIMIT = isset( $this->default_email_settings['bwfan_email_per_second_limit'] ) ? absint( $this->default_email_settings['bwfan_email_per_second_limit'] ) : 15;
		$this->EMAIL_PER_SECOND_LIMIT = apply_filters( 'bwfan_broadcast_email_per_second_limit', $this->EMAIL_PER_SECOND_LIMIT );
		$this->SMS_PER_SECOND_LIMIT   = apply_filters( 'bwfan_broadcast_sms_per_second_limit', $this->SMS_PER_SECOND_LIMIT );

		$this->start_time = time();

		/** Process scheduled broadcasts, make them running */
		$this->process_scheduled_broadcasts();
		if ( $this->is_time_exceeded() ) {
			return;
		}

		/** Process running broadcasts */
		$this->process_running_broadcasts();
	}

	public function process_scheduled_broadcasts() {
		$scheduled = BWFAN_Model_Broadcast::get_scheduled_broadcasts_to_run();
		if ( ! is_array( $scheduled ) || empty( $scheduled ) ) {
			return;
		}

		foreach ( $scheduled as $broadcast ) {
			if ( $this->is_time_exceeded() ) {
				break;
			}

			$this->process_single_scheduled_broadcasts( $broadcast );
		}
	}

	public function process_single_scheduled_broadcasts( $broadcast ) {
		$args = $this->get_broadcast_query_args( $broadcast );

		$parentBroadcast = isset( $broadcast['parent'] ) ? absint( $broadcast['parent'] ) : false;

		/** Fetching last contact ID of a broadcast from the given filters */
		if ( ! empty( $parentBroadcast ) ) {
			$args['props']['limit']          = 1;
			$args['props']['offset']         = 0;
			$args['props']['grab_totals']    = true;
			$args['props']['exclude_unsubs'] = isset( $args['data']['is_promotional'] ) && 1 === intval( $args['data']['is_promotional'] );
			$contacts_array                  = BWFCRM_Core()->campaigns->get_unopen_broadcast_contacts( absint( $parentBroadcast ), $args['props'], OBJECT );
		} else {

			if ( ! isset( $args['props']['exclude_unsubs'] ) || false === $args['props']['exclude_unsubs'] ) {
				$args['props']['exclude_unsubs'] = apply_filters( 'bwfan_force_exclude_unsubscribe_contact', false );
			}
			$contacts_array = BWFCRM_Contact::get_contacts( '', 0, 1, $args['filters'], $args['props'], OBJECT );
		}
		$contacts       = $contacts_array['contacts'];
		$total_contacts = absint( $contacts_array['total_count'] );
		$last_contact   = is_array( $contacts ) && count( $contacts ) > 0 && $contacts[0] instanceof BWFCRM_Contact ? $contacts[0]->get_id() : 0;

		if ( $total_contacts > 0 && absint( $last_contact ) > 0 ) {
			/** Increment the last contact ID by 1, to always get the contacts with IDs less than ($last_contact+1)  */
			$last_contact            = absint( $last_contact ) + 1;
			$args['data']['content'] = $this->get_or_create_multiple_templates( $args['data']['content'], $args['props']['fetch_base'], true );
			BWFAN_Model_Broadcast::update_status_to_run( $broadcast['id'], $last_contact, $args['data'], $total_contacts );

			return;
		}

		$args['data']['halt_reason'] = __( 'No contacts found for this broadcast', 'wp-marketing-automations-pro' );
		BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $args['data'] );
	}

	public function get_or_create_multiple_templates( $emails, $mode = 1, $return_with_contents = false ) {
		if ( ! is_array( $emails ) ) {
			return array();
		}

		$result = array();
		foreach ( $emails as $email ) {
			/** Set template mode 1 - rich, 3 - html 4 - editor */
			$template_type = BWFAN_PRO_Common::get_email_template_type( $email['type'] );
			$data          = array( 'template' => $template_type );
			if ( isset( $email['whatsAppImage'] ) && $email['whatsAppImage'] ) {
				if ( isset( $email['whatsAppImageSetting'] ) && ! empty( $email['whatsAppImageSetting'] ) ) {
					$data = $email['whatsAppImageSetting'];
				}
			}
			$editor_body   = isset( $email['editor'] ) && is_array( $email['editor'] ) && isset( $email['editor']['body'] ) ? $email['editor']['body'] : '';
			$template_body = isset( $email['type'] ) && 'editor' === $email['type'] ? $editor_body : $email['body'];
			$template_body = method_exists( 'BWFAN_Common', 'correct_shortcode_string' ) ? BWFAN_Common::correct_shortcode_string( $template_body, $email['type'] ) : $template_body;
			if ( 5 === intval( $template_type ) ) {
				include_once BWFAN_PRO_PLUGIN_DIR . '/crm/includes/class-bwfcrm-block-editor.php';
				if ( method_exists( 'BWFCRM_Block_Editor', 'decode_specific_blocks_before' ) ) {
					$template_body = BWFCRM_Block_Editor::decode_specific_blocks_before( $template_body, [ 'bwfbe_multi_product', 'bwfbe_coupon' ] );
				}
			}
			$template_id   = BWFCRM_Core()->conversation->get_or_create_template( $mode, 1 === $mode && isset( $email['subject'] ) ? $email['subject'] : '', $template_body, 'id', $data );
			if ( true === $return_with_contents ) {
				$email['template_id'] = $template_id;
				$result[]             = $email;
			} else {
				$result[] = $template_id;
			}
		}

		return $result;
	}

	public function process_running_broadcasts() {
		$this->set_log( 'processing running broadcast' );
		$broadcasts = BWFAN_Model_Broadcast::get_broadcasts_by_status( self::$CAMPAIGN_RUNNING, true );
		if ( ! is_array( $broadcasts ) || empty( $broadcasts ) ) {
			return;
		}

		do_action( 'bwfan_before_executing_broadcast', $broadcasts );
		$this->before_executing_broadcast();

		$this->set_log( count( $broadcasts ) . ' broadcasts found' );

		$this->started_send_mail_at = microtime( true );

		foreach ( $broadcasts as $broadcast ) {
			/** Set delay if daily limit reached and set daily limit count */
			if ( $this->maybe_daily_limit_reached( $broadcast['type'] ) ) {
				BWFAN_Model_Broadcast::set_delay( $broadcast['id'] );

				continue;
			}

			/** Store current broadcast for class-wide access */
			$this->current_broadcast = $broadcast;

			/** Break, if current call request time exceeded */
			if ( $this->is_time_exceeded() ) {
				break;
			}

			/** Check if broadcast is valid */
			if ( ! $this->is_broadcast_valid() ) {
				continue;
			}

			/**
			 * Update last_modified to inform subsequent requests
			 */
			$last_modified_time = current_time( 'mysql', 1 );
			BWFAN_Model_Broadcast::update( array( 'last_modified' => $last_modified_time ), array( 'id' => absint( $broadcast['id'] ) ) );
			$broadcast['last_modified']               = $last_modified_time;
			$this->current_broadcast['last_modified'] = $last_modified_time;

			$data = isset( $broadcast['data'] ) && ! empty( $broadcast['data'] ) ? $broadcast['data'] : array();

			$this->processed = absint( $broadcast['processed'] );
			$this->offset    = absint( $broadcast['offset'] );
			$this->count     = absint( $broadcast['count'] );

			do {
				$this->set_log( 'broadcast_id' . $broadcast['id'] . '_before_fetch_contacts' );
				$this->fetch_contacts_for_broadcast();

				if ( $this->error_while_fetching_contacts() ) {
					break;
				}

				if ( true === $this->stop_broadcast_required ) {
					$this->stop_broadcast_required = false;

					break;
				}

				if ( empty( $this->contact_ids ) ) {
					BWFAN_Model_Broadcast::update_status_to_complete( $broadcast['id'], $data, $this->processed, $this->count );
					break;
				}

				$this->process_single_ongoing_broadcast();
				/** Continue to next broadcast, in case needed to end current broadcast process */
				if ( true === $this->stop_broadcast_required ) {
					$this->stop_broadcast_required = false;

					break;
				}

				if ( $this->is_time_exceeded() ) {
					BWFAN_Model_Broadcast::update( array(
						'last_modified' => date( 'Y-m-d H:i:s', time() - 6 ),
					), array(
						'id' => absint( $broadcast['id'] ),
					) );
					BWFCRM_Common::reschedule_broadcast_action();
				}
			} while ( ! $this->is_time_exceeded() && ! BWFCRM_Common::memory_exceeded() );
		}

		$this->set_log( 'broadcasts_loop_end' );
		$this->log();
	}

	public function is_broadcast_valid() {
		$broadcast = $this->current_broadcast;
		$data      = $this->get_current_broadcast_data();

		/** SMS Validation */
		if ( self::$CAMPAIGN_SMS === absint( $broadcast['type'] ) ) {
			$provider = BWFCRM_Common::get_sms_provider_integration();
			if ( is_wp_error( $provider ) ) {
				$error_message       = $provider->get_error_message();
				$data['halt_reason'] = ! empty( $error_message ) ? $error_message : __( 'Invalid SMS settings', 'wp-marketing-automations-pro' );
				BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );

				return false;
			}
		}

		/** WhatsApp Validation */
		if ( self::$CAMPAIGN_WHATSAPP === absint( $broadcast['type'] ) && false === $this->maybe_validate_whatsapp_settings() ) {
			return false;
		}

		/** Broadcast Content Validation */
		$broadcast_content = $data['content'];
		if ( empty( $broadcast_content ) || ! is_array( $broadcast_content ) ) {
			$data['halt_reason'] = __( 'Broadcast content not found', 'wp-marketing-automations-pro' );
			BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );

			return false;
		}

		/** Validate broadcast might be running */
		if ( true === $this->maybe_broadcast_running() ) {
			return false;
		}

		return true;
	}

	public function fetch_contacts_for_broadcast() {
		$broadcast = $this->current_broadcast;

		$args = $this->get_broadcast_query_args( $broadcast );

		$args['props']['end_id']         = absint( $this->offset );
		$args['props']['exclude_end_id'] = true;

		/** Get Contacts */
		$parentBroadcast = isset( $broadcast['parent'] ) ? absint( $broadcast['parent'] ) : false;
		if ( ! empty( $parentBroadcast ) ) {
			$args['props']['limit']          = $this->CONTACT_BATCH_COUNT;
			$args['props']['offset']         = 0;
			$args['props']['grab_totals']    = true;
			$args['props']['exclude_unsubs'] = isset( $args['data']['is_promotional'] ) && 1 === intval( $args['data']['is_promotional'] );
			$contacts_array                  = BWFCRM_Core()->campaigns->get_unopen_broadcast_contacts( absint( $parentBroadcast ), $args['props'], OBJECT );
		} else {
			if ( ! isset( $args['props']['exclude_unsubs'] ) || false === $args['props']['exclude_unsubs'] ) {
				$args['props']['exclude_unsubs'] = apply_filters( 'bwfan_force_exclude_unsubscribe_contact', false );
			}
			$contacts_array = BWFCRM_Contact::get_contacts( '', 0, $this->CONTACT_BATCH_COUNT, $args['filters'], $args['props'], OBJECT );
		}

		if ( ! is_array( $contacts_array ) || ! isset( $contacts_array['contacts'] ) || ! is_array( $contacts_array['contacts'] ) ) {
			$this->contact_ids = BWFCRM_Common::crm_error( __( 'Invalid broadcast contact query results', 'wp-marketing-automations-pro' ) );
			$this->set_log( 'broadcast_id' . $broadcast['id'] . '_no_query_result' );

			return;
		}

		/** Get IDs From Contacts */
		$this->contact_ids = array_map( function ( $contact ) {
			return $contact->get_id();
		}, $contacts_array['contacts'] );

		/** Adding failsafe to avoid multiple broadcast processing on the same contact */
		/** getting contacts ids in desc order */
		arsort( $this->contact_ids );
		/** First id to process is */
		$first_id = current( $this->contact_ids );

		if ( $first_id >= $this->offset ) {
			/** If first contact already processed, empty the contact array */
			$this->set_log( 'broadcast_id' . $broadcast['id'] . ' has data but contact already processed' );

			$this->stop_broadcast_required = true;

			return;
		}

		$this->set_log( 'broadcast_id' . $broadcast['id'] . ' - IDs: ' . implode( ',', $this->contact_ids ) );

		return;
	}

	protected function get_broadcast_query_args( $broadcast ) {
		if ( ! is_array( $broadcast ) || empty( $broadcast ) ) {
			return false;
		}

		$data = isset( $broadcast['data'] ) && ! empty( $broadcast['data'] ) ? $broadcast['data'] : false;
		if ( ! is_array( $data ) && ! empty( $data ) ) {
			$data = json_decode( $data, true );
		}

		$exclude = ! empty( $data ) && isset( $data['exclude'] ) && is_array( $data['exclude'] ) ? $data['exclude'] : array();
		$exclude = array_map( 'absint', $exclude );

		$parentBroadcast = isset( $broadcast['parent'] ) ? absint( $broadcast['parent'] ) : false;
		if ( ! empty( $parentBroadcast ) ) {
			return array(
				'filters' => array(),
				'data'    => $data,
				'props'   => array(
					'order'       => 'DESC',
					'exclude_ids' => $exclude,
					'fetch_base'  => 1,
				),
			);
		}

		$filters              = ! empty( $data ) && isset( $data['filters'] ) ? $data['filters'] : array();
		$filters['status_is'] = 1;

		$mode = isset( $broadcast['type'] ) ? absint( $broadcast['type'] ) : 1;

		/** Fetch Base must SMS (Phone Number), if mode is WhatsApp */
		$mode = self::$CAMPAIGN_WHATSAPP === $mode ? self::$CAMPAIGN_SMS : $mode;

		return array(
			'filters' => $filters,
			'data'    => $data,
			'props'   => array(
				'fetch_base'           => $mode,
				'order_by'             => 'id',
				'order'                => 'DESC',
				'grab_totals'          => true,
				'exclude_unsubs_lists' => isset( $filters['lists_any'] ),
				'exclude_ids'          => $exclude,
				'include_soft_bounce'  => ! empty( $data['includeSoftBounce'] ),
				'include_unverified'   => ! empty( $data['includeUnverified'] ),
				'include_unsubscribe'  => empty( $data['is_promotional'] ),
			),
		);
	}

	public function error_while_fetching_contacts() {
		if ( ! $this->contact_ids instanceof WP_Error ) {
			return false;
		}

		$data                = $this->get_current_broadcast_data();
		$data['halt_reason'] = $this->contact_ids->get_error_message();
		BWFAN_Model_Broadcast::update_status_to_halt( $this->current_broadcast['id'], $data );

		return true;
	}

	public function get_per_second_limit() {
		/** Email */
		if ( self::$CAMPAIGN_EMAIL === absint( $this->current_broadcast['type'] ) ) {
			return absint( $this->EMAIL_PER_SECOND_LIMIT );
		}
		/** SMS */
		if ( self::$CAMPAIGN_SMS === absint( $this->current_broadcast['type'] ) ) {
			return absint( $this->SMS_PER_SECOND_LIMIT );
		}

		/** Not checking WhatsApp here */
		return false;
	}

	public function process_single_ongoing_broadcast() {
		$broadcast = $this->current_broadcast;
		$data      = $this->get_current_broadcast_data();

		$per_second_limit = $this->get_per_second_limit();
		foreach ( $this->contact_ids as $id ) {
			$this->set_log( 'contact#' . $id . '_start' );
			if ( $this->is_time_exceeded() || BWFCRM_Common::memory_exceeded() ) {
				$this->set_log( 'contact#' . $id . '_time exceeded' );
				break;
			}

			/**
			 *  Fallback, in case previously processed ID gets into loop again,
			 *  where offset must be old, and id must be new.
			 */
			if ( $this->offset <= $id ) {
				$this->set_log( 'contact#' . $id . ' moving to next contact ' . $this->offset . ' = ' . $id );
				continue;
			}

			/**
			 * If broadcast support daily limit, check that
			 * currently email type supports daily limit
			 * if daily limit reached then delay and end current broadcast
			 */
			$daily_limit_supported = in_array( absint( $broadcast['type'] ), $this->supported_daily_limit_broadcast_types );
			if ( $daily_limit_supported && $this->daily_achieved_limit >= $this->daily_message_limit ) {
				BWFAN_Model_Broadcast::set_delay( $broadcast['id'] );
				$this->stop_broadcast_required = true;

				return;
			}

			/** Hold for one second to complete, if per second limit achieved */
			if ( ! empty( $per_second_limit ) && $per_second_limit <= $this->records_processed_in_one_sec ) {
				$this->set_log( 'contact#' . $id . '_per_second_limit_reached' );
				$this->delay_time();
			}

			/**
			 * Check if Smart sending is enabled
			 * Maybe need to stop broadcast in case contacts processed
			 * If nothing returned, continue because smart sending is not enabled
			 * If hold returned, stop the broadcast
			 */
			$smart_send = $this->maybe_process_smart_send_data( $broadcast['id'], $data, $this->processed, $this->count, absint( $broadcast['type'] ) );
			if ( 'hold' === $smart_send ) {
				$this->stop_broadcast_required = true;

				return;
			} elseif ( ! empty( $smart_send ) ) {
				$data['winner']    = absint( $smart_send );
				$broadcast['data'] = $data;
			}

			if ( self::$CAMPAIGN_SMS === absint( $broadcast['type'] ) ) {
				$this->process_broadcast_contact_sms( $broadcast, $id );
			} elseif ( self::$CAMPAIGN_WHATSAPP === absint( $broadcast['type'] ) ) {
				$this->whatsapp_delay_time();
				$this->process_broadcast_contact_whatsapp( $broadcast, $id );
			} else {
				$this->process_broadcast_contact_email( $broadcast, $id );
			}

			if ( true === $this->stop_broadcast_for_shorten_url ) {
				return;
			}

			/** set last engagement time */
			$this->last_engagement_sent = microtime( true );

			$this->records_processed_in_one_sec ++;
			$this->processed ++;
			$this->daily_achieved_limit ++;
			$this->offset = $id;

			BWFAN_Model_Broadcast::update_campaign_offsets( $broadcast['id'], $this->offset, $this->processed );

			if ( true === $this->stop_broadcast_required ) {
				return;
			}
		}
	}

	/**
	 * @param $broadcast
	 * @param $contact_id
	 *
	 * @return false|void
	 */
	public function process_broadcast_contact_whatsapp( $broadcast, $contact_id ) {
		$contact = new BWFCRM_Contact( absint( $contact_id ) );
		if ( ! $contact->is_contact_exists() ) {
			return;
		}
		$this->set_log( 'contact#' . $contact_id . '_process_whatsapp_message_start' );

		$to = BWFAN_PRO_Common::get_contact_full_number( $contact->contact );
		if ( empty( $to ) ) {
			$this->set_log( 'contact#' . $contact_id . '_no_phone_number_whatsapp' );

			return;
		}

		$data = $broadcast['data'];

		$broadcast_id        = absint( $broadcast['id'] );
		$broadcast_author_id = absint( $broadcast['created_by'] );
		$broadcast_content   = $data['content'];

		$template_winner = isset( $data['winner'] ) ? absint( $data['winner'] ) : 0;
		$chosen_content  = $this->get_current_email_template( $broadcast_content, $this->processed, $template_winner );

		$message_data = BWFCRM_Core()->conversation->create_campaign_conversation( $contact, $broadcast_id, $chosen_content['template_id'], $broadcast_author_id, BWFCRM_Campaigns::$CAMPAIGN_WHATSAPP );
		if ( is_wp_error( $message_data ) ) {
			$e_data = $message_data->get_error_data();
			if ( is_array( $e_data ) && isset( $e_data['conversation_id'] ) ) {
				BWFCRM_Core()->conversation->fail_the_conversation( absint( $e_data['conversation_id'] ), $message_data->get_error_message() );
			}

			return;
		}

		if ( ! isset( $message_data['conversation_id'] ) ) {
			$this->set_log( 'contact#' . $contact_id . '_conversation_id not found' );

			return;
		}
		$conversation_id = absint( $message_data['conversation_id'] );
		$hash_code       = isset( $message_data['hash_code'] ) ? $message_data['hash_code'] : '';
		$template        = isset( $message_data['template'] ) ? $message_data['template'] : '';
		$template_data   = isset( $message_data['template_data'] ) ? $message_data['template_data'] : array();
		$utm_details     = isset( $chosen_content['utm'] ) && is_array( $chosen_content['utm'] ) ? $this->get_utm_data_from_campaign_data( $chosen_content['utm'] ) : '';

		$message_body = BWFCRM_Core()->conversation->prepare_sms_body( $conversation_id, $contact_id, $hash_code, $template, $utm_details, $broadcast_id );
		if ( true === $this->stop_broadcast_for_shorten_url ) {
			return false;
		}
		$textdata = array(
			'type' => 'text',
			'data' => $message_body,
		);
		$message  = array( $textdata );
		if ( ! empty( $template_data ) && isset( $template_data['position'] ) && isset( $template_data['imageURL'] ) && ! empty( $template_data['position'] ) && ! empty( $template_data['imageURL'] ) ) {
			$imagedata = array(
				'type' => 'image',
				'data' => $template_data['imageURL'],
			);

			if ( $template_data['position'] == 'before' ) {
				$message = array( $imagedata, $textdata );
			} else {
				$message = array( $textdata, $imagedata );
			}
		}

		$response = BWFCRM_Core()->conversation->send_whatsapp_message( $to, $message, $utm_details );
		if ( is_array( $response ) && $response['status'] == true ) {
			BWFCRM_Core()->conversation->update_conversation_status( $conversation_id, BWFAN_Email_Conversations::$STATUS_SEND );

			return;
		}

		$error_message = __( 'Message could not be sent. ', 'wp-marketing-automations-pro' );

		if ( isset( $response['msg'] ) && ! empty( $response['msg'] ) ) {
			$error_message = $response['msg'];
		}

		BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, $error_message );
	}

	public function process_broadcast_contact_sms( $broadcast, $contact_id ) {
		$contact = new BWFCRM_Contact( absint( $contact_id ) );
		if ( ! $contact->is_contact_exists() ) {
			return false;
		}

		$data = $broadcast['data'];

		$broadcast_id        = absint( $broadcast['id'] );
		$broadcast_author_id = absint( $broadcast['created_by'] );
		$broadcast_content   = $data['content'];
		if ( empty( $broadcast_content ) || ! is_array( $broadcast_content ) ) {
			return false;
		}

		$template_winner = isset( $data['winner'] ) ? absint( $data['winner'] ) : 0;
		$chosen_content  = $this->get_current_email_template( $broadcast_content, $this->processed, $template_winner );
		$sms_data        = BWFCRM_Core()->conversation->create_campaign_conversation( $contact, $broadcast_id, $chosen_content['template_id'], $broadcast_author_id, BWFCRM_Campaigns::$CAMPAIGN_SMS );
		if ( is_wp_error( $sms_data ) ) {
			$e_data = $sms_data->get_error_data();
			if ( is_array( $e_data ) && isset( $e_data['conversation_id'] ) ) {
				BWFCRM_Core()->conversation->fail_the_conversation( absint( $e_data['conversation_id'] ), $sms_data->get_error_message() );
			}

			return false;
		}

		if ( ! isset( $sms_data['conversation_id'] ) ) {
			return false;
		}

		$conversation_id = absint( $sms_data['conversation_id'] );
		$hash_code       = isset( $sms_data['hash_code'] ) ? $sms_data['hash_code'] : '';
		$template        = isset( $sms_data['template'] ) ? $sms_data['template'] : '';
		$utm_details     = isset( $chosen_content['utm'] ) && is_array( $chosen_content['utm'] ) ? $this->get_utm_data_from_campaign_data( $chosen_content['utm'] ) : '';
		$sms_body        = BWFCRM_Core()->conversation->prepare_sms_body( $conversation_id, $contact_id, $hash_code, $template, $utm_details, $broadcast_id );
		if ( true === $this->stop_broadcast_for_shorten_url ) {
			return false;
		}
		if ( is_wp_error( $sms_body ) ) {
			BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, $sms_body->get_error_message() );

			return false;
		}

		$to = BWFAN_PRO_Common::get_contact_full_number( $contact->contact );

		if ( empty( $to ) ) {
			BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, __( 'No phone number found for this contact: ' . $contact_id, 'wp-marketing-automations-pro' ) );

			return false;
		}

		$send_sms_result = BWFCRM_Common::send_sms( array(
			'to'   => $to,
			'body' => $sms_body
		) );

		if ( is_wp_error( $send_sms_result ) ) {
			BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, $send_sms_result->get_error_message() );

			return false;
		}

		/** Save the time of last sent engagement **/
		$data = array( 'cid' => $contact_id );
		BWFCRM_Conversation::save_last_sent_engagement( $data );

		return BWFCRM_Core()->conversation->update_conversation_status( $conversation_id, BWFAN_Email_Conversations::$STATUS_SEND );
	}

	public function process_broadcast_contact_email( $broadcast, $contact_id ) {
		$contact = new BWFCRM_Contact( absint( $contact_id ) );
		if ( ! $contact->is_contact_exists() ) {
			return;
		}

		$data = $broadcast['data'];

		$broadcast_id        = absint( $broadcast['id'] );
		$broadcast_author_id = absint( $broadcast['created_by'] );
		$broadcast_content   = $data['content'];
		$template_winner     = isset( $data['winner'] ) ? absint( $data['winner'] ) : 0;
		$chosen_content      = $this->get_current_email_template( $broadcast_content, $this->processed, $template_winner );
		$content_template    = array(
			'template' => 'editor' === $chosen_content['type'] ? $chosen_content['editor']['body'] : $chosen_content['body'],
			'subject'  => $chosen_content['subject'],
			'type'     => $chosen_content['type']
		);

		$email_data = BWFCRM_Core()->conversation->create_campaign_conversation( $contact, $broadcast_id, $chosen_content['template_id'], $broadcast_author_id, BWFCRM_Campaigns::$CAMPAIGN_EMAIL, false, $content_template );

		if ( true === $this->validate_email_data_error( $email_data ) ) {
			return;
		}

		if ( ! isset( $email_data['conversation_id'] ) ) {
			return;
		}

		$conversation_id = absint( $email_data['conversation_id'] );
		$hash_code       = isset( $email_data['hash_code'] ) ? $email_data['hash_code'] : '';
		$template_type   = isset( $chosen_content['type'] ) ? $chosen_content['type'] : '';
		$email_subject   = isset( $email_data['subject'] ) ? $email_data['subject'] : '';
		$template        = isset( $email_data['template'] ) ? $email_data['template'] : '';
		$pre_header      = isset( $chosen_content['preheader'] ) ? $chosen_content['preheader'] : '';
		$utm_details     = isset( $chosen_content['utm'] ) && is_array( $chosen_content['utm'] ) ? $this->get_utm_data_from_campaign_data( $chosen_content['utm'] ) : '';

		$email_subject = BWFCRM_Core()->conversation->prepare_email_subject( $email_subject, $contact_id );
		try {
			$email_body = BWFCRM_Core()->conversation->prepare_email_body( $conversation_id, $contact_id, $hash_code, $template_type, $template, $pre_header, $utm_details, $broadcast_id );
		} catch ( Error $e ) {
			/** Update broadcast status to halt if any error comes in email body */
			$data['halt_reason'] = __( 'Error in email body: ', 'wp-marketing-automations-pro' ) . $e->getMessage();
			BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );
			die();
		}
		if ( is_wp_error( $email_body ) ) {
			BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, $email_body->get_error_message() );

			return;
		}

		$to = $contact->contact->get_email();
		if ( ! is_email( $to ) ) {
			BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, __( 'No email found for this contact: ' . $contact_id, 'wp-marketing-automations-pro' ) );

			return;
		}

		$headers = array(
			'MIME-Version: 1.0',
			'From: ' . $data['senders_name'] . ' <' . $data['senders_email'] . '>',
			'Content-type: text/html;charset=UTF-8',
		);

		if ( isset( $data['replyto'] ) && ! empty( $data['replyto'] ) ) {
			$headers[] = 'Reply-To:  ' . $data['replyto'];
		}

		/** Set unsubscribe link in header */
		$unsubscribe_link = BWFAN_PRO_Common::get_unsubscribe_link( [ 'uid' => $contact->contact->get_uid(), 'broadcast_id' => $broadcast_id ] );
		if ( ! empty( $unsubscribe_link ) ) {
			$headers[] = "List-Unsubscribe: <$unsubscribe_link>";
			$headers[] = "List-Unsubscribe-Post: List-Unsubscribe=One-Click";
		}

		$this->set_log( 'contact#' . $contact_id . '_before_wp_mail' );

		do_action( 'bwfan_before_send_email', $data, $email_body );
		/** Process the email */
		$result = $this->process_email( $to, $email_subject, $email_body, $headers );

		$this->set_log( 'contact#' . $contact_id . '_after_wp_mail' );

		if ( true === $result ) {
			BWFCRM_Core()->conversation->update_conversation_status( $conversation_id, BWFAN_Email_Conversations::$STATUS_SEND );

			$data = array( 'cid' => $contact_id );
			BWFCRM_Conversation::save_last_sent_engagement( $data );

			return;
		}

		$error = BWFCRM_Common::maybe_get_failed_mail_error();
		$error = ! empty( $error ) ? $error : __( 'Email not sent (Error Unknown)', 'wp-marketing-automations-pro' );
		BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, $error );
	}

	public function maybe_process_smart_send_data( $broadcast_id, $broadcast_data, $processed, $count, $type = 1 ) {
		/** Return false if smart sending not enabled */
		$smart_send_settings   = is_array( $broadcast_data ) && isset( $broadcast_data['smart_send'] ) ? $broadcast_data['smart_send'] : false;
		$is_smart_send_enabled = is_array( $smart_send_settings ) && isset( $smart_send_settings['enable'] ) && true === $smart_send_settings['enable'];
		if ( ! $is_smart_send_enabled ) {
			return false;
		}

		/** Return winner if already declared */
		$winner = is_array( $broadcast_data ) && isset( $broadcast_data['winner'] ) && ! empty( $broadcast_data['winner'] ) ? absint( $broadcast_data['winner'] ) : false;
		if ( $is_smart_send_enabled && ! empty( $winner ) ) {
			return absint( $winner );
		}

		/** Return if smart sending process already over */
		$is_smart_send_finished = is_array( $broadcast_data ) && isset( $broadcast_data['smart_sending_done'] ) && 1 === absint( $broadcast_data['smart_sending_done'] );
		if ( $is_smart_send_enabled && $is_smart_send_finished ) {
			return false;
		}

		/** Return false, if percent is not achieved yet */
		$processed        = ! empty( $processed ) ? absint( $processed ) : 0;
		$percent          = $is_smart_send_enabled && isset( $smart_send_settings['percent'] ) && ! empty( $smart_send_settings['percent'] ) ? absint( $smart_send_settings['percent'] ) : 0;
		$percent_achieved = $is_smart_send_enabled ? absint( ( $processed / absint( $count ) ) * 100 ) : 0;
		if ( empty( $percent ) || $percent_achieved < $percent ) {
			return false;
		}

		/** Return 'hold', if percent is achieved, and need to wait for the most opens or clicked template */
		$hold_broadcast = is_array( $broadcast_data ) && isset( $broadcast_data['hold_broadcast'] );
		if ( ! $hold_broadcast ) {
			$hours = $is_smart_send_enabled && isset( $smart_send_settings['hours'] ) ? absint( $smart_send_settings['hours'] ) : 0;
			BWFAN_Model_Broadcast::hold_broadcast_for_smart_send( $broadcast_id, $hours );

			return 'hold';
		}

		/** Return false, if unable to get the Winner */
		$db_winner = BWFAN_Model_Engagement_Tracking::get_most_interacted_template( absint( $broadcast_id ), BWFAN_Email_Conversations::$TYPE_CAMPAIGN, absint( $type ) );
		if ( ! is_array( $db_winner ) || empty( $db_winner['tid'] ) ) {
			BWFAN_Model_Broadcast::remove_hold_flag_smart_sending( absint( $broadcast_id ) );

			return false;
		}

		/** Return & declare winner */
		$winner = absint( $db_winner['tid'] );
		BWFAN_Model_Broadcast::declare_the_winner_email( absint( $broadcast_id ), $winner );

		return $winner;
	}

	public function get_current_email_template( $email_content, $processed, $winner_tid ) {
		if ( ! empty( $winner_tid ) ) {
			foreach ( $email_content as $email ) {
				if ( ! is_array( $email ) || ! isset( $email['template_id'] ) || absint( $email['template_id'] ) !== absint( $winner_tid ) ) {
					continue;
				}

				return $email;
			}
		}

		return $email_content[ $processed % count( $email_content ) ];
	}

	public function get_utm_data_from_campaign_data( $utm_data ) {
		$final_data = array();
		$utm_keys   = array(
			'source'  => 'utm_source',
			'medium'  => 'utm_medium',
			'name'    => 'utm_campaign',
			'content' => 'utm_content',
			'term'    => 'utm_term',
		);

		foreach ( $utm_data as $key => $datum ) {
			if ( ! isset( $utm_keys[ $key ] ) ) {
				continue;
			}

			$final_data[ $utm_keys[ $key ] ] = $datum;
		}

		return $final_data;
	}

	/**
	 * Add gap between whatsapp messages
	 */
	public function whatsapp_delay_time() {
		if ( is_null( $this->last_engagement_sent ) ) {
			return;
		}

		$gap             = 1;
		$global_settings = BWFAN_Common::get_global_settings();
		if ( isset( $global_settings['bwfan_whatsapp_gap_btw_message'] ) && ! empty( $global_settings['bwfan_whatsapp_gap_btw_message'] ) ) {
			$gap = floatval( $global_settings['bwfan_whatsapp_gap_btw_message'] );
		}
		$sleep_time = $gap * 1000 * 1000;

		/** Get Remaining sleep time, in Micro-Seconds */
		$sleep_time = $sleep_time - ( ( microtime( true ) - $this->last_engagement_sent ) * 1000000 );

		if ( $sleep_time > 0 && $sleep_time < 1000000 ) {
			usleep( ceil( $sleep_time ) );
		}

		if ( $sleep_time >= 1000000 ) {
			$gap = $sleep_time / 1000000;
			sleep( ceil( $gap ) );
		}
	}

	public function delay_time() {
		$sleep_time = 1000000 - ( microtime( true ) - $this->started_send_mail_at ) * 1000000;
		if ( $sleep_time > 0 ) {
			usleep( ceil( $sleep_time ) );
		}

		$this->records_processed_in_one_sec = 0;
		$this->started_send_mail_at         = microtime( true );
	}

	/**
	 * Check if email daily limit reached
	 *
	 * @param int $type 1 email 2 sms 3 whatsapp
	 *
	 * @return bool
	 */
	public function maybe_daily_limit_reached( $type = 1 ) {
		if ( empty( $this->default_email_settings ) ) {
			$this->default_email_settings = BWFAN_Common::get_global_settings();
		}

		/** If not email campaign, no limit */
		if ( self::$CAMPAIGN_EMAIL !== absint( $type ) ) {
			return false;
		}

		$this->daily_achieved_limit = ( 0 === absint( $this->daily_achieved_limit ) ) ? BWFAN_Model_Engagement_Tracking::get_last_24_hours_conversations_count() : absint( $this->daily_achieved_limit );
		$this->daily_message_limit  = isset( $this->default_email_settings['bwfan_email_daily_limit'] ) ? absint( $this->default_email_settings['bwfan_email_daily_limit'] ) : 10000;

		/** Setting default 10k email daily limit */
		$this->daily_message_limit = empty( $this->daily_message_limit ) ? 10000 : $this->daily_message_limit;

		return ( $this->daily_achieved_limit >= $this->daily_message_limit );
	}

	/**
	 * Set delay in broadcast execution
	 *
	 * @param $campaign
	 * @param string $time
	 * @param string $in
	 */
	public function set_delay( $campaign, $time = '10', $in = 'minutes' ) {
		$execution_time = date( 'Y-m-d H:i:s', strtotime( "+$time $in", strtotime( $campaign['execution_time'] ) ) );
		BWFAN_Model_Broadcast::update( array(
			'execution_time' => $execution_time,
			'last_modified'  => current_time( 'mysql', 1 ),
		), array(
			'id' => $campaign['id'],
		) );
	}

	public function get_daily_limit_status_array() {
		if ( empty( $this->default_email_settings ) ) {
			$this->default_email_settings = BWFAN_Common::get_global_settings();
		}

		return array(
			'reached'     => $this->maybe_daily_limit_reached(),
			'daily_limit' => isset( $this->default_email_settings['bwfan_email_daily_limit'] ) ? absint( $this->default_email_settings['bwfan_email_daily_limit'] ) : 10000,
		);
	}

	public function get_per_call_time() {
		if ( defined( 'BWFCRM_BROADCAST_AS_CALL_SECONDS' ) ) {
			return absint( BWFCRM_BROADCAST_AS_CALL_SECONDS );
		}

		return apply_filters( 'bwfan_as_per_call_time', 30 );
	}

	public function is_time_exceeded() {
		return ( time() - $this->start_time ) >= $this->AS_CALL_TIME;
	}

	public function maybe_validate_twilio_settings() {
		$broadcast = $this->current_broadcast;
		$data      = isset( $broadcast['data'] ) && ! empty( $broadcast['data'] ) ? $broadcast['data'] : array();

		if ( ! BWFCRM_Core()->conversation->is_twilio_connected() ) {
			$data['halt_reason'] = __( 'Twilio Connector is not connected, during the schedule time of this campaign', 'wp-marketing-automations-pro' );
			BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );

			return false;
		}

		$sms_settings = BWFCRM_Core()->conversation->get_twilio_settings();
		if ( ! is_array( $sms_settings ) || empty( $sms_settings ) || ! isset( $sms_settings['twilio_no'] ) || ! isset( $sms_settings['account_sid'] ) || ! isset( $sms_settings['auth_token'] ) ) {
			$data['halt_reason'] = __( 'Twilio Connector\'s required details are not found. Please re-connect to Twilio connector again', 'wp-marketing-automations-pro' );
			BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );

			return false;
		}

		return true;
	}

	public function maybe_validate_whatsapp_settings() {
		$broadcast = $this->current_broadcast;
		$data      = isset( $broadcast['data'] ) && ! empty( $broadcast['data'] ) ? $broadcast['data'] : array();

		if ( ! BWFAN_Common::is_whatsapp_services_enabled() ) {
			$data['halt_reason'] = __( 'No WhatsApp service connected', 'wp-marketing-automations-pro' );
			BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );

			return false;
		}

		return true;
	}

	public function maybe_broadcast_running() {
		$broadcast = $this->current_broadcast;

		$last_modified = ! empty( $broadcast ) && isset( $broadcast['last_modified'] ) ? $broadcast['last_modified'] : false;
		if ( false !== $last_modified ) {
			$last_modified = time() - strtotime( $last_modified );
			if ( $last_modified <= 5 ) {
				return true;
			}
		}

		return false;
	}

	public function get_current_broadcast_data() {
		if ( ! is_array( $this->current_broadcast ) || ! isset( $this->current_broadcast['data'] ) ) {
			return array();
		}

		return $this->current_broadcast['data'];
	}

	public function validate_email_data_error( $email_data ) {
		if ( ! is_wp_error( $email_data ) ) {
			return false;
		}

		$e_data = $email_data->get_error_data();
		if ( is_array( $e_data ) && isset( $e_data['conversation_id'] ) ) {
			BWFCRM_Core()->conversation->fail_the_conversation( absint( $e_data['conversation_id'] ), $email_data->get_error_message() );
		}

		return true;
	}

	public function process_email( $to, $email_subject, $email_body, $headers ) {
		/** Will add code for other mailing providers */
		return wp_mail( $to, $email_subject, $email_body, $headers );
	}

	public function set_log( $log ) {
		if ( empty( $log ) ) {
			return;
		}
		$this->logs[] = array(
			't' => microtime( true ),
			'm' => $log,
		);
	}

	protected function log() {
		if ( ! is_array( $this->logs ) || 0 === count( $this->logs ) ) {
			return;
		}
		if ( false === apply_filters( 'bwfan_allow_broadcast_logging', false ) ) {
			return;
		}
		add_filter( 'bwfan_before_making_logs', '__return_true' );
		BWFAN_Core()->logger->log( print_r( $this->logs, true ), 'crm_broadcast' );
		$this->logs = [];
	}

	protected function before_executing_broadcast() {
		if ( ! class_exists( 'BWFAN_Compatibility_With_WP_SMTP' ) || ! method_exists( 'BWFAN_Compatibility_With_WP_SMTP', 'has_multiple_connections' ) || ! BWFAN_Compatibility_With_WP_SMTP::has_multiple_connections() ) {
			remove_all_filters( 'wp_mail' );
			remove_all_filters( 'wp_mail_from' );
		}

		remove_all_filters( 'wp_mail_from_name' );
		remove_all_filters( 'wp_mail_content_type' );
		remove_all_filters( 'wp_mail_charset' );
	}
}
