<?php
/**
 * RISE2 self-hosted plugin updater for R2 Media Offload.
 *
 * Checks plugins.rise2.studio for updates and integrates with WordPress core:
 * updates appear under Dashboard → Updates and a "Check for updates" link is
 * added on the Plugins page. Downloads are restricted to the trusted host and
 * manual checks are nonce- and capability-gated.
 *
 * @package R2_Offload
 */

namespace R2_Offload;

defined( 'ABSPATH' ) || exit;

class Updater {

	/** @var string */
	private $slug;
	/** @var string */
	private $version;
	/** @var string */
	private $basename;
	/** @var string */
	private $update_url;
	/** @var string */
	private $cache_key;
	/** @var int */
	private $cache_ttl;
	/** @var object|false|null */
	private $remote_data = null;

	private const TRUSTED_DOWNLOAD_HOST = 'plugins.rise2.studio';

	/**
	 * @param array $args { slug, version, basename, update_url, cache_ttl? }
	 */
	public function __construct( array $args ) {
		$this->slug       = $args['slug'];
		$this->version    = $args['version'];
		$this->basename   = $args['basename'];
		$this->update_url = $args['update_url'];
		$this->cache_ttl  = $args['cache_ttl'] ?? 21600;
		$this->cache_key  = 'r2_updater_' . $this->slug;

		add_filter( 'plugins_api', array( $this, 'plugin_info' ), 20, 3 );
		add_filter( 'site_transient_update_plugins', array( $this, 'check_update' ) );
		add_action( 'upgrader_process_complete', array( $this, 'clear_cache' ), 10, 2 );

		add_filter( 'plugin_action_links_' . $this->basename, array( $this, 'add_check_link' ) );
		add_action( 'admin_init', array( $this, 'handle_manual_check' ) );
		add_action( 'admin_notices', array( $this, 'checked_notice' ) );
	}

	/** Validate that a download URL points to the trusted domain over HTTPS. */
	private function validate_download_url( $url ) {
		if ( empty( $url ) || ! is_string( $url ) ) {
			return '';
		}
		$parsed = wp_parse_url( $url );
		if (
			empty( $parsed['scheme'] ) || 'https' !== $parsed['scheme'] ||
			empty( $parsed['host'] ) || self::TRUSTED_DOWNLOAD_HOST !== $parsed['host']
		) {
			return '';
		}
		return esc_url_raw( $url );
	}

	/** Fetch remote plugin info (cached). */
	private function get_remote_data( $force_refresh = false ) {
		if ( null !== $this->remote_data && ! $force_refresh ) {
			return $this->remote_data;
		}
		if ( ! $force_refresh ) {
			$cached = get_transient( $this->cache_key );
			if ( false !== $cached ) {
				$this->remote_data = $cached;
				return $cached;
			}
		}

		$response = wp_remote_get(
			$this->update_url,
			array(
				'timeout'    => 15,
				'sslverify'  => true,
				'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . home_url(),
				'headers'    => array( 'Accept' => 'application/json' ),
			)
		);

		if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
			set_transient( $this->cache_key, false, 300 );
			$this->remote_data = false;
			return false;
		}

		$data = json_decode( wp_remote_retrieve_body( $response ) );
		if ( empty( $data ) || ! isset( $data->version ) ) {
			set_transient( $this->cache_key, false, 300 );
			$this->remote_data = false;
			return false;
		}

		set_transient( $this->cache_key, $data, $this->cache_ttl );
		$this->remote_data = $data;
		return $data;
	}

	/** Inject update info into WP's update_plugins transient. */
	public function check_update( $transient ) {
		if ( empty( $transient->checked ) ) {
			return $transient;
		}
		$remote = $this->get_remote_data();
		if ( ! $remote || ! isset( $remote->version ) ) {
			return $transient;
		}

		if ( version_compare( $this->version, $remote->version, '<' ) ) {
			$update               = new \stdClass();
			$update->slug         = $this->slug;
			$update->plugin       = $this->basename;
			$update->new_version  = $remote->version;
			$update->tested       = $remote->tested ?? '';
			$update->requires     = $remote->requires ?? '';
			$update->requires_php = $remote->requires_php ?? '';
			$update->url          = $remote->homepage ?? '';
			$update->package      = $this->validate_download_url( $remote->download_url ?? '' );

			if ( ! empty( $remote->icons ) ) {
				$update->icons = (array) $remote->icons;
			}
			if ( ! empty( $remote->banners ) ) {
				$update->banners = (array) $remote->banners;
			}

			$transient->response[ $this->basename ] = $update;
		} else {
			$no_update              = new \stdClass();
			$no_update->slug        = $this->slug;
			$no_update->plugin      = $this->basename;
			$no_update->new_version = $this->version;
			$no_update->url         = '';
			$no_update->package     = '';
			$transient->no_update[ $this->basename ] = $no_update;
		}

		return $transient;
	}

	/** Provide full plugin info for the "View details" popup. */
	public function plugin_info( $result, $action, $args ) {
		if ( 'plugin_information' !== $action ) {
			return $result;
		}
		if ( ! isset( $args->slug ) || $args->slug !== $this->slug ) {
			return $result;
		}
		$remote = $this->get_remote_data();
		if ( ! $remote ) {
			return $result;
		}

		$info                 = new \stdClass();
		$info->name           = sanitize_text_field( $remote->name ?? $this->slug );
		$info->slug           = $this->slug;
		$info->version        = sanitize_text_field( $remote->version );
		$info->author         = sanitize_text_field( $remote->author ?? '' );
		$info->author_profile = esc_url_raw( $remote->author_profile ?? '' );
		$info->homepage       = esc_url_raw( $remote->homepage ?? '' );
		$info->requires       = sanitize_text_field( $remote->requires ?? '' );
		$info->tested         = sanitize_text_field( $remote->tested ?? '' );
		$info->requires_php   = sanitize_text_field( $remote->requires_php ?? '' );
		$info->download_link  = $this->validate_download_url( $remote->download_url ?? '' );
		$info->last_updated   = sanitize_text_field( $remote->last_updated ?? '' );

		if ( ! empty( $remote->sections ) ) {
			$sections = (array) $remote->sections;
			foreach ( $sections as $key => $value ) {
				$sections[ sanitize_key( $key ) ] = wp_kses_post( $value );
			}
			$info->sections = $sections;
		}
		if ( ! empty( $remote->banners ) ) {
			$info->banners = (array) $remote->banners;
		}
		if ( ! empty( $remote->icons ) ) {
			$info->icons = (array) $remote->icons;
		}

		return $info;
	}

	/** Add "Check for updates" link next to Deactivate on the Plugins page. */
	public function add_check_link( $links ) {
		$check_url = wp_nonce_url(
			add_query_arg( 'r2_force_check', $this->slug, admin_url( 'plugins.php' ) ),
			'r2_force_check_' . $this->slug
		);
		$links[] = '<a href="' . esc_url( $check_url ) . '">' . esc_html__( 'Check for updates', 'r2-offload' ) . '</a>';
		return $links;
	}

	/** Show admin notice after a manual update check. */
	public function checked_notice() {
		$checked = isset( $_GET['r2_checked'] ) ? sanitize_text_field( wp_unslash( $_GET['r2_checked'] ) ) : '';
		if ( $checked !== $this->slug ) {
			return;
		}
		$screen = get_current_screen();
		if ( ! $screen || 'plugins' !== $screen->id ) {
			return;
		}
		$remote = $this->get_remote_data();
		echo '<div class="notice notice-success is-dismissible"><p>';
		echo esc_html( ( $remote->name ?? $this->slug ) . ': ' );
		if ( $remote && ! empty( $remote->version ) && version_compare( $this->version, $remote->version, '<' ) ) {
			/* translators: %s: available version. */
			echo '<strong>' . sprintf( esc_html__( 'Version %s is available!', 'r2-offload' ), esc_html( $remote->version ) ) . '</strong>';
		} else {
			echo esc_html__( 'You are up to date.', 'r2-offload' ) . ' (v' . esc_html( $this->version ) . ')';
		}
		echo '</p></div>';
	}

	/** Handle the manual "Check for updates" click. */
	public function handle_manual_check() {
		if ( ! isset( $_GET['r2_force_check'] ) || $_GET['r2_force_check'] !== $this->slug ) {
			return;
		}
		if ( ! current_user_can( 'update_plugins' ) ) {
			return;
		}
		check_admin_referer( 'r2_force_check_' . $this->slug );

		delete_transient( $this->cache_key );
		delete_site_transient( 'update_plugins' );

		wp_safe_redirect( add_query_arg( 'r2_checked', $this->slug, admin_url( 'plugins.php' ) ) );
		exit;
	}

	/** Clear the cache after this plugin updates. */
	public function clear_cache( $upgrader, $options ) {
		if ( 'update' === ( $options['action'] ?? '' ) && 'plugin' === ( $options['type'] ?? '' ) ) {
			if ( in_array( $this->basename, (array) ( $options['plugins'] ?? array() ), true ) ) {
				delete_transient( $this->cache_key );
			}
		}
	}
}
