<?php
/**
 * PS_Label_Autoconfig — TIER 1 plug-and-play installer features.
 *
 * Implements 4 self-configuration mechanisms so the plugin "just works"
 * on a fresh shop with zero manual configuration:
 *
 *   1. WooCommerce dependency (declared via the "Requires Plugins" header)
 *   2. WP cron fallback (admin AJAX heartbeat when DISABLE_WP_CRON=true)
 *   3. Oracle URL multi-pool with health check + auto-failover
 *   4. Auto installation_id (UUID v4) generation
 *
 * Note: the SOL wallet that receives label payments is NOT configured by the
 * shop. It is provisioned automatically by the Oracle during enrollment — the
 * merchant only collects the SOL fee for the label, never supplies a wallet.
 *
 * Wired from parcselect_activate() + 'init' / 'admin_notices' hooks.
 *
 * @package ParcelSelectorFree
 * @since   4.3.8
 */

defined( 'ABSPATH' ) || exit;

final class Parcselect_Label_Autoconfig {

	/**
	 * Hardcoded Oracle pool — first reachable URL wins on health check.
	 * Filterable via 'parcselect_oracle_pool' so power users can override.
	 */
	const ORACLE_POOL = array(
		'https://api.parcelselector.com',
		'https://etykietownik.parcelselector.com',
	);

	const HEALTH_CACHE_OPTION = 'parcselect_oracle_health_cache';
	const HEALTH_CACHE_TTL    = 300; // 5 min

	/**
	 * Boot — register hooks.
	 */
	public static function init() {
		add_action( 'admin_notices',  array( __CLASS__, 'render_admin_notices' ) );
		add_action( 'admin_footer',   array( __CLASS__, 'render_fakecron_heartbeat' ) );
		add_action( 'wp_ajax_parcselect_fakecron_tick', array( __CLASS__, 'ajax_fakecron_tick' ) );
		add_action( 'parcselect_oracle_health_check',   array( __CLASS__, 'do_oracle_health_check' ) );

		// Schedule oracle health check every 5 min if not already scheduled.
		if ( ! wp_next_scheduled( 'parcselect_oracle_health_check' ) ) {
			wp_schedule_event( time() + 60, 'parcselect_label_minute', 'parcselect_oracle_health_check' );
		}
	}

	// ─────────────────────────────────────────────────────────────────────
	// 1. ACTIVATION — runs once from parcselect_activate()
	// ─────────────────────────────────────────────────────────────────────

	/**
	 * Master activation routine — runs all 5 auto-config steps.
	 * Idempotent: safe to run on every activation.
	 */
	public static function on_activation() {
		self::ensure_installation_id();
		self::pick_best_oracle_url();
	}

	/**
	 * (4) Auto installation_id — generate UUID v4 if no canonical or legacy id exists.
	 * Required by Oracle webhook OUT (NS-49 fix).
	 */
	public static function ensure_installation_id() {
		$canonical = (string) get_option( 'parcselect_installation_id', '' );
		if ( '' !== $canonical ) {
			return $canonical;
		}
		// Check legacy keys before generating new UUID — preserves existing identity.
		foreach ( array( 'parcselect_installation_id', 'parcselect_label_installation_id' ) as $legacy ) {
			$v = (string) get_option( $legacy, '' );
			if ( '' !== $v ) {
				update_option( 'parcselect_installation_id', $v, false );
				return $v;
			}
		}
		// Fresh generate.
		// NS-84: WP.org Plugin Check requires wp_rand() instead of mt_rand() (more secure).
		$uuid = function_exists( 'wp_generate_uuid4' )
			? wp_generate_uuid4()
			: sprintf(
				'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
				wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ),
				wp_rand( 0, 0xffff ),
				wp_rand( 0, 0x0fff ) | 0x4000,
				wp_rand( 0, 0x3fff ) | 0x8000,
				wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff )
			);
		update_option( 'parcselect_installation_id', $uuid, false );
		return $uuid;
	}

	// ─────────────────────────────────────────────────────────────────────
	// 2. ORACLE POOL — pick best, cache, scheduled re-check
	// ─────────────────────────────────────────────────────────────────────

	/**
	 * (3) Pick first reachable Oracle URL from pool. Cache result for 5 min.
	 * Falls back to the first pool entry if all health checks fail (so plugin
	 * still has a non-empty URL — Oracle will return errors, but the plugin
	 * surface remains intact).
	 */
	public static function pick_best_oracle_url() {
		$pool = apply_filters( 'parcselect_oracle_pool', self::ORACLE_POOL );
		if ( empty( $pool ) || ! is_array( $pool ) ) {
			return get_option( 'parcselect_label_oracle_url', '' );
		}

		$cache = get_transient( self::HEALTH_CACHE_OPTION );
		if ( is_array( $cache ) && ! empty( $cache['url'] ) ) {
			update_option( 'parcselect_label_oracle_url', $cache['url'], false );
			return $cache['url'];
		}

		$best     = '';
		$best_lat = PHP_INT_MAX;
		$results  = array();
		foreach ( $pool as $url ) {
			$url   = rtrim( (string) $url, '/' );
			$start = microtime( true );
			$resp  = wp_remote_get(
				$url . '/api/v1/health',
				array( 'timeout' => 5, 'sslverify' => true, 'redirection' => 2 )
			);
			$lat = (int) ( ( microtime( true ) - $start ) * 1000 );
			if ( ! is_wp_error( $resp ) && wp_remote_retrieve_response_code( $resp ) === 200 ) {
				$results[ $url ] = $lat;
				if ( $lat < $best_lat ) {
					$best     = $url;
					$best_lat = $lat;
				}
			}
		}

		if ( '' === $best ) {
			$best     = rtrim( (string) $pool[0], '/' );
			$best_lat = -1;
		}

		set_transient(
			self::HEALTH_CACHE_OPTION,
			array( 'url' => $best, 'latency_ms' => $best_lat, 'all' => $results, 'checked_at' => time() ),
			self::HEALTH_CACHE_TTL
		);
		update_option( 'parcselect_label_oracle_url', $best, false );
		return $best;
	}

	/**
	 * Cron handler — periodic health re-check (every 5 min via parcselect_label_minute).
	 */
	public static function do_oracle_health_check() {
		delete_transient( self::HEALTH_CACHE_OPTION );
		self::pick_best_oracle_url();
	}

	// ─────────────────────────────────────────────────────────────────────
	// WooCommerce dependency is declared via the "Requires Plugins: woocommerce"
	// header in parcel-selector.php (WP 6.5+ Plugin Dependencies). No automatic
	// install/activate of other plugins from our code — per WP.org Guideline 11
	// (no changing other plugins' state without explicit user action).
	// ─────────────────────────────────────────────────────────────────────

	// ─────────────────────────────────────────────────────────────────────
	// 4. FAKE-CRON FALLBACK (when DISABLE_WP_CRON=true)
	// ─────────────────────────────────────────────────────────────────────

	/**
	 * (2) Render JS heartbeat in admin footer that pings AJAX every 60s.
	 * Triggers parcselect_label_payment_sweep when due.
	 * Only renders when DISABLE_WP_CRON=true (otherwise normal WP cron handles it).
	 */
	public static function render_fakecron_heartbeat() {
		if ( ! defined( 'DISABLE_WP_CRON' ) || ! DISABLE_WP_CRON ) {
			return;
		}
		if ( ! is_admin() || ! is_user_logged_in() ) {
			return;
		}
		$nonce    = wp_create_nonce( 'parcselect_fakecron' );
		$ajax_url = admin_url( 'admin-ajax.php' );

		$js  = '(function(){';
		$js .=   'if(window.__parcselect_fakecron_started)return;';
		$js .=   'window.__parcselect_fakecron_started=true;';
		$js .=   'function tick(){try{';
		$js .=     'var fd=new FormData();';
		$js .=     'fd.append("action","parcselect_fakecron_tick");';
		$js .=     'fd.append("nonce",' . wp_json_encode( $nonce ) . ');';
		$js .=     'fetch(' . wp_json_encode( $ajax_url ) . ',{method:"POST",body:fd,credentials:"same-origin",keepalive:true}).catch(function(){});';
		$js .=   '}catch(e){}}';
		$js .=   'setTimeout(tick,5000);';
		$js .=   'setInterval(tick,60000);';
		$js .= '})();';

		// Use a dummy registered handle so WordPress emits the script through the
		// normal pipeline instead of bare <script> echo (WP.org Plugin Check rule).
		if ( ! wp_script_is( 'parcselect-fakecron', 'registered' ) ) {
			wp_register_script( 'parcselect-fakecron', '', array(), PARCSELECT_VERSION, true );
		}
		wp_enqueue_script( 'parcselect-fakecron' );
		wp_add_inline_script( 'parcselect-fakecron', $js );
	}

	/**
	 * AJAX endpoint — runs payment sweep when due.
	 * Idempotent: rate-limited to once per 50s via transient.
	 */
	public static function ajax_fakecron_tick() {
		if ( ! check_ajax_referer( 'parcselect_fakecron', 'nonce', false ) ) {
			wp_send_json_error( array( 'message' => 'bad_nonce' ), 403 );
		}
		// Rate limit so multiple admin tabs don't hammer.
		if ( get_transient( 'parcselect_fakecron_lock' ) ) {
			wp_send_json_success( array( 'skipped' => 'locked' ) );
		}
		set_transient( 'parcselect_fakecron_lock', 1, 50 );

		// Fire the same handlers WP cron would have fired.
		do_action( 'parcselect_label_payment_sweep' );
		do_action( 'parcselect_oracle_health_check' );

		wp_send_json_success( array( 'ran' => true, 'at' => wp_date( 'c' ) ) );
	}

	// ─────────────────────────────────────────────────────────────────────
	// 5. ADMIN NOTICES — status checklist
	// ─────────────────────────────────────────────────────────────────────

	/**
	 * Render aggregated checklist notice on plugin pages + first 3 admin loads.
	 * Self-dismissing once all checks pass.
	 */
	public static function render_admin_notices() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		// Show on plugins page, dashboard, and our own settings pages.
		$show_screens = array( 'dashboard', 'plugins', 'toplevel_page_parcelselector-settings' );
		if ( $screen && ! in_array( $screen->id, $show_screens, true ) && false === strpos( (string) $screen->id, 'parcelselector' ) ) {
			return;
		}
		// Suppress on our own settings page if dismissed.
		if ( '1' === (string) get_user_meta( get_current_user_id(), 'parcselect_autoconfig_dismissed', true ) ) {
			return;
		}

		$checks = self::run_status_checks();
		$pass   = array_filter( $checks, function( $c ) { return $c['ok']; } );
		if ( count( $pass ) === count( $checks ) ) {
			return; // All green — no need to nag.
		}
		?>
		<div class="notice notice-warning is-dismissible parcsepa-autoconfig-notice" style="border-left-color:#f59e0b;padding:12px 16px;">
			<p style="margin:0 0 8px;font-weight:600;font-size:14px">
				<?php /* translators: 1: number of passed checks, 2: total number of checks */ printf( esc_html__( '⚙️ Parcel Selector — shop configuration (%1$d/%2$d ready)', 'parcel-selector' ), (int) count( $pass ), (int) count( $checks ) ); ?>
			</p>
			<ul style="margin:6px 0 6px 4px;padding:0;list-style:none;font-size:13px;line-height:1.6">
				<?php foreach ( $checks as $c ) : ?>
					<li>
						<span style="display:inline-block;width:18px"><?php echo $c['ok'] ? '✅' : '⚠️'; ?></span>
						<strong><?php echo esc_html( $c['label'] ); ?></strong>
						— <?php echo wp_kses_post( $c['detail'] ); ?>
						<?php if ( ! $c['ok'] && ! empty( $c['action_url'] ) ) : ?>
							<a href="<?php echo esc_url( $c['action_url'] ); ?>" style="margin-left:6px"><?php echo esc_html( $c['action_label'] ?? __( 'Fix', 'parcel-selector' ) ); ?></a>
						<?php endif; ?>
					</li>
				<?php endforeach; ?>
			</ul>
			<p style="margin:8px 0 0;font-size:12px;color:#6b7280">
				<?php esc_html_e( 'These requirements are verified every 5 minutes and will disappear automatically once all are green.', 'parcel-selector' ); ?>
				<?php esc_html_e( 'The parcel locker map works even without them. Stage 4 (SOL payment) requires all ✅.', 'parcel-selector' ); ?>
			</p>
		</div>
		<?php
	}

	/**
	 * Run all 4 status checks. Returns array of [ok, label, detail, action_url, action_label].
	 */
	public static function run_status_checks() {
		$out = array();

		// 1. WooCommerce active
		$wc_ok = class_exists( 'WooCommerce' );
		$out[] = array(
			'ok'           => $wc_ok,
			'label'        => 'WooCommerce',
			'detail'       => $wc_ok
				? sprintf( /* translators: %s = WooCommerce version */ __( 'active (%s)', 'parcel-selector' ), esc_html( defined( 'WC_VERSION' ) ? WC_VERSION : '?' ) )
				: __( 'the WooCommerce plugin is required — it will be installed automatically on the next admin refresh.', 'parcel-selector' ),
			'action_url'   => $wc_ok ? '' : admin_url( 'plugin-install.php?s=woocommerce&tab=search&type=term' ),
			'action_label' => __( 'Install now', 'parcel-selector' ),
		);

		// 2. WP cron OR fakecron fallback
		$cron_disabled = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON;
		$cron_ok       = ! $cron_disabled || true; // fakecron renders silently when disabled
		$out[] = array(
			'ok'     => $cron_ok,
			'label'  => 'WP Cron',
			'detail' => $cron_disabled
				? __( 'WP cron disabled — we use a fakecron heartbeat (works when an admin is logged in).', 'parcel-selector' )
				: __( 'active (standard wp-cron.php).', 'parcel-selector' ),
		);

		// 3. HTTPS
		$https_ok = is_ssl();
		$out[] = array(
			'ok'     => $https_ok,
			'label'  => 'HTTPS',
			'detail' => $https_ok
				? __( 'active — the outgoing Oracle webhook will be delivered.', 'parcel-selector' )
				: __( 'the shop runs over HTTP. Enable Let\'s Encrypt or Cloudflare so the Stage 4 SOL payment works. The parcel locker map works without HTTPS.', 'parcel-selector' ),
		);

		// 4. Oracle URL reachable
		$cache       = get_transient( self::HEALTH_CACHE_OPTION );
		$oracle_url  = (string) get_option( 'parcselect_label_oracle_url', '' );
		$oracle_ok   = ( is_array( $cache ) && ! empty( $cache['url'] ) && ( $cache['latency_ms'] ?? -1 ) >= 0 );
		$oracle_lat  = is_array( $cache ) ? (int) ( $cache['latency_ms'] ?? -1 ) : -1;
		$out[] = array(
			'ok'     => $oracle_ok,
			'label'  => 'Oracle',
			'detail' => $oracle_ok
				? esc_html( $cache['url'] ) . ' (' . esc_html( $oracle_lat ) . ' ms)'
				: ( '' !== $oracle_url
					? sprintf( /* translators: %s = cached Oracle URL */ __( 'cached: %s — health-check in progress (every 5 min).', 'parcel-selector' ), esc_html( $oracle_url ) )
					: __( 'not selected — running a health check on the next cron tick.', 'parcel-selector' ) ),
		);

		return $out;
	}
}
