<?php
/**
 * Admin notice handler.
 */

namespace StoreEngine\Admin;

use StoreEngine\ActionQueue;
use StoreEngine\Installer;
use StoreEngine\Traits\Singleton;
use StoreEngine\Utils\Formatting;
use StoreEngine\Utils\Helper;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Notices {
	use Singleton;

	const TYPE_INFO = 'info';

	const TYPE_SUCCESS = 'success';

	const TYPE_WARNING = 'warning';

	const TYPE_ERROR = 'error';

	private static array $notices = [];

	private static string $action = 'storeengine/hide-notice';

	private static string $option_name = 'storeengine_notices';

	protected function __construct() {

		add_action( 'init', [ $this, 'dispatch_notices' ] );
		add_filter( 'storeengine/backend_scripts_data', [ $this, 'add_notice_data' ] );
		add_action( 'admin_notices', [ $this, 'render_notices' ] );

		// Handle dismissal requests.
		add_action( 'admin_init', [ $this, 'handle_admin_request' ], 20 );
		add_action( 'wp_ajax_' . self::$action, [ $this, 'handle_admin_request' ] );
	}

	public function dispatch_notices() {
		Notices::show_get_pro_nag();

		if ( empty( get_option( 'permalink_structure' ) ) ) {
			$this->add_permalink_notice();
		}


		if ( ! Installer::is_new_install() ) {
			self::add_ablocks_notice();
		}

		if ( ! self::is_uploads_directory_protected() ) {
			self::add_unprotected_uploads_directory_notice();
		}

		$core_pages    = [
			'shop_page',
			'cart_page',
			'checkout_page',
			'thankyou_page',
			'dashboard_page',
		];
		$contents      = Helper::get_store_page_contents();
		$missing_pages = [];

		foreach ( $core_pages as $page ) {
			if ( ! Helper::get_settings( $page ) || ! get_post( Helper::get_settings( $page ) ) ) {
				$missing_pages[ $page ] = $contents[ $page ]['title'];
			}
		}

		if ( ! empty( $missing_pages ) ) {
			self::add_notice( 'uploads_directory_is_unprotected', [
				'type'          => self::TYPE_ERROR,
				'icon'          => 'info',
				'alt'           => false,
				'large'         => false,
				'classes'       => '',
				'title'         => '',
				'message'       => sprintf(
				// translators: %s. Missing pages.
					__( '<h3>Some core pages are missing.</h3><p>One or more required pages were not found: <code>%s</code>.</p>', 'storeengine' ),
					implode( ', ', $missing_pages )
				),
				'button_text'   => __( 'Generate Missing Pages', 'storeengine' ),
				'button_action' => admin_url( 'admin.php?page=storeengine-tools&path=pages' ),
				'dismissible'   => false,
			] );
		}
	}

	private function respond_error( $message, $code = 403 ) {
		if ( ! wp_doing_ajax() ) {
			wp_die( esc_html( $message ), esc_html__( 'Action failed.', 'storeengine' ), [ 'response' => $code ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		}

		wp_send_json_error( $message, $code );
	}

	private function respond_success( $message = null ) {
		if ( ! wp_doing_ajax() ) {
			wp_safe_redirect( remove_query_arg( [ 'action', 'security', 'notice' ] ) );
			die();
		}

		wp_send_json_success( $message );
	}

	public function handle_admin_request() {
		if ( isset( $_REQUEST['action'], $_REQUEST['security'] ) && ! empty( $_REQUEST['notice'] ) && self::$action === wp_unslash( $_REQUEST['action'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['security'] ) ), 'storeengine_nonce' ) ) {
				$this->respond_error( __( 'Action failed. Please refresh the page and retry.', 'storeengine' ) );
			}

			$notice     = sanitize_text_field( wp_unslash( $_REQUEST['notice'] ) );
			$capability = apply_filters( 'storeengine/admin_notice/dismissal_capability', 'manage_options', $notice );

			if ( ! current_user_can( $capability ) ) {
				$this->respond_error( __( "You don't have permission to do this.", 'storeengine' ), 403 );
			}

			if ( ! self::has_notice( $notice ) ) {
				$this->respond_success(); // Return the user. Notice may have already been removed (different tab).
			}

			self::remove_notice( $notice );

			$this->respond_success();
		}

	}

	public function add_notice_data( array $data ): array {
		return array_merge( $data, [ 'admin_notices' => array_values( self::get_notices() ) ] );
	}

	public function render_notices() {
		global $pagenow;

		$notices = self::get_notices();

		if ( ! empty( $notices ) ) {
			$has_dismissible = false;
			foreach ( $notices as $notice ) {
				$classes = array_merge(
					[
						'storeengine-admin-notice',
						'storeengine-admin-notice-' . $notice['type'],
						'notice',
						str_replace( [ '.' ], '-', $pagenow ),
						'storeengine-admin-notice--' . $notice['key'],

					],
					array_filter( $notice['classes'] )
				);

				switch ( $notice['type'] ) {
					case 'error':
						$classes[] = 'notice-error';
						break;
					case 'warning':
						$classes[] = 'notice-warning';
						break;
					case 'success':
						$classes[] = 'notice-success';
						break;
					case 'info':
					default:
						$classes[] = 'notice-info';
						break;
				}

				if ( $notice['large'] ) {
					$classes[] = 'notice-large';
				}

				if ( $notice['alt'] ) {
					$classes[] = 'notice-alt';
				}

				if ( $notice['dismissible'] ) {
					$classes[] = 'is-dismissible';
					if ( ! $has_dismissible ) {
						$has_dismissible = true;
					}
				}

				$classes = implode( ' ', $classes );

				include __DIR__ . '/notice-html.php';
			}

			if ( $has_dismissible ) {
				add_action( 'admin_footer', [ $this, 'add_notice_script' ], 100 );
			}
		}
	}

	public function add_notice_script() {
		?>
		<script>
			(
				( $ ) => {
					$( document ).on( 'click', '.storeengine-admin-notice-close.notice-dismiss', function( event ) {
						event.preventDefault();
						const notice = $( this ).data( 'notice' );
						$.post( '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>', {
							notice: notice,
							action: '<?php echo esc_js( self::$action ); ?>',
							security: '<?php echo esc_js( wp_create_nonce( 'storeengine_nonce' ) ); ?>',
						}, function( response ) {
							<?php if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { ?>
							console.log( 'Dismissible Response', {notice, response} );
							<?php } ?>
							$( '#storeengine-admin-notice-' + notice ).remove();
						} );
					} );
				}
			)( jQuery );
		</script>
		<?php
	}

	/**
	 * Add notice.
	 *
	 * @param string $notice_name
	 * @param array $args {
	 *
	 * @type string $type Notice type.
	 * @type string $icon Icon.
	 * @type bool $alt Render alt style (wp notice-alt class)
	 * @type string|string[] $classes Extra class.
	 * @type string $title Optional Title.
	 * @type string $message The Message.
	 * @type string $button_text Extra button text/label
	 * @type string $button_action Extra button action url
	 * @type bool $dismissible Show dismissible button.
	 * }
	 *
	 * @return void
	 */
	public static function add_notice( string $notice_name, array $args ) {
		$args = wp_parse_args( $args, [
			'type'          => self::TYPE_INFO,
			'icon'          => 'info',
			'alt'           => false,
			'large'         => false,
			'classes'       => '',
			'title'         => '',
			'message'       => '',
			'button_text'   => '',
			'button_action' => '',
			'button_class'  => '',
			'button_target' => '_self',
			'dismissible'   => false,
		] );

		// This is final.
		$args['key'] = $notice_name;

		$types = [ self::TYPE_INFO, self::TYPE_SUCCESS, self::TYPE_WARNING, self::TYPE_ERROR ];

		if ( ! in_array( $args['type'], $types, true ) ) {
			$args['type'] = 'info';
		}

		$args['alt']           = (bool) $args['alt'];
		$args['large']         = (bool) $args['large'];
		$args['dismissible']   = (bool) $args['dismissible'];
		$args['classes']       = $args['classes'] && ! is_array( $args['classes'] ) ? explode( ' ', $args['classes'] ) : [];
		$args['title']         = esc_html( $args['title'] );
		$args['message']       = wp_kses_post( wpautop( $args['message'] ) );
		$args['button_text']   = esc_html( $args['button_text'] );
		$args['button_action'] = sanitize_url( $args['button_action'] );
		$args['has_buttons']   = $args['dismissible'] || ( $args['button_text'] && $args['button_action'] );

		self::$notices[ $notice_name ] = $args;
	}

	public static function remove_notice( string $notice_name ) {
		$notice = self::$notices[ $notice_name ];

		unset( self::$notices[ $notice_name ] );

		if ( 'uploads_directory_is_unprotected' === $notice_name ) {
			// this will hide the notice for next 12 hours.
			set_transient( '_storeengine_upload_directory_status', 'protected', DAY_IN_SECONDS );
		}

		if ( $notice['dismissible'] ) {
			// Hide for the current user only.
			update_user_meta( get_current_user_id(), 'dismissed_' . $notice_name . '_notice', 'yes' );
		}

		do_action( 'storeengine/admin_notice/hide_' . $notice_name . '_notice' );
	}

	protected static function is_user_dismissed( string $notice_name ): bool {
		return Formatting::string_to_bool( get_user_meta( get_current_user_id(), 'dismissed_' . $notice_name . '_notice', true ) );
	}

	protected static function remove_user_dismissal( string $notice_name ) {
		delete_user_meta( get_current_user_id(), 'dismissed_' . $notice_name . '_notice' );
	}

	protected function maybe_inside_dashboard(): bool {
		return isset( $_REQUEST['page'] ) && str_starts_with( wp_unslash( $_REQUEST['page'] ), 'storeengine-' ) || wp_is_serving_rest_request(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
	}

	public static function get_notices(): array {
		return array_filter( self::$notices, fn( $notice ) => ! self::is_user_dismissed( $notice ), ARRAY_FILTER_USE_KEY );
	}

	public static function remove_all_notices() {
		self::$notices = [];
	}

	public static function has_notice( string $notice_name ): bool {
		return isset( self::$notices[ $notice_name ] );
	}

	public function add_permalink_notice() {
		if ( self::has_notice( 'update_permalink_settings' ) ) {
			return;
		}

		self::add_notice( 'update_permalink_settings', [
			'type'          => 'warning',
			'message'       => __( 'Your permalink settings is set to <code>plain</code>. Please update your permalink settings. StoreEngine works better with search engine friendly permalink.', 'storeengine' ),
			'button_text'   => __( 'Update Permalink', 'storeengine' ),
			'button_action' => admin_url( 'options-permalink.php' ),
		] );
	}

	/**
	 * Check if uploads directory is protected.
	 *
	 * @return bool
	 */
	protected static function is_uploads_directory_protected(): bool {
		$status = get_transient( '_storeengine_upload_directory_status' );

		// Check for cache.
		if ( false !== $status ) {
			return 'protected' === $status;
		}

		// retry creating index & .htaccess files.
		Installer::create_base_secure_directory();

		// Get only data from the uploads directory.
		$uploads = wp_get_upload_dir();

		// Check for the "uploads/storeengine_uploads" directory.
		$response         = wp_safe_remote_get( esc_url_raw( $uploads['baseurl'] . '/storeengine_uploads/' ), [ 'redirection' => 0 ] );
		$response_code    = intval( wp_remote_retrieve_response_code( $response ) );
		$response_content = wp_remote_retrieve_body( $response );

		// Check if returns 200 with empty content in case can open an index.html file,
		// and check for non-200 codes in case the directory is protected.
		$is_protected = ( 200 === $response_code && empty( $response_content ) ) || ( 200 !== $response_code );

		set_transient(
			'_storeengine_upload_directory_status',
			$is_protected ? 'protected' : 'unprotected',
			DAY_IN_SECONDS
		);

		return $is_protected;
	}

	public function add_unprotected_uploads_directory_notice() {
		if ( self::has_notice( 'uploads_directory_is_unprotected' ) ) {
			return;
		}

		$uploads = wp_get_upload_dir();

		/** @noinspection HtmlUnknownTarget */
		self::add_notice( 'uploads_directory_is_unprotected', [
			'type'        => 'error',
			'dismissible' => true,
			'message'     => sprintf(
			/* translators: 1: uploads directory URL 2: documentation URL */
				__( 'Your store\'s uploads directory is <a href="%1$s" target="_blank" rel="noopener">browsable via the web</a>. We strongly recommend <a href="%2$s" target="_blank" rel="noopener">configuring your web server to prevent directory indexing</a>.', 'storeengine' ),
				esc_url( $uploads['baseurl'] . '/storeengine_uploads' ),
				'https://storeengine.pro/docs/prevent-public-access-to-storeengine-uploads/'
			),
		] );
	}

	protected function maybe_show_ablocks_notice(): bool {
		if (
			isset( $_REQUEST['action'], $_REQUEST['plugin'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			'install-plugin' === sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			'ablocks' === sanitize_text_field( wp_unslash( $_REQUEST['plugin'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		) {
			return false;
		}

		return ! self::has_notice( 'install_ablocks' ) && ! Helper::is_plugin_active( 'ablocks/ablocks.php' ) && ! self::is_user_dismissed( 'install_ablocks' );
	}

	public function add_ablocks_notice() {
		if ( ! $this->maybe_show_ablocks_notice() ) {
			return;
		}

		if ( ! Helper::is_plugin_installed( 'ablocks/ablocks.php' ) ) {
			$type        = __( 'install & activate', 'storeengine' );
			$button      = __( 'Install & Activate aBlocks', 'storeengine' );
			$install_url = Helper::get_plugin_install_url( 'ablocks' );
		} else {
			$type        = __( 'activate', 'storeengine' );
			$button      = __( 'Activate aBlocks', 'storeengine' );
			$install_url = Helper::get_plugin_activation_url( 'ablocks/ablocks.php' );
		}

		/** @noinspection HtmlUnknownTarget */
		self::add_notice( 'install_ablocks', [
			'type'          => 'info',
			'dismissible'   => true,
			'message'       => sprintf(
			/* translators: %1$s: install or install & activate %2$s: aBlocks WordPress plugin repository URL. */
				__( '<b>StoreEngine</b> offers 20+ Gutenberg Blocks powered by <a href="%2$s" target="_blank" rel="noopener">aBlocks</a>. Please %1$s <a href="%2$s" target="_blank" rel="noopener">aBlocks</a> to use them.', 'storeengine' ),
				$type,
				'https://wordpress.org/plugins/ablocks/'
			),
			'button_text'   => $button,
			'button_action' => $install_url,
			'button_target' => $this->maybe_inside_dashboard() ? '_blank' : '',
		] );
	}

	public static function show_get_pro_nag() {
		if ( self::has_notice( 'se-get-pro-nag' ) || defined( 'STOREENGINE_PRO_VERSION' ) ) {
			return;
		}

		$link = add_query_arg(
			[
				'utm_source'   => 'storeengine-plugin',
				'utm_medium'   => 'admin-notice',
				'utm_campaign' => 'upgrade-to-pro',
				'utm_content'  => 'get-pro-notice',
				'utm_term'     => 'free-user',
				'locale'       => get_locale(),
			],
			'https://storeengine.pro/pricing'
		);

		self::add_notice( 'se-get-pro-nag', [
			'type'          => self::TYPE_INFO,
			'large'         => true,
			'dismissible'   => true,
			'title'         => '',
			'message'       => '<h3>'.__( 'Unlock more with StoreEngine Pro 🚀', 'storeengine' ) .'</h3><p>'.__( 'Upgrade to <strong>StoreEngine Pro</strong> to unlock powerful features and get priority support—so you can build faster, customize more, and grow without limitations.', 'storeengine' ) . '</p>',
			'button_text'   => __( 'Get Pro', 'storeengine' ),
			'button_action' => $link,
			'button_target' => '_blank',
		] );
	}

	public static function remove_get_pro_nag() {
		self::remove_notice( 'se-get-pro-nag' );
	}
}
