<?php
/**
 * Costudio_Telemetry — Client-side telemetry queue and transmitter.
 *
 * Events are stored locally in a WP option queue and flushed in batches
 * to contentosapp-api every hour via the costudio_telemetry_ping cron.
 *
 * No network calls happen during pipeline execution — only during the
 * scheduled flush, so the production flow is never blocked.
 *
 * Options used:
 *   costudio_telemetry_queue        — pending event queue (array, not autoloaded)
 *   costudio_telemetry_enabled      — bool, default true (opt-out model)
 *   costudio_telemetry_endpoint     — custom endpoint URL (optional override)
 *   costudio_telemetry_last_status  — last HTTP status code or 'error'
 *
 * @package Contentosapp_Studio
 * @since   1.1.0
 */

defined( 'ABSPATH' ) || exit;

class Costudio_Telemetry {

	const QUEUE_OPTION     = 'costudio_telemetry_queue';
	const MAX_QUEUE_SIZE   = 100;
	const DEFAULT_ENDPOINT = COSTUDIO_API_URL . '/wp-json/contentosapp-api/v1/telemetry/ingest';

	const CONSENT_VERSION_OPTION = 'costudio_telemetry_consent_version';
	const CURRENT_CONSENT_VERSION = 1;

	// ── Public API ────────────────────────────────────────────────────────────

	/**
	 * Add one event to the local queue for later batch transmission.
	 * Safe to call from the pipeline — no network I/O happens here.
	 *
	 * @param string $event_name  Dot-notation name, e.g. 'production.completed'.
	 * @param array  $data        Arbitrary key→value payload (no PII).
	 */
	public static function queue( string $event_name, array $data = [] ): void {
		if ( ! self::is_enabled() ) {
			return;
		}

		$queue   = (array) get_option( self::QUEUE_OPTION, [] );
		$queue[] = [
			'name' => $event_name,
			'data' => $data,
			'ts'   => time(),
		];

		// Queue is full — flush immediately to avoid unbounded growth.
		if ( count( $queue ) >= self::MAX_QUEUE_SIZE ) {
			update_option( self::QUEUE_OPTION, $queue, false );
			self::flush();
			return;
		}

		update_option( self::QUEUE_OPTION, $queue, false );
	}

	/**
	 * Opportunistic flush: if we have events and haven't flushed in a while,
	 * do it now (called on admin_init).
	 */
	public static function maybe_flush_on_admin(): void {
		$queue = (array) get_option( self::QUEUE_OPTION, [] );
		if ( empty( $queue ) ) {
			return;
		}

		// Don't hammer the server on every page load — max once every 5 minutes.
		$last_flush = (int) get_option( 'costudio_telemetry_last_flush_ts', 0 );
		if ( ( time() - $last_flush ) < 300 ) {
			return;
		}

		update_option( 'costudio_telemetry_last_flush_ts', time(), false );
		self::flush();
	}

	/**
	 * Transmit all queued events to contentosapp-api in a single POST.
	 *
	 * Called by the costudio_telemetry_ping cron hook.
	 * On success the queue is cleared; on failure it is preserved for retry.
	 *
	 * Transport priority:
	 *   1. Local PHP call (when running on the contentosapp.com server itself).
	 *   2. admin-ajax.php fallback (bypasses WAF/Imunify360 on /wp-json/).
	 *   3. REST API endpoint (standard path, may be blocked on some hosts).
	 */
	public static function flush(): void {
		if ( ! self::is_enabled() ) {
			return;
		}

		$queue = (array) get_option( self::QUEUE_OPTION, [] );
		if ( empty( $queue ) ) {
			return;
		}

		$license_key = (string) get_option( 'costudio_license_key', '' );
		if ( empty( $license_key ) ) {
			// Fallback identity for Starter (free tier).
			$license_key = 'starter:' . md5( home_url() );
		}

		$payload = [
			'site_hash'      => self::site_hash(),
			'site_url'       => home_url(), // Required for Starter server-side validation.
			'plugin_version' => defined( 'COSTUDIO_VERSION' ) ? COSTUDIO_VERSION : '',
			'wp_version'     => get_bloginfo( 'version' ),
			'php_version'    => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION,
			'events'         => $queue,
		];

		// ── Path 1: local PHP call (same server as contentosapp-api) ──────────
		if ( defined( 'CAP_API_SERVER' ) && class_exists( 'CAP_Auth' ) && class_exists( 'CAP_Telemetry' ) ) {
			$json_body = (string) wp_json_encode( $payload );
			$fake_req  = new WP_REST_Request( 'POST', '/contentosapp-api/v1/telemetry/ingest' );
			$fake_req->set_header( 'Authorization', 'Bearer ' . $license_key );
			$fake_req->set_header( 'Content-Type', 'application/json' );
			$fake_req->set_param( 'site_url', home_url() );
			$fake_req->set_body( $json_body );

			// Sign the payload so CAP_Auth::verify_signature() accepts it
			// Required for paid FOS2 keys; Starter keys skip signature check.
			if ( class_exists( 'Costudio_Standalone_License' ) && strpos( $license_key, 'starter:' ) !== 0 ) {
				$secret = Costudio_Standalone_License::get_signing_secret( $license_key );
				$fake_req->set_header( 'X-FOS-Signature', Costudio_Standalone_Crypto::sign_payload( $json_body, $secret ) );
			}

			$auth = CAP_Auth::authenticate( $fake_req );
			if ( ! is_wp_error( $auth ) ) {
				$server_payload                = $payload;
				$server_payload['license_key'] = $auth['license_key'];
				CAP_Telemetry::ingest( $server_payload, $auth['plan'] );
				update_option( self::QUEUE_OPTION, [], false );
				update_option( 'costudio_telemetry_last_status', '200', false );
			} else {
				costudio_log_debug(
					'telemetry.local_auth_failed',
					array(
						'code' => $auth->get_error_code(),
						'msg'  => $auth->get_error_message(),
					)
				);
				update_option( 'costudio_telemetry_last_status', 'auth_error', false );
			}
			return;
		}

		// ── Path 2 & 3: remote HTTP (AJAX first, REST fallback) ───────────────
		$ua      = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
		$headers = [
			'Authorization'    => 'Bearer ' . $license_key,
			'Content-Type'     => 'application/json',
			'Accept'           => 'application/json',
			'X-Requested-With' => 'XMLHttpRequest',
			'Origin'           => COSTUDIO_API_URL,
			'Referer'          => COSTUDIO_API_URL . '/wp-admin/',
		];

		$body = (string) wp_json_encode( $payload );

		// Assinatura HMAC para garantir integridade e autenticidade.
		if ( class_exists( 'Costudio_Standalone_License' ) && strpos( $license_key, 'starter:' ) !== 0 ) {
			$secret = Costudio_Standalone_License::get_signing_secret( $license_key );
			$headers['X-FOS-Signature'] = Costudio_Standalone_Crypto::sign_payload( $body, $secret );
		}

		$ajax_url = add_query_arg( 'site_url', home_url(), costudio_api_ajax_url( 'cap_api_telemetry_ingest' ) );
		$response = wp_safe_remote_post( $ajax_url, costudio_safe_remote_args( [
			'timeout'    => 12,
			'blocking'   => true,
			'sslverify'  => costudio_sslverify( 'telemetry' ),
			'headers'    => $headers,
			'body'       => $body,
			'user-agent' => $ua,
		] ) );

		// If AJAX returned non-JSON (WAF challenge), fall back to REST endpoint.
		if ( ! is_wp_error( $response ) ) {
			$code = (int) wp_remote_retrieve_response_code( $response );
			$raw = (string) wp_remote_retrieve_body( $response );
			$trim = ltrim( $raw );
			if ( $trim === '' || ( $trim[0] !== '{' && $trim[0] !== '[' ) ) {
				if ( $code >= 200 && $code < 300 ) {
					update_option( 'costudio_telemetry_last_status', (string) $code, false );
					update_option( self::QUEUE_OPTION, [], false );
					return;
				}
				$response = null; // Force REST fallback.
			}
		}

		if ( is_wp_error( $response ) || $response === null ) {
			$rest_url = add_query_arg( 'site_url', urlencode( home_url() ), self::get_endpoint() );
			$response = wp_safe_remote_post( $rest_url, costudio_safe_remote_args( [
				'timeout'    => 12,
				'blocking'   => true,
				'sslverify'  => costudio_sslverify( 'telemetry' ),
				'headers'    => $headers,
				'body'       => $body,
				'user-agent' => $ua,
			] ) );
		}

		if ( is_wp_error( $response ) ) {
			update_option( 'costudio_telemetry_last_status', 'error', false );
			return;
		}

		$code = (int) wp_remote_retrieve_response_code( $response );
		update_option( 'costudio_telemetry_last_status', (string) $code, false );

		if ( $code >= 200 && $code < 300 ) {
			update_option( self::QUEUE_OPTION, [], false );
		}
	}

	/**
	 * Register cron hooks. Called from Costudio_Standalone_Bootstrap::init().
	 */
	public static function register_hooks(): void {
		add_action( 'costudio_telemetry_ping', [ self::class, 'flush' ] );

		if ( ! wp_next_scheduled( 'costudio_telemetry_ping' ) ) {
			wp_schedule_event( time(), 'hourly', 'costudio_telemetry_ping' );
		}
	}

	/**
	 * Return the last flush status — used by the Contentosapp Studio dashboard data handler.
	 *
	 * Mirrors the shape the old HMAC-based class returned so the JS dashboard
	 * doesn't need changes.
	 *
	 * @return array{ status: string, endpoint: string, queue_size: int, enabled: bool }
	 */
	public static function get_last_status(): array {
		$queue = (array) get_option( self::QUEUE_OPTION, [] );
		return [
			'status'     => (string) get_option( 'costudio_telemetry_last_status', '' ),
			'endpoint'   => self::get_endpoint(),
			'queue_size' => count( $queue ),
			'enabled'    => self::is_enabled(),
		];
	}

	// ── Helpers ───────────────────────────────────────────────────────────────

	/**
	 * Telemetry requires explicit user consent (LGPD/GDPR opt-in model).
	 * Disabled by default until the user makes a decision via the setup wizard,
	 * the admin notice for existing installs, or Settings → Advanced.
	 */
	public static function is_enabled(): bool {
		// Without a consent decision, telemetry is paused regardless of the flag.
		if ( ! self::has_user_decided() ) {
			return false;
		}
		$opt = get_option( 'costudio_telemetry_enabled', null );
		if ( $opt === null ) {
			return false; // opt-in model: default off
		}
		return ! in_array( (string) $opt, [ '0', 'false', 'no', 'off' ], true );
	}

	/**
	 * Whether the user has explicitly accepted or declined telemetry.
	 * Used by the admin notice to know when to stop showing the consent prompt.
	 */
	public static function has_user_decided(): bool {
		$version = (int) get_option( self::CONSENT_VERSION_OPTION, 0 );
		return $version >= self::CURRENT_CONSENT_VERSION;
	}

	/**
	 * Persist the user's consent decision and bump the version so the admin
	 * notice stops appearing.
	 *
	 * @param bool $accepted True to enable telemetry, false to disable it.
	 */
	public static function record_consent( bool $accepted ): void {
		update_option( 'costudio_telemetry_enabled', $accepted ? '1' : '0', false );
		update_option( self::CONSENT_VERSION_OPTION, self::CURRENT_CONSENT_VERSION, false );
	}

	/**
	 * Resolved endpoint URL.
	 */
	public static function get_endpoint(): string {
		$custom = (string) get_option( 'costudio_telemetry_endpoint', '' );
		if ( ! empty( $custom ) ) {
			$custom = costudio_safe_http_url( $custom );
			if ( '' !== $custom ) {
				return $custom;
			}
		}
		return self::DEFAULT_ENDPOINT;
	}

	/**
	 * Public privacy-safe site identifier shared with managed service calls.
	 */
	public static function current_site_hash(): string {
		return self::site_hash();
	}

	/**
	 * Privacy-safe, consistent site identifier derived from home URL + auth salt.
	 * Cannot be reversed to the original URL without access to wp-config.php.
	 */
	private static function site_hash(): string {
		return hash( 'sha256', home_url() . wp_salt( 'auth' ) );
	}
}
