<?php
/**
 * Base class that new host providers integrations must extend.
 *
 * @package WP_Ultimo
 * @subpackage Integrations/Host_Providers
 * @since 2.0.0
 */

namespace WP_Ultimo\Integrations\Host_Providers;

use WP_Ultimo\Helpers\WP_Config;

// Exit if accessed directly
defined('ABSPATH') || exit;

/**
 * This base class should be extended to implement new host integrations for SSL and domains.
 */
abstract class Base_Host_Provider {

	/**
	 * Holds the id of the integration.
	 *
	 * @var string
	 * @since 2.0.0
	 */
	protected $id;

	/**
	 * Keeps the title of the integration.
	 *
	 * @var string
	 * @since 2.0.0
	 */
	protected $title;

	/**
	 * Link to the tutorial teaching how to make this integration work.
	 *
	 * @var string
	 * @since 2.0.0
	 */
	protected $tutorial_link = '';

	/**
	 * Array containing the features this integration supports.
	 *
	 * @var array
	 * @since 2.0.0
	 */
	protected $supports = [];

	/**
	 * Constants that need to be present on wp-config.php for this integration to work.
	 * The values can be strings or array with constants to check for a match.
	 *
	 * @since 2.0.0
	 * @var array
	 */
	protected $constants = [];

	/**
	 * Constants that are optional on wp-config.php.
	 *
	 * @since 2.0.0
	 * @var array
	 */
	protected $optional_constants = [];

	/**
	 * Runs on singleton instantiation.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function init(): void {

		add_filter('wu_domain_manager_get_integrations', [$this, 'self_register']);

		add_action('init', [$this, 'add_to_integration_list']);
	}

	/**
	 * Loads the hooks and dependencies, but only if the hosting is enabled via is_enabled().
	 *
	 * @since 2.0.0
	 */
	final public function __construct() {

		if ($this->detect() && ! $this->is_enabled()) {
			/*
			 * Adds an admin notice telling the admin that they should probably enable this integration.
			 */
			$this->alert_provider_detected();
			return;
		}

		/*
		 * Only add hooks if the integration is enabled and correctly setup.
		 */
		if ($this->is_enabled()) {
			/*
			 * Checks if everything was correctly setup.
			 */
			if ( ! $this->is_setup()) {
				/*
				 * Adds an admin notice telling the admin that the provider is not correctly setup.
				 */
				$this->alert_provider_not_setup();
				return;
			}

			/*
			 * Load the dependencies.
			 */
			$this->load_dependencies();

			/*
			 * Initialize the hooks.
			 */
			$this->register_hooks();
		}
	}

	/**
	 * Let the class register itself on the manager, allowing us to access the integrations later via the slug.
	 *
	 * @since 2.0.0
	 *
	 * @param array $integrations List of integrations added so far.
	 * @return array
	 */
	final public function self_register($integrations) {

		$integrations[ $this->get_id() ] = static::class;

		return $integrations;
	}

	/**
	 * Get the list of enabled host integrations.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	protected function get_enabled_list() {

		return get_network_option(null, 'wu_host_integrations_enabled', []);
	}

	/**
	 * Check if this integration is enabled.
	 *
	 * @since 2.0.0
	 * @return boolean
	 */
	final public function is_enabled() {

		$list = $this->get_enabled_list();

		return wu_get_isset($list, $this->get_id(), false);
	}

	/**
	 * Enables this integration.
	 *
	 * @since 2.0.0
	 * @return boolean
	 */
	public function enable() {

		$list = $this->get_enabled_list();

		$list[ $this->get_id() ] = true;

		return update_network_option(null, 'wu_host_integrations_enabled', $list);
	}

	/**
	 * Disables this integration.
	 *
	 * @since 2.0.0
	 * @return boolean
	 */
	public function disable() {

		$list = $this->get_enabled_list();

		$list[ $this->get_id() ] = false;

		return update_network_option(null, 'wu_host_integrations_enabled', $list);
	}

	/**
	 * Adds the host to the list of integrations.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function add_to_integration_list(): void {

		$slug = $this->get_id();

		$html = $this->is_enabled() ? sprintf('<span class="wu-self-center wu-text-green-800 wu-mr-4"><span class="dashicons-wu-check"></span> %s</span>', __('Activated', 'wp-multisite-waas')) : '';

		$url = wu_network_admin_url(
			'wp-ultimo-hosting-integration-wizard',
			[
				'integration' => $slug,
			]
		);

		$html .= sprintf('<a href="%s" class="button-primary">%s</a>', $url, __('Configuration', 'wp-multisite-waas'));

		// translators: %s is the name of a host provider (e.g. Cloudways, WPMUDev, Closte...).
		$title = sprintf(__('%s Integration', 'wp-multisite-waas'), $this->get_title());

		$title .= sprintf(
			"<span class='wu-normal-case wu-block wu-text-xs wu-font-normal wu-mt-1'>%s</span>",
			__('Go to the setup wizard to setup this integration.', 'wp-multisite-waas')
		);

		wu_register_settings_field(
			'integrations',
			"integration_{$slug}",
			[
				'type'  => 'note',
				'title' => $title,
				'desc'  => $html,
			]
		);
	}

	/**
	 * Adds an admin notice telling the admin that they should probably enable this integration.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function alert_provider_detected(): void {

		if (WP_Ultimo()->is_loaded() === false) {
			return;
		}

		// translators: %1$s will be replaced with the integration title. E.g. RunCloud
		$message = sprintf(__('It looks like you are using %1$s as your hosting provider, yet the %1$s integration module is not active. In order for the domain mapping integration to work with %1$s, you might want to activate that module.', 'wp-multisite-waas'), $this->get_title());

		$slug = $this->get_id();

		$actions = [
			'activate' => [
				// translators: %s is the integration name.
				'title' => sprintf(__('Activate %s', 'wp-multisite-waas'), $this->get_title()),
				'url'   => wu_network_admin_url(
					'wp-ultimo-hosting-integration-wizard',
					[
						'integration' => $slug,
					]
				),
			],
		];

		WP_Ultimo()->notices->add($message, 'info', 'network-admin', "should-enable-{$slug}-integration", $actions);
	}

	/**
	 * Adds an admin notice telling the admin that the provider is not correctly setup.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function alert_provider_not_setup(): void {

		if (WP_Ultimo()->is_loaded() === false) {
			return;
		}

		// translators: %1$s will be replaced with the integration title. E.g. RunCloud.
		$message = sprintf(__('It looks like you are using %1$s as your hosting provider, yet the %1$s integration module was not properly setup. In order for the domain mapping integration to work with %1$s, you need to configure that module.', 'wp-multisite-waas'), $this->get_title());

		$slug = $this->get_id();

		$actions = [
			'activate' => [
				// translators: %s is the integration name
				'title' => sprintf(__('Setup %s', 'wp-multisite-waas'), $this->get_title()),
				'url'   => wu_network_admin_url(
					'wp-ultimo-hosting-integration-wizard',
					[
						'integration' => $slug,
						'tab'         => 'config',
					]
				),
			],
		];

		WP_Ultimo()->notices->add($message, 'warning', 'network-admin', "should-setup-{$slug}-integration", $actions);
	}

	/**
	 * Get Fields for the integration.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	public function get_fields() {

		return [];
	}

	/**
	 * Returns the integration id.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_id() {

		return $this->id;
	}

	/**
	 * Returns the integration title.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_title() {

		return $this->title;
	}

	/**
	 * Checks if a feature is supported, like auto-ssl for example.
	 *
	 * @since 2.0.0
	 * @param string $feature Feature to check.
	 * @return boolean
	 */
	public function supports($feature) {

		return apply_filters('wu_hosting_support_supports', in_array($feature, $this->supports, true), $this);
	}

	/**
	 * Initializes the hooks.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function register_hooks(): void {
		/*
		 * Hooks the event that is triggered when a new domain is added.
		 */
		add_action('wu_add_domain', [$this, 'on_add_domain'], 10, 2);

		/*
		 * Hooks the event that is triggered when a domain is deleted.
		 */
		add_action('wu_remove_domain', [$this, 'on_remove_domain'], 10, 2);

		/*
		 * Hooks the event that is triggered when a sub-domain is added.
		 */
		add_action('wu_add_subdomain', [$this, 'on_add_subdomain'], 10, 2);

		/*
		 * Hooks the event that is triggered when a sub-domain is added.
		 */
		add_action('wu_remove_subdomain', [$this, 'on_remove_subdomain'], 10, 2);

		/*
		 * Add additional hooks.
		 */
		$this->additional_hooks();
	}

	/**
	 * Lets integrations add additional hooks.
	 *
	 * @since 2.0.7
	 * @return void
	 */
	public function additional_hooks() {}

	/**
	 * Can be used to load dependencies.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function load_dependencies() {}

	/**
	 * Picks up on tips that a given host provider is being used.
	 *
	 * We use this to suggest that the user should activate an integration module.
	 *
	 * @since 2.0.0
	 * @return boolean
	 */
	abstract public function detect();

	/**
	 * Checks if the integration is correctly setup after enabled.
	 *
	 * @since 2.0.0
	 * @return boolean
	 */
	public function is_setup() {

		$all_set = true;

		foreach ($this->constants as $constant) {
			$constants = is_array($constant) ? $constant : [$constant];

			$current = false;

			foreach ($constants as $constant) {
				if (defined($constant) && constant($constant)) {
					$current = true;

					break;
				}
			}

			$all_set = $all_set && $current;

			/*
			 * If any constant fail, bail.
			 */
			if (false === $all_set) {
				return false;
			}
		}

		return $all_set;
	}

	/**
	 * Returns a list of missing constants configured on wp-config.php
	 *
	 * @since 2.0.14
	 * @return array
	 */
	public function get_missing_constants() {

		$missing_constants = [];

		foreach ($this->constants as $constant) {
			$constants = is_array($constant) ? $constant : [$constant];

			$current = false;

			foreach ($constants as $constant) {
				if (defined($constant) && constant($constant)) {
					$current = true;

					break;
				}
			}

			$missing_constants = $current ? $missing_constants : array_merge($missing_constants, $constants);
		}

		return $missing_constants;
	}

	/**
	 * Returns a list of all constants, optional or not.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	public function get_all_constants() {

		$constants = [];

		foreach ($this->constants as $constant) {
			$current = is_array($constant) ? $constant : [$constant];

			$constants = array_merge($constants, $current);
		}

		return array_merge($constants, $this->optional_constants);
	}

	/**
	 * Adds the constants with their respective values into the wp-config.php.
	 *
	 * @since 2.0.0
	 *
	 * @param array $constant_values Key => Value of the necessary constants.
	 * @return void
	 */
	public function setup_constants($constant_values): void {
		/*
		 * Important: This step is crucial, as it makes sure we clean up undesired constants.
		 * Removing this can allow insertion of arbitrary constants onto the wp-config.pp file
		 * and that should NEVER happen.
		 *
		 * Note that this is also performed on the get_constants_string.
		 */
		$values = shortcode_atts(array_flip($this->get_all_constants()), $constant_values);

		foreach ($values as $constant => $value) {
			WP_Config::get_instance()->inject_wp_config_constant($constant, $value);
		}
	}

	/**
	 * Generates a define string for manual insertion on-to wp-config.php.
	 *
	 * This is useful when the user is not willing to let WP Multisite WaaS inject the code,
	 * Or when the wp-config.php is not writable.
	 *
	 * @since 2.0.0
	 *
	 * @param array $constant_values Key => Value of the necessary constants.
	 * @return string
	 */
	public function get_constants_string($constant_values) {
		/*
		 * Initializes the array with an opening comment.
		 */
		$content = [
			sprintf('// WP Multisite WaaS - Domain Mapping - %s', $this->get_title()),
		];

		/*
		 * Important: This step is crucial, as it makes sure we clean up undesired constants.
		 * Removing this can allow insertion of arbitrary constants onto the wp-config.php file
		 * and that should NEVER happen.
		 */
		$constant_values = shortcode_atts(array_flip($this->get_all_constants()), $constant_values);

		/*
		 * Adds the constants, one by one.
		 */
		foreach ($constant_values as $constant => $value) {
			$content[] = sprintf("define( '%s', '%s' );", $constant, $value);
		}

		/*
		 * Adds the final line.
		 */
		$content[] = sprintf('// WP Multisite WaaS - Domain Mapping - %s - End', $this->get_title());

		return implode(PHP_EOL, $content);
	}

	/**
	 * Returns the explainer lines for the integration.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	public function get_explainer_lines() {

		$explainer_lines = [
			'will'     => [
				// translators: %s is the name of the integration e.g. RunCloud
				'send_domains' => sprintf(__('Send API calls to %s servers with domain names added to this network', 'wp-multisite-waas'), $this->get_title()),
			],
			'will_not' => [],
		];

		if ($this->supports('autossl')) {

			// translators: %s is the name of the integration e.g. RunCloud
			$explainer_lines['will'][] = sprintf(__('Fetch and install a SSL certificate on %s platform after the domain is added.', 'wp-multisite-waas'), $this->get_title());
		} else {

			// translators: %s is the name of the integration e.g. RunCloud
			$explainer_lines['will_not'][] = sprintf(__('Fetch and install a SSL certificate on %s platform after the domain is added. This needs to be done manually.', 'wp-multisite-waas'), $this->get_title());
		}

		return $explainer_lines;
	}

	/**
	 * This method gets called when a new domain is mapped.
	 *
	 * @since 2.0.0
	 * @param string $domain The domain name being mapped.
	 * @param int    $site_id ID of the site that is receiving that mapping.
	 * @return void
	 */
	abstract public function on_add_domain($domain, $site_id);

	/**
	 * This method gets called when a mapped domain is removed.
	 *
	 * @since 2.0.0
	 * @param string $domain The domain name being removed.
	 * @param int    $site_id ID of the site that is receiving that mapping.
	 * @return void
	 */
	abstract public function on_remove_domain($domain, $site_id);

	/**
	 * This method gets called when a new subdomain is being added.
	 *
	 * This happens every time a new site is added to a network running on subdomain mode.
	 *
	 * @since 2.0.0
	 * @param string $subdomain The subdomain being added to the network.
	 * @param int    $site_id ID of the site that is receiving that mapping.
	 * @return void
	 */
	abstract public function on_add_subdomain($subdomain, $site_id);

	/**
	 * This method gets called when a new subdomain is being removed.
	 *
	 * This happens every time a new site is removed to a network running on subdomain mode.
	 *
	 * @since 2.0.0
	 * @param string $subdomain The subdomain being removed to the network.
	 * @param int    $site_id ID of the site that is receiving that mapping.
	 * @return void
	 */
	abstract public function on_remove_subdomain($subdomain, $site_id);

	/**
	 * Tests the connection with the API.
	 *
	 * Needs to be implemented by integrations.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function test_connection(): void {

		wp_send_json_success([]);
	}

	/**
	 * Returns the description of this integration.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_description() {

		return __('No description provided.', 'wp-multisite-waas');
	}

	/**
	 * Returns the logo for the integration.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_logo() {

		return '';
	}
}
