<?php
/**
 * WooCommerce Compatibility Check.
 *
 * @package Automattic/WooCommerce/Grow/Tools
 */

namespace Automattic\WooCommerce\Grow\Tools\CompatChecker\v0_0_1\Checks;

use Automattic\WooCommerce\Grow\Tools\CompatChecker\v0_0_1\Exception\IncompatibleException;

defined( 'ABSPATH' ) || exit;

/**
 * The WooCommerce Compatibility Check class.
 */
class WCCompatibility extends CompatCheck {

	/**
	 * WooCommerce plugin file path.
	 *
	 * @var string
	 */
	private const WC_PLUGIN_FILE = 'woocommerce/woocommerce.php';

	/**
	 * Define the L-n support policy here.
	 *
	 * @var int|float The miniumum supported WooCommerce versions before the latest.
	 *
	 * @see https://woo.com/support-policy/
	 */
	private $min_wc_semver = 0.2; // By default, the latest minus two version.

	/**
	 * Determins if WooCommerce is installed.
	 *
	 * @return bool
	 */
	private function is_wc_installed() {
		$installed_plugins = get_plugins();

		return isset( $installed_plugins[ self::WC_PLUGIN_FILE ] );
	}

	/**
	 * Determines if WooCommerce is activated.
	 *
	 * @return bool
	 */
	private function is_wc_activated() {
		return class_exists( 'WooCommerce' );
	}

	/**
	 * Gets the version of the currently installed WooCommerce.
	 *
	 * @return string|null Woocommerce version number or null if undetermined.
	 */
	public static function get_wc_version() {
		return defined( 'WC_VERSION' ) && WC_VERSION ? WC_VERSION : null;
	}

	/**
	 * Determines if the WooCommerce compatible.
	 *
	 * @return bool
	 */
	private function is_wc_compatible() {
		$wc_version          = $this->get_wc_version();
		$wc_version_required = $this->plugin_data['RequiresWC'];

		if ( empty( $wc_version_required ) ) {
			return true;
		}

		return version_compare( $wc_version, $wc_version_required, '>=' );
	}

	/**
	 * Determines if the WooCommerce version is untested.
	 *
	 * @return bool
	 */
	private function is_wc_untested() {
		if ( empty( $this->plugin_data['TestedWC'] ) ) {
			return false;
		}

		$wc_version             = $this->get_wc_version();
		$wc_version_tested_upto = $this->plugin_data['TestedWC'];

		return $this->compare_major_version( $wc_version, $wc_version_tested_upto, '<=' );
	}

	/**
	 * Check WooCommerce installation and activation.
	 *
	 * @return bool
	 * @throws IncompatibleException If WooCommerce is not activated.
	 */
	private function check_wc_installation_and_activation() {
		if ( ! $this->is_wc_activated() ) {
			add_action( 'admin_notices', [ $this, 'wc_fail_load' ] );
			throw new IncompatibleException( esc_html__( 'WooCommerce is not installed or activated.', 'woogrow-compat-checker' ) );
		}
		return true;
	}

	/**
	 * Check WooCommerce version.
	 *
	 * @return bool
	 * @throws IncompatibleException If WooCommerce version is not compatible.
	 */
	private function check_wc_version() {
		if ( ! $this->is_wc_compatible() ) {
			add_action( 'admin_notices', [ $this, 'wc_out_of_date' ] );
			throw new IncompatibleException( esc_html__( 'WooCommerce version not compatible.', 'woogrow-compat-checker' ) );
		}

		if ( ! $this->is_wc_untested() ) {
			add_action( 'admin_notices', [ $this, 'wc_untested' ] );
		}

		return true;
	}

	/**
	 * Retrieves a list of the latest available WooCommerce versions.
	 *
	 * Excludes betas, release candidates and development versions.
	 * Versions are sorted from most recent to least recent.
	 *
	 * @return string[] Array of semver strings.
	 */
	private function get_latest_wc_versions() {
		$latest_wc_versions = get_transient( 'compat_checker_wc_versions' );

		if ( ! is_array( $latest_wc_versions ) ) {

			/**
			 * The endpoint to fetch the latest WooCommerce versions.
			 *
			 * @link https://codex.wordpress.org/WordPress.org_API
			 */
			$wp_org_request = wp_remote_get( 'https://api.wordpress.org/plugins/info/1.0/woocommerce.json', [ 'timeout' => 1 ] );

			if ( is_array( $wp_org_request ) && isset( $wp_org_request['body'] ) ) {

				$plugin_info = json_decode( $wp_org_request['body'], true );

				if ( is_array( $plugin_info ) && ! empty( $plugin_info['versions'] ) && is_array( $plugin_info['versions'] ) ) {

					$latest_wc_versions = [];

					// Reverse the array as WordPress supplies oldest version first, newest last.
					foreach ( array_keys( array_reverse( $plugin_info['versions'] ) ) as $wc_version ) {

						// Skip trunk, release candidates, betas and other non-final or irregular versions.
						if (
							is_string( $wc_version )
							&& '' !== $wc_version
							&& is_numeric( $wc_version[0] )
							&& false === strpos( $wc_version, '-' )
						) {
							$latest_wc_versions[] = $wc_version;
						}
					}

					set_transient( 'compat_checker_wc_versions', $latest_wc_versions, WEEK_IN_SECONDS );
				}
			}
		}

		return is_array( $latest_wc_versions ) ? $latest_wc_versions : [];
	}

	/**
	 * Get the L-n version of WooCommerce.
	 *
	 * @return string
	 */
	private function get_supported_wc_version() {
		$latest_wc_versions = $this->get_latest_wc_versions();

		if ( empty( $latest_wc_versions ) ) {
			return '';
		}

		$latest_wc_version    = current( $latest_wc_versions );
		$supported_wc_version = $latest_wc_version;

		$latest_semver    = explode( '.', $latest_wc_version );
		$supported_semver = explode( '.', (string) $this->min_wc_semver );
		$supported_major  = max( 0, (int) $latest_semver[0] - (int) $supported_semver[0] );
		$supported_minor  = isset( $supported_semver[1] ) ? (int) $supported_semver[1] : 0;
		$previous_minor   = null;

		// Loop known WooCommerce versions from the most recent until we get the oldest supported one.
		foreach ( $latest_wc_versions as $older_wc_version ) {
			// As we loop through the versions, the latest one before we break the loop will be the minimum supported version.
			$supported_wc_version = $older_wc_version;

			$older_semver = explode( '.', $older_wc_version );
			$older_major  = (int) $older_semver[0];
			$older_minor  = isset( $older_semver[1] ) ? (int) $older_semver[1] : 0;

			// if major is ignored, skip; if the minor hasn't changed (patch must be), skip.
			if ( $older_major > $supported_major || $older_minor === $previous_minor ) {
				continue;
			}

			// We reached the maximum number of supported minor versions.
			if ( $supported_minor <= 0 ) {
				break;
			}

			// Store the previous minor while we loop patch versions, which we ignore.
			$previous_minor = $older_minor;

			--$supported_minor;
		}

		return $supported_wc_version;
	}

	/**
	 * Check for WooCommerce upgrade recommendation.
	 *
	 * @return bool
	 */
	private function check_wc_upgrade_recommendation() {
		// Bail on frontend requests or if there is no definied versions to compare.
		if ( ! is_admin() || empty( $this->min_wc_semver ) || ! is_numeric( $this->min_wc_semver ) ) {
			return;
		}

		$current_wc_version   = $this->get_wc_version();
		$supported_wc_version = $this->get_supported_wc_version();

		if ( ! $this->compare_major_version( $current_wc_version, $supported_wc_version, '>=' ) ) {
			add_action( 'admin_notices', [ $this, 'make_upgrade_recommendation' ] );
		}
		return true;
	}

	/**
	 * Add notices for WooCommerce not being installed or activated.
	 */
	public function wc_fail_load() {
		$plugin_name = $this->plugin_data['Name'];

		if ( $this->is_wc_installed() ) {
			if ( ! current_user_can( 'activate_plugins' ) ) {
				return;
			}

			$activation_url = wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . self::WC_PLUGIN_FILE . '&amp;plugin_status=all&amp;paged=1&amp;s', 'activate-plugin_' . self::WC_PLUGIN_FILE );
			$message        = sprintf(
				/* translators: %1$s - Plugin Name, %2$s - activate WooCommerce link open, %3$s - activate WooCommerce link close. */
				esc_html__( '%1$s requires WooCommerce to be activated. Please %2$sactivate WooCommerce%3$s.', 'woogrow-compat-checker' ),
				'<strong>' . $plugin_name . '</strong>',
				'<a href="' . esc_url( $activation_url ) . '">',
				'</a>'
			);
			$this->add_admin_notice(
				'woocommerce-not-activated',
				'error',
				$message
			);
		} else {
			if ( ! current_user_can( 'install_plugins' ) ) {
				return;
			}

			$install_url = wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=woocommerce' ), 'install-plugin_woocommerce' );
			$message     = sprintf(
				/* translators: %1$s - Plugin Name, %2$s - install WooCommerce link open, %3$s - install WooCommerce link close. */
				esc_html__( '%1$s requires WooCommerce to be installed and activated. Please %2$sinstall WooCommerce%3$s.', 'woogrow-compat-checker' ),
				'<strong>' . $plugin_name . '</strong>',
				'<a href="' . esc_url( $install_url ) . '">',
				'</a>'
			);

			$this->add_admin_notice(
				'woocommerce-not-installed',
				'error',
				$message
			);
		}
	}

	/**
	 * Add notices for out of date WooCommerce.
	 */
	public function wc_out_of_date() {
		if ( ! current_user_can( 'update_plugins' ) ) {
			return;
		}

		$plugin_name         = $this->plugin_data['Name'];
		$wc_version_required = ( 1 === substr_count( $this->plugin_data['RequiresWC'], '.' ) ) ? $this->plugin_data['RequiresWC'] . '.0' : $this->plugin_data['RequiresWC']; // Pad .0 if the min required WC version is not in semvar format.

		$message = sprintf(
			/* translators: %1$s - Plugin Name, %2$s - minimum WooCommerce version, %3$s - update WooCommerce link open, %4$s - update WooCommerce link close, %5$s - download minimum WooCommerce link open, %6$s - download minimum WooCommerce link close. */
			esc_html__( '%1$s requires WooCommerce version %2$s or higher. Please %3$supdate WooCommerce%4$s to the latest version, or %5$sdownload the minimum required version &raquo;%6$s', 'woogrow-compat-checker' ),
			'<strong>' . $plugin_name . '</strong>',
			$wc_version_required,
			'<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '#update-plugins-table">',
			'</a>',
			'<a href="' . esc_url( 'https://downloads.wordpress.org/plugin/woocommerce.' . $wc_version_required . '.zip' ) . '">',
			'</a>'
		);

		$this->add_admin_notice(
			'woocommerce-out-of-date',
			'error',
			$message
		);
	}

	/**
	 * Adds notice for untested WooCommerce.
	 */
	public function wc_untested() {
		$plugin_name    = $this->plugin_data['Name'];
		$wc_version     = $this->get_wc_version();
		$plugin_version = $this->plugin_data['Version'];

		$message = sprintf(
			/* translators: %1$s - Plugin Name, %2$s - Plugin version, %3$s - WooCommerce version number */
			esc_html__( '%1$s - version %2$s is untested with WooCommerce %3$s.', 'woogrow-compat-checker' ),
			'<strong>' . $plugin_name . '</strong>',
			$plugin_version,
			$wc_version
		);

		$this->add_admin_notice(
			'woocommerce-untested',
			'warning is-dismissible',
			$message
		);
	}

	/**
	 * Adds notice for WooCommerce upgrade recommendation.
	 */
	public function make_upgrade_recommendation() {
		// Bail if the user does not have the update plugins capability.
		if ( ! current_user_can( 'update_plugins' ) ) {
			return;
		}

		$plugin_name        = $this->plugin_data['Name'];
		$current_wc_version = $this->get_wc_version();

		$message = sprintf(
			/* translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version number, %3$s - opening <a> HTML link tag, %4$s - closing </a> HTML link tag */
			esc_html__( 'Heads up! %1$s will soon discontinue support for WooCommerce %2$s. Please %3$supdate WooCommerce%4$s to take advantage of the latest updates and features.', 'woogrow-compat-checker' ),
			'<strong>' . $plugin_name . '</strong>',
			$current_wc_version,
			'<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '#update-plugins-table">',
			'</a>'
		);

		$this->add_admin_notice(
			'woocommerce-upgrade-recommendation',
			'warning is-dismissible',
			$message
		);
	}

	/**
	 * Run all compatibility checks.
	 */
	protected function run_checks() {
		try {
			$this->check_wc_installation_and_activation();
			$this->check_wc_version();
			$this->check_wc_upgrade_recommendation();
			return true;
		} catch ( IncompatibleException $e ) {
			return false;
		}
	}
}
