<?php
/**
 * Botcrawl Bot Blocker - WordPress Update Checker
 *
 * Hooks into WordPress's native plugin update mechanism so that:
 *  1. The WP dashboard shows an "Update available" notice when a new version
 *     is published on sera.guru.
 *  2. Admins can read the changelog in the "View version X.X.X details" modal
 *     without leaving wp-admin.
 *  3. One-click (or auto-update) installs the new zip directly from sera.guru,
 *     identical to how free plugins from wordpress.org update.
 *
 * The update manifest endpoint is:
 *   GET https://sera.guru/api/plugin-update?plugin=botcrawl-bot-blocker
 *
 * Expected JSON response shape:
 * {
 *   "version":      "1.2.2",
 *   "download_url": "https://cdn.sera.guru/plugins/sera-bot-blocker-1.2.2.zip",
 *   "details_url":  "https://sera.guru/products/botcrawl-bot-blocker/",
 *   "tested":       "6.8",
 *   "requires":     "5.9",
 *   "requires_php": "7.4",
 *   "last_updated": "2026-04-22",
 *   "changelog":    "<h4>1.2.2<\/h4><ul><li>...<\/li><\/ul>"
 * }
 *
 * @package SeraBotBlocker
 */

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

class BotBlocker_Updater {

	const UPDATE_URL   = 'https://sera.guru/api/plugin-update';
	const PLUGIN_SLUG  = 'botcrawl-bot-blocker';
	const CACHE_KEY    = 'sera_bb_update_check';
	const CACHE_TTL    = 12 * HOUR_IN_SECONDS;

	/**
	 * Register all WordPress hooks.
	 */
	public static function init(): void {
		// Inject update info into the WP update transient.
		add_filter(
			'pre_set_site_transient_update_plugins',
			[ __CLASS__, 'check_for_update' ]
		);

		// Provide plugin info for the "View details" modal.
		add_filter(
			'plugins_api',
			[ __CLASS__, 'plugin_info' ],
			10,
			3
		);

		// Allow auto-updates (WP 5.5+): return true to enable the toggle.
		add_filter(
			'auto_update_plugin',
			[ __CLASS__, 'allow_auto_update' ],
			10,
			2
		);

		// Clear cached manifest after a successful update so the next check
		// fetches a fresh response.
		add_action(
			'upgrader_process_complete',
			[ __CLASS__, 'clear_cache_after_update' ],
			10,
			2
		);

		// Show an admin notice on the plugin's own pages when an update is available.
		add_action( 'admin_notices',         [ __CLASS__, 'maybe_show_update_notice' ] );
		add_action( 'network_admin_notices', [ __CLASS__, 'maybe_show_update_notice' ] );
	}

	// -------------------------------------------------------------------------
	// Core: fetch manifest from sera.guru
	// -------------------------------------------------------------------------

	/**
	 * Fetch (or return cached) update manifest from sera.guru.
	 *
	 * @return array|null  Parsed manifest array, or null on failure.
	 */
	private static function get_manifest(): ?array {
		$cached = get_transient( self::CACHE_KEY );
		if ( is_array( $cached ) ) {
			return $cached;
		}

		$url      = add_query_arg( 'plugin', self::PLUGIN_SLUG, self::UPDATE_URL );
		$response = wp_remote_get( $url, [
			'timeout'    => 10,
			'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . home_url(),
		] );

		if ( is_wp_error( $response ) ) {
			// Cache failure briefly so we don't hammer the server.
			set_transient( self::CACHE_KEY, [], HOUR_IN_SECONDS );
			return null;
		}

		$code = wp_remote_retrieve_response_code( $response );
		if ( 200 !== (int) $code ) {
			set_transient( self::CACHE_KEY, [], HOUR_IN_SECONDS );
			return null;
		}

		$body = wp_remote_retrieve_body( $response );
		$data = json_decode( $body, true );

		if ( ! is_array( $data ) || empty( $data['version'] ) || empty( $data['download_url'] ) ) {
			set_transient( self::CACHE_KEY, [], HOUR_IN_SECONDS );
			return null;
		}

		set_transient( self::CACHE_KEY, $data, self::CACHE_TTL );
		return $data;
	}

	// -------------------------------------------------------------------------
	// Hook: pre_set_site_transient_update_plugins
	// -------------------------------------------------------------------------

	/**
	 * Inject our plugin's update info into WordPress's update transient.
	 *
	 * @param  object $transient  The current update_plugins transient.
	 * @return object             Modified transient.
	 */
	public static function check_for_update( $transient ) {
		if ( empty( $transient->checked ) ) {
			return $transient;
		}

		$manifest = self::get_manifest();
		if ( ! $manifest ) {
			return $transient;
		}

		$plugin_file    = plugin_basename( BOT_BLOCKER_FILE );
		$current_version = defined( 'BOTCRAWL_BOT_BLOCKER_VERSION' )
			? BOTCRAWL_BOT_BLOCKER_VERSION
			: '0.0.0';

		if ( version_compare( $current_version, $manifest['version'], '<' ) ) {
			$update = (object) [
				'id'            => self::PLUGIN_SLUG . '/' . self::PLUGIN_SLUG . '.php',
				'slug'          => self::PLUGIN_SLUG,
				'plugin'        => $plugin_file,
				'new_version'   => $manifest['version'],
				'url'           => $manifest['details_url'] ?? 'https://sera.guru/products/botcrawl-bot-blocker/',
				'package'       => $manifest['download_url'],
				'icons'         => [],
				'banners'       => [],
				'banners_rtl'   => [],
				'tested'        => $manifest['tested'] ?? '',
				'requires_php'  => $manifest['requires_php'] ?? '7.4',
				'compatibility' => new stdClass(),
			];

			$transient->response[ $plugin_file ] = $update;
		}

		return $transient;
	}

	// -------------------------------------------------------------------------
	// Hook: plugins_api
	// -------------------------------------------------------------------------

	/**
	 * Provide plugin information for the "View version X.X.X details" modal.
	 *
	 * @param  false|object $result  Default false.
	 * @param  string       $action  API action.
	 * @param  object       $args    Request arguments.
	 * @return false|object          Plugin info object or false to pass through.
	 */
	public static function plugin_info( $result, string $action, $args ) {
		if ( 'plugin_information' !== $action ) {
			return $result;
		}

		if ( ! isset( $args->slug ) || $args->slug !== self::PLUGIN_SLUG ) {
			return $result;
		}

		$manifest = self::get_manifest();
		if ( ! $manifest ) {
			return $result;
		}

		$info = (object) [
			'name'              => 'Botcrawl Bot Blocker',
			'slug'              => self::PLUGIN_SLUG,
			'version'           => $manifest['version'],
			'author'            => '<a href="https://sera.guru">Sera.guru</a>',
			'author_profile'    => 'https://sera.guru',
			'requires'          => $manifest['requires'] ?? '5.9',
			'tested'            => $manifest['tested'] ?? '',
			'requires_php'      => $manifest['requires_php'] ?? '7.4',
			'last_updated'      => $manifest['last_updated'] ?? '',
			'homepage'          => 'https://sera.guru/products/botcrawl-bot-blocker/',
			'short_description' => 'Professional bot detection, enforcement, live monitoring, and geo-analytics for WordPress.',
			'sections'          => [
				'description' => '<p>Botcrawl Bot Blocker detects and blocks malicious bots, AI scrapers, vulnerability scanners, and spam bots in real time. Features include a live monitor with geo-analytics, DNS identity verification for trusted crawlers, and a 600+ bot registry updated with every release.</p>',
				'changelog'   => $manifest['changelog'] ?? '<p>See <a href="https://sera.guru/products/botcrawl-bot-blocker/">sera.guru</a> for the full changelog.</p>',
				'faq'         => '<dl><dt>Does this affect legitimate search engine bots?</dt><dd>No. Googlebot, Bingbot, and other verified crawlers are always allowed through by default.</dd></dl>',
			],
			'download_link'     => $manifest['download_url'],
			'banners'           => [],
			'icons'             => [],
		];

		return $info;
	}

	// -------------------------------------------------------------------------
	// Hook: auto_update_plugin
	// -------------------------------------------------------------------------

	/**
	 * Allow WordPress auto-updates for this plugin.
	 *
	 * This enables the "Enable auto-updates" toggle in the Plugins list table
	 * (WP 5.5+). The site admin still controls whether auto-updates are on or
	 * off - this filter just makes the option available.
	 *
	 * @param  bool|null $update  Current decision.
	 * @param  object    $item    Plugin update object.
	 * @return bool|null          True to allow, or pass through.
	 */
	public static function allow_auto_update( $update, $item ) {
		if ( isset( $item->slug ) && $item->slug === self::PLUGIN_SLUG ) {
			// Respect the site's stored auto-update preference for this plugin.
			$auto_updates = (array) get_site_option( 'auto_update_plugins', [] );
			$plugin_file  = plugin_basename( BOT_BLOCKER_FILE );

			if ( in_array( $plugin_file, $auto_updates, true ) ) {
				return true;
			}
		}

		return $update;
	}

	// -------------------------------------------------------------------------
	// Hook: admin_notices
	// -------------------------------------------------------------------------

	/**
	 * Show a standard WP admin notice when a new version is available.
	 * Only shown on the plugin's own admin pages to avoid noise elsewhere.
	 */
	public static function maybe_show_update_notice(): void {
		if ( ! current_user_can( 'update_plugins' ) ) {
			return;
		}

		$screen = get_current_screen();
		if ( ! $screen || strpos( $screen->id, 'bot-blocker' ) === false ) {
			return;
		}

		$manifest = self::get_manifest();
		if ( ! $manifest ) {
			return;
		}

		$current = defined( 'BOTCRAWL_BOT_BLOCKER_VERSION' ) ? BOTCRAWL_BOT_BLOCKER_VERSION : '0.0.0';
		if ( ! version_compare( $current, $manifest['version'], '<' ) ) {
			return;
		}

		$update_url = wp_nonce_url(
			self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . urlencode( plugin_basename( BOT_BLOCKER_FILE ) ) ),
			'upgrade-plugin_' . plugin_basename( BOT_BLOCKER_FILE )
		);
		printf(
			'<div class="notice notice-warning"><p><strong>Botcrawl Bot Blocker %s is available.</strong> <a href="%s">Update now</a> or <a href="%s" target="_blank">view changelog</a>.</p></div>',
			esc_html( $manifest['version'] ),
			esc_url( $update_url ),
			esc_url( $manifest['details_url'] ?? 'https://sera.guru/products/botcrawl-bot-blocker/' )
		);
	}

	// -------------------------------------------------------------------------
	// Hook: upgrader_process_complete
	// -------------------------------------------------------------------------

	/**
	 * Clear the cached manifest after a successful plugin update.
	 *
	 * @param WP_Upgrader $upgrader  Upgrader instance.
	 * @param array       $options   Upgrade options.
	 */
	public static function clear_cache_after_update( $upgrader, array $options ): void {
		if (
			isset( $options['action'], $options['type'], $options['plugins'] )
			&& 'update' === $options['action']
			&& 'plugin' === $options['type']
			&& in_array( plugin_basename( BOT_BLOCKER_FILE ), (array) $options['plugins'], true )
		) {
			delete_transient( self::CACHE_KEY );
		}
	}
}
