<?php

class ITSEC_Malware_Scheduling {

	// Approximately 10 hours since first try
	const MAX_TRIES = 8;

	public function run() {
		add_action( 'itsec_scheduler_register_events', array( $this, 'register_event' ) );
		add_action( 'itsec_scheduled_malware-scan', array( $this, 'run_scan' ) );
		add_action( 'itsec_scheduled_malware-scan-site', array( $this, 'run_sub_site_scan' ) );

		add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
		add_filter( 'itsec-filter-itsec-get-everything-verbs', array( $this, 'register_sync_get_everything_verbs' ) );
		add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
		add_filter( 'itsec_malware-scheduling_notification_strings', array( $this, 'notification_strings' ) );

		ITSEC_Core::get_scheduler()->register_loop( 'malware-scan-site', ITSEC_Scheduler::S_TWICE_DAILY, 600 );
	}

	/**
	 * Run the malware scan.
	 *
	 * @param ITSEC_Job $job
	 */
	public function run_scan( $job ) {

		require_once( dirname( __FILE__ ) . '/class-itsec-malware-scheduling-scanner.php' );

		$results = ITSEC_Malware_Scheduling_Scanner::scan();

		if ( is_wp_error( $results ) && $results->get_error_message( 'itsec-scheduled-malware-scanner-sucuri-error' ) ) {
			$retry = $job->is_retry();

			if ( ! ITSEC_Modules::get_setting( 'malware-scheduling', 'sucuri_failing' ) ) {
				ITSEC_Modules::set_setting( 'malware-scheduling', 'sucuri_failing', true );
			}

			if ( $retry > self::MAX_TRIES ) {
				ITSEC_Malware_Scheduling_Scanner::send_email( $results );
			} else {
				$job->reschedule_in( $this->minutes_until_retry( $retry + 1 ) );
			}
		} elseif ( ITSEC_Modules::get_setting( 'malware-scheduling', 'sucuri_failing' ) ) {
			ITSEC_Modules::set_setting( 'malware-scheduling', 'sucuri_failing', false );
		}
	}

	/**
	 * Run the malware scan for a sub site.
	 *
	 * @param ITSEC_Job $job
	 */
	public function run_sub_site_scan( $job ) {

		if ( ITSEC_Modules::get_setting( 'malware-scheduling', 'sucuri_failing' ) ) {
			$job->reschedule_in( HOUR_IN_SECONDS );

			return;
		}

		$data = $job->get_data();

		if ( empty( $data['site_id'] ) ) {
			return; // Todo maybe reschedule if things get corrupted.
		}

		require_once( dirname( __FILE__ ) . '/class-itsec-malware-scheduling-scanner.php' );
		$results = ITSEC_Malware_Scheduling_Scanner::scan( $data['site_id'] );

		if ( is_wp_error( $results ) && $results->get_error_message( 'itsec-scheduled-malware-scanner-sucuri-error' ) ) {
			ITSEC_Modules::set_setting( 'malware-scheduling', 'sucuri_failing', true );
			$job->reschedule_in( HOUR_IN_SECONDS );

			return;
		}

		$next = $this->get_next_site_id( $data['site_id'] );

		if ( null === $next ) {
			$job->schedule_new_loop( array( 'site_id' => $this->get_next_site_id( 0 ) ) );
		} else {
			$job->schedule_next_in_loop( array( 'site_id' => $next ) );
		}
	}

	/**
	 * Maybe schedule the initial sub-site scan event.
	 *
	 * It will only be scheduled if no malware-scan-site jobs are scheduled.
	 *
	 * @param ITSEC_Scheduler $scheduler
	 */
	private function maybe_schedule_initial_sub_site_event( $scheduler ) {

		if ( $scheduler->is_single_scheduled( 'malware-scan-site', null ) ) {
			return;
		}

		if ( ! $site_id = $this->get_next_site_id( 0 ) ) {
			return;
		}

		$scheduler->schedule_loop( 'malware-scan-site', compact( 'site_id' ) );
	}

	/**
	 * Get the next site ID to scan based on the current site ID.
	 *
	 * @param int $site_id
	 *
	 * @return int|null
	 */
	private function get_next_site_id( $site_id ) {

		global $wpdb;

		$id = $wpdb->get_var( $wpdb->prepare( "SELECT `blog_id` FROM {$wpdb->blogs} WHERE `public` = 1 AND `archived` = 0 AND `deleted` = 0 AND `blog_id` > %d ORDER BY `blog_id` ASC LIMIT 1", $site_id ) );

		if ( null === $id ) {
			return null;
		}

		if ( is_main_site( $id ) ) {
			return $this->get_next_site_id( $id );
		}

		return (int) $id;
	}

	/**
	 * Register the event.
	 *
	 * @param ITSEC_Scheduler $scheduler
	 */
	public function register_event( $scheduler ) {
		$scheduler->schedule( ITSEC_Scheduler::S_TWICE_DAILY, 'malware-scan' );

		if ( is_multisite() ) {
			$this->maybe_schedule_initial_sub_site_event( $scheduler );
		}
	}

	/**
	 * Determine the number of minutes we should wait before contacting Sucuri.
	 *
	 * Exponential back-off modified to have a minimum of 10 minute difference.
	 *
	 * [ ( 2ˣ-1 ) / 2 ] + 10x
	 *
	 * @param int $try Try count.
	 *
	 * @return int
	 */
	private function minutes_until_retry( $try ) {
		return (int) floor( ( ( pow( 2, $try ) - 1 ) / 2 ) + ( 10 * $try ) );
	}

	/**
	 * Register verbs for Sync.
	 *
	 * @since 3.6.0
	 *
	 * @param Ithemes_Sync_API $api Sync API object.
	 */
	public function register_sync_verbs( $api ) {
		$api->register( 'itsec-get-malware-schedule-settings', 'Ithemes_Sync_Verb_ITSEC_Get_Malware_Schedule_Settings', dirname( __FILE__ ) . '/sync-verbs/itsec-get-malware-schedule-settings.php' );
		$api->register( 'itsec-manage-malware-schedule-settings', 'Ithemes_Sync_Verb_ITSEC_Manage_Malware_Schedule_Settings', dirname( __FILE__ ) . '/sync-verbs/itsec-manage-malware-schedule-settings.php' );
	}

	/**
	 * Filter to add verbs to the response for the itsec-get-everything verb.
	 *
	 * @since 3.6.0
	 *
	 * @param array $verbs
	 *
	 * @return array Array of verbs.
	 */
	public function register_sync_get_everything_verbs( $verbs ) {
		$verbs['malware_scheduling'][] = 'itsec-get-malware-schedule-settings';

		return $verbs;
	}

	/**
	 * Register the Scheduled Malware Scan notification.
	 *
	 * @param array $notifications
	 *
	 * @return array
	 */
	public function register_notification( $notifications ) {
		$notifications['malware-scheduling'] = array(
			'recipient' => ITSEC_Notification_Center::R_USER_LIST,
			'optional'  => true,
			'module'    => 'malware-scheduling',
		);

		return $notifications;
	}

	/**
	 * Register the strings for the Scheduled Malware Scan notification.
	 *
	 * @return array
	 */
	public function notification_strings() {
		return array(
			'label'       => esc_html__( 'Malware Scan Results', 'it-l10n-ithemes-security-pro' ),
			'description' => sprintf( esc_html__( 'The %1$sMalware Scan Scheduling%2$s module sends an email if it discovers an issue or has repeated difficulty conducting the scan.', 'it-l10n-ithemes-security-pro' ), '<a href="#" data-module-link="malware-scheduling">', '</a>' ),
		);
	}
}
