<?php
/**
 * Health class.
 *
 * @package automattic/jetpack-sync
 */

namespace Automattic\Jetpack\Sync;

/**
 * Health class.
 */
class Health {

	/**
	 * Prefix of the blog lock transient.
	 *
	 * @access public
	 *
	 * @var string
	 */
	const STATUS_OPTION = 'sync_health_status';

	/**
	 * Status key in option array.
	 *
	 * @access public
	 *
	 * @var string
	 */
	const OPTION_STATUS_KEY = 'status';

	/**
	 * Timestamp key in option array.
	 *
	 * @access public
	 *
	 * @var string
	 */
	const OPTION_TIMESTAMP_KEY = 'timestamp';

	/**
	 * Unknown status code.
	 *
	 * @access public
	 *
	 * @var string
	 */
	const STATUS_UNKNOWN = 'unknown';

	/**
	 * Disabled status code.
	 *
	 * @access public
	 *
	 * @var string
	 */
	const STATUS_DISABLED = 'disabled';

	/**
	 * Out of sync status code.
	 *
	 * @access public
	 *
	 * @var string
	 */
	const STATUS_OUT_OF_SYNC = 'out_of_sync';

	/**
	 * In sync status code.
	 *
	 * @access public
	 *
	 * @var string
	 */
	const STATUS_IN_SYNC = 'in_sync';

	/**
	 * If sync is active, Health-related hooks will be initialized after plugins are loaded.
	 */
	public static function init() {
		add_action( 'jetpack_full_sync_end', array( __CLASS__, 'full_sync_end_update_status' ), 10, 2 );
	}

	/**
	 * Gets health status code.
	 *
	 * @return string Sync Health Status
	 */
	public static function get_status() {
		$status = \Jetpack_Options::get_option( self::STATUS_OPTION );

		if ( false === $status || ! is_array( $status ) || empty( $status[ self::OPTION_STATUS_KEY ] ) ) {
			return self::STATUS_UNKNOWN;
		}

		switch ( $status[ self::OPTION_STATUS_KEY ] ) {
			case self::STATUS_DISABLED:
			case self::STATUS_OUT_OF_SYNC:
			case self::STATUS_IN_SYNC:
				return $status[ self::OPTION_STATUS_KEY ];
			default:
				return self::STATUS_UNKNOWN;
		}
	}

	/**
	 * When the Jetpack plugin is upgraded, set status to disabled if sync is not enabled,
	 * or to unknown, if the status has never been set before.
	 */
	public static function on_jetpack_upgraded() {
		if ( ! Settings::is_sync_enabled() ) {
			self::update_status( self::STATUS_DISABLED );
			return;
		}
		if ( false === self::is_status_defined() ) {
			self::update_status( self::STATUS_UNKNOWN );
		}
	}

	/**
	 * When the Jetpack plugin is activated, set status to disabled if sync is not enabled,
	 * or to unknown.
	 */
	public static function on_jetpack_activated() {
		if ( ! Settings::is_sync_enabled() ) {
			self::update_status( self::STATUS_DISABLED );
			return;
		}
		self::update_status( self::STATUS_UNKNOWN );
	}

	/**
	 * Updates sync health status with either a valid status, or an unknown status.
	 *
	 * @param string $status Sync Status.
	 *
	 * @return bool True if an update occoured, or false if the status didn't change.
	 */
	public static function update_status( $status ) {
		if ( self::get_status() === $status ) {
			return false;
		}
		// Default Status Option.
		$new_status = array(
			self::OPTION_STATUS_KEY    => self::STATUS_UNKNOWN,
			self::OPTION_TIMESTAMP_KEY => microtime( true ),
		);

		switch ( $status ) {
			case self::STATUS_DISABLED:
			case self::STATUS_OUT_OF_SYNC:
			case self::STATUS_IN_SYNC:
				$new_status[ self::OPTION_STATUS_KEY ] = $status;
				break;
		}

		\Jetpack_Options::update_option( self::STATUS_OPTION, $new_status );
		return true;
	}

	/**
	 * Check if Status has been previously set.
	 *
	 * @return bool is a Status defined
	 */
	public static function is_status_defined() {
		$status = \Jetpack_Options::get_option( self::STATUS_OPTION );

		if ( false === $status || ! is_array( $status ) || empty( $status[ self::OPTION_STATUS_KEY ] ) ) {
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Check if the sync queue is healthy based on size and lag.
	 *
	 * The queue is considered healthy if either size OR lag is within its limit.
	 * This matches the logic in Listener::can_add_to_queue(): data loss only occurs
	 * when both limits are exceeded simultaneously.
	 *
	 * @param int   $queue_size     Current queue size.
	 * @param float $queue_lag      Current queue lag in seconds.
	 * @param int   $max_queue_size Maximum allowed queue size.
	 * @param float $max_queue_lag  Maximum allowed queue lag in seconds.
	 * @return bool Whether the queue is healthy.
	 */
	public static function is_queue_healthy( $queue_size, $queue_lag, $max_queue_size, $max_queue_lag ) {
		return ( $queue_size < $max_queue_size )
			|| ( $queue_lag < $max_queue_lag );
	}

	/**
	 * Update Sync Status if Full Sync ended of Posts
	 *
	 * @param string $checksum The checksum that's currently being processed.
	 * @param array  $range The ranges of object types being processed.
	 */
	public static function full_sync_end_update_status( $checksum, $range ) {
		if ( isset( $range['posts'] ) ) {
			self::update_status( self::STATUS_IN_SYNC );
		}
	}
}
