<?php
/**
 * System Check class
 *
 * @since   3.0.0
 * @package DokanKits\Setup
 */

namespace DokanKits\Setup;

use DokanKits\Admin\Notices;
use DokanKits\Core\Data\Models\Settings;
use DokanKits\Core\Data\Stores\SettingsDataStore;
use DokanKits\Core\Interfaces\Hookable;
use Exception;

/**
 * System Check
 *
 * Checks system requirements for the plugin and provides extensible
 * architecture for third-party developers to add custom requirement checks.
 *
 * @since   3.0.0
 * @package DokanKits\Setup
 */
class SystemCheck implements Hookable {
	/**
	 * Requirements.
	 *
	 * @since 3.0.0
	 * @var array
	 */
	protected array $requirements = array(
		'php'        => '7.4',
		'wp'         => '5.8',
		'dokan_lite' => '3.9.7',
		'extensions' => array(
			'curl'     => true,
			'json'     => true,
			'mbstring' => true,
		),
	);

	/**
	 * PHP error messages.
	 *
	 * @since 3.0.0
	 * @var array
	 */
	protected array $php_errors = array();

	/**
	 * WordPress error messages.
	 *
	 * @since 3.0.0
	 * @var array
	 */
	protected array $wp_errors = array();

	/**
	 * Plugin error messages.
	 *
	 * @since 3.0.0
	 * @var array
	 */
	protected array $plugin_errors = array();

	/**
	 * Extension error messages.
	 *
	 * @since 3.0.0
	 * @var array
	 */
	protected array $extension_errors = array();

	/**
	 * Notice ID for system requirements.
	 *
	 * @since 3.0.0
	 * @var string
	 */
	protected string $notice_id = 'dokan_kits_system_requirements';

	/**
	 * Register hooks.
	 *
	 * Implements the Hookable interface to register all necessary hooks
	 * for the System Check functionality.
	 *
	 * @since  3.0.0
	 *
	 * @return void
	 */
	public function register_hooks(): void {
		add_action( 'admin_init', array( $this, 'maybe_display_admin_notice' ), 20 );
	}

	/**
	 * Get system requirements.
	 *
	 * @since 3.0.0
	 *
	 * @return array System requirements.
	 */
	public function get_requirements(): array {
		/**
		 * Filter the system requirements
		 *
		 * Allows third-party developers to modify or add additional
		 * system requirements for Dokan Kits.
		 *
		 * @since 3.0.0
		 * @param array $requirements The system requirements array.
		 */
		return apply_filters( 'dokan_kits_system_requirements', $this->requirements );
	}

	/**
	 * Get system status settings model.
	 *
	 * @since 3.0.0
	 *
	 * @return Settings
	 * @throws Exception If settings cannot be retrieved or created.
	 */
	protected function get_system_status(): Settings {
		$data_store    = dokan_kits_get_container()->get( SettingsDataStore::class );
		$system_status = $data_store->get_settings_by_name( 'system_status' );

		// If we somehow still couldn't get the settings, create a fallback instance.
		if ( ! $system_status instanceof Settings ) {
			$system_status = dokan_kits_get_container()->get( Settings::class );
			$system_status->set_name( 'system_status' );
			$system_status->set_value(
				array(
					'last_check_time' => 0,
					'status'          => 'unknown',
					'errors'          => array(),
				)
			);
			$system_status->save();
		}

		return $system_status;
	}

	/**
	 * Check system requirements.
	 *
	 * Performs comprehensive checks on PHP version, WordPress version,
	 * required plugins, and PHP extensions. Can be extended via filters.
	 *
	 * @since 3.0.0
	 *
	 * @return bool True if all requirements are met, false otherwise.
	 * @throws Exception If system status cannot be updated.
	 */
	public function check(): bool {
		/**
		 * Action hook that fires before system requirements check
		 *
		 * @since 3.0.0
		 *
		 * @param SystemCheck $this System_Check instance.
		 */
		do_action( 'dokan_kits_before_system_check', $this );

		// Reset errors.
		$this->php_errors       = array();
		$this->wp_errors        = array();
		$this->plugin_errors    = array();
		$this->extension_errors = array();

		// Get filtered requirements.
		$requirements = $this->get_requirements();

		// Check PHP version.
		if ( version_compare( PHP_VERSION, $requirements['php'], '<' ) ) {
			$this->php_errors[] = sprintf(
			// translators: %1$s: Current PHP version, %2$s: Required PHP version.
				__( 'Current PHP version (%1$s) does not meet the minimum required version (%2$s).', 'dokan-kits' ),
				PHP_VERSION,
				$requirements['php']
			);
		}

		// Check WordPress version.
		$wp_version = get_bloginfo( 'version' );
		if ( version_compare( $wp_version, $requirements['wp'], '<' ) ) {
			$this->wp_errors[] = sprintf(
			// translators: %1$s: Current WordPress version, %2$s: Required WordPress version.
				__( 'Current WordPress version (%1$s) does not meet the minimum required version (%2$s).', 'dokan-kits' ),
				$wp_version,
				$requirements['wp']
			);
		}

		// Check Dokan Lite.
		if ( defined( 'DOKAN_PLUGIN_VERSION' ) ) {
			if ( version_compare( DOKAN_PLUGIN_VERSION, $requirements['dokan_lite'], '<' ) ) {
				$this->plugin_errors[] = sprintf(
				// translators: %1$s: Current Dokan version, %2$s: Required Dokan version.
					__( 'Current Dokan version (%1$s) does not meet the minimum required version (%2$s).', 'dokan-kits' ),
					DOKAN_PLUGIN_VERSION,
					$requirements['dokan_lite']
				);
			}
		} else {
			$this->plugin_errors[] = __( 'Dokan Lite is not installed or activated.', 'dokan-kits' );
		}

		// Check extensions.
		foreach ( $requirements['extensions'] as $extension => $required ) {
			if ( $required && ! extension_loaded( $extension ) ) {
				$this->extension_errors[] = sprintf(
				// translators: %s: PHP extension name.
					__( 'Required PHP extension %s is missing.', 'dokan-kits' ),
					$extension
				);
			}
		}

		/**
		 * Filter PHP errors
		 *
		 * @since 3.0.0
		 * @param array $php_errors PHP requirement errors.
		 */
		$this->php_errors = apply_filters( 'dokan_kits_system_check_php_errors', $this->php_errors );

		/**
		 * Filter WordPress errors
		 *
		 * @since 3.0.0
		 * @param array $wp_errors WordPress requirement errors.
		 */
		$this->wp_errors = apply_filters( 'dokan_kits_system_check_wp_errors', $this->wp_errors );

		/**
		 * Filter plugin errors
		 *
		 * @since 3.0.0
		 * @param array $plugin_errors Plugin dependency errors.
		 */
		$this->plugin_errors = apply_filters( 'dokan_kits_system_check_plugin_errors', $this->plugin_errors );

		/**
		 * Filter PHP extension errors
		 *
		 * @since 3.0.0
		 * @param array $extension_errors PHP extension requirement errors.
		 */
		$this->extension_errors = apply_filters( 'dokan_kits_system_check_extension_errors', $this->extension_errors );

		// Update system status.
		$this->update_system_status();

		$result = empty( $this->php_errors ) && empty( $this->wp_errors ) && empty( $this->plugin_errors ) && empty( $this->extension_errors );

		/**
		 * Action hook that fires after system requirements check
		 *
		 * @since 3.0.0
		 *
		 * @param SystemCheck $system_check   System_Check instance.
		 * @param bool        $result Whether all requirements are met.
		 */
		do_action( 'dokan_kits_after_system_check', $this, $result );

		return $result;
	}

	/**
	 * Update system status.
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 * @throws Exception If system status cannot be saved.
	 */
	protected function update_system_status(): void {
		$system_status = $this->get_system_status();

		$errors = $this->get_all_errors();
		$status = empty( $errors ) ? 'ok' : 'error';

		$status_data = array(
			'last_check_time' => time(),
			'status'          => $status,
			'errors'          => $errors,
		);

		/**
		 * Filter the system status data before saving
		 *
		 * @since 3.0.0
		 *
		 * @@param array       $status_data The system status data.
		 * @@param SystemCheck $system_check System_Check instance.
		 */
		$status_data = apply_filters( 'dokan_kits_system_status_data', $status_data, $this );

		$system_status->set_value( $status_data );

		try {
			$system_status->save();

			/**
			 * Action hook that fires after system status is updated
			 *
			 * @since 3.0.0
			 *
			 * @param array       $status_data  The saved status data.
			 * @param SystemCheck $system_check System_Check instance.
			 */
			do_action( 'dokan_kits_system_status_updated', $status_data, $this );
		} catch ( Exception $e ) {
			// Just log the error and continue. Not being able to save the status
			// shouldn't prevent the plugin from operating.
			error_log( sprintf( 'Failed to save system status: %s', $e->getMessage() ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
		}
	}

	/**
	 * Get all error messages.
	 *
	 * @since 3.0.0
	 *
	 * @return array All error messages.
	 */
	public function get_all_errors(): array {
		$errors = array_merge(
			$this->php_errors,
			$this->wp_errors,
			$this->plugin_errors,
			$this->extension_errors
		);

		/**
		 * Filter all system check errors
		 *
		 * @since 3.0.0
		 *
		 * @param array $errors All collected error messages.
		 * @param SystemCheck $system_check System_Check instance.
		 */
		return apply_filters( 'dokan_kits_system_check_errors', $errors, $this );
	}

	/**
	 * Check if we need to display an admin notice.
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 * @throws Exception If system check fails.
	 */
	public function maybe_display_admin_notice(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		// Re-run the check to ensure we have fresh results.
		$check_result = $this->check();

		// Remove existing notice if requirements are met.
		if ( $check_result ) {
			$notices = dokan_kits_get_container()->get( Notices::class );
			$notices->remove_notice( $this->notice_id );
			return;
		}

		// Add notice with errors if requirements aren't met.
		$this->display_system_requirements_notice();
	}

	/**
	 * Display system requirements notice using the Notices class.
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	protected function display_system_requirements_notice(): void {
		$errors = $this->get_all_errors();

		if ( empty( $errors ) ) {
			return;
		}

		/**
		 * Filter the admin notice title
		 *
		 * @since 3.0.0
		 * @param string $title The notice title.
		 */
		$notice_title = apply_filters(
			'dokan_kits_system_check_notice_title',
			__( 'Dokan Kits - System Requirements Not Met', 'dokan-kits' )
		);

		// Build notice message with title and error list.
		$message  = sprintf( '<strong>%s</strong>', esc_html( $notice_title ) );
		$message .= '<ul class="dokan-kits-requirements-errors">';

		foreach ( $errors as $error ) {
			$message .= '<li>' . esc_html( $error ) . '</li>';
		}

		$message .= '</ul>';

		/**
		 * Filter the complete notice message
		 *
		 * @since 3.0.0
		 * @param string $message The formatted notice message.
		 * @param array  $errors  The error messages array.
		 */
		$message = apply_filters( 'dokan_kits_system_requirements_notice_message', $message, $errors );

		// Add notice using the Notices API.
		$notices = dokan_kits_get_container()->get( Notices::class );
		$notices->error( $message, true, $this->notice_id, true );

		/**
		 * Action hook that fires after system requirements notice is created
		 *
		 * @since 3.0.0
		 * @param array   $errors  The error messages.
		 * @param Notices $notices The notices instance.
		 */
		do_action( 'dokan_kits_system_requirements_notice_created', $errors, $notices );
	}
}
