<?php
/**
 * Diagnostics payload builder.
 *
 * @package FoodOrd
 */

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

/**
 * Builds opt-in technical diagnostics data for compatibility analysis.
 */
class Foodord_Diagnostics {

	/**
	 * Option key for diagnostic data sharing consent.
	 */
	public const OPTION_ALLOW_TRACKING = 'foodord_allow_tracking';

	/**
	 * Option key for last diagnostics status.
	 */
	private const OPTION_LAST_STATUS = 'foodord_diagnostics_last_status';

	/**
	 * Option key for the last successful diagnostics send timestamp.
	 */
	private const OPTION_LAST_SENT_AT = 'foodord_diagnostics_last_sent_at';

	/**
	 * Monthly cron hook name.
	 */
	private const CRON_HOOK = 'foodord_send_diagnostics_monthly';

	/**
	 * Diagnostics payload schema version.
	 */
	private const SCHEMA_VERSION = '1.0';

	/**
	 * Register diagnostics hooks.
	 */
	public static function init(): void {
		add_filter( 'cron_schedules', array( __CLASS__, 'register_cron_schedule' ) );
		add_action( self::CRON_HOOK, array( __CLASS__, 'send_scheduled_payload' ) );

		if ( self::has_explicit_consent() ) {
			self::schedule_monthly_event();
		}
	}

	/**
	 * Add monthly schedule interval.
	 *
	 * @param array<string,array<string,mixed>> $schedules Existing schedules.
	 * @return array<string,array<string,mixed>>
	 */
	public static function register_cron_schedule( array $schedules ): array {
		if ( ! isset( $schedules['foodord_monthly'] ) ) {
			$schedules['foodord_monthly'] = array(
				'interval' => 30 * DAY_IN_SECONDS,
				'display'  => __( 'Once monthly', 'foodord' ),
			);
		}

		return $schedules;
	}

	/**
	 * Persist diagnostics preference and react to opt-in changes.
	 *
	 * @param bool $enabled Whether diagnostics sharing is enabled.
	 */
	public static function update_preference( bool $enabled ): void {
		$previous_value = get_option( self::OPTION_ALLOW_TRACKING, null );

		update_option( self::OPTION_ALLOW_TRACKING, $enabled ? 'yes' : 'no' );

		if ( ! $enabled ) {
			self::clear_monthly_event();
			return;
		}

		self::schedule_monthly_event();

		if ( 'yes' !== $previous_value ) {
			self::send_payload( 'opt_in' );
		}
	}

	/**
	 * Determine whether diagnostics sharing is enabled.
	 */
	public static function is_enabled(): bool {
		return 'no' !== (string) get_option( self::OPTION_ALLOW_TRACKING, 'yes' );
	}

	/**
	 * Determine whether the site has explicitly opted in.
	 */
	public static function has_explicit_consent(): bool {
		return 'yes' === (string) get_option( self::OPTION_ALLOW_TRACKING, '' );
	}

	/**
	 * Schedule monthly diagnostics sending.
	 */
	public static function schedule_monthly_event(): void {
		if ( ! self::has_explicit_consent() ) {
			return;
		}

		if ( false !== wp_next_scheduled( self::CRON_HOOK ) ) {
			return;
		}

		wp_schedule_event( time() + ( 30 * DAY_IN_SECONDS ), 'foodord_monthly', self::CRON_HOOK );
	}

	/**
	 * Clear monthly diagnostics sending.
	 */
	public static function clear_monthly_event(): void {
		wp_clear_scheduled_hook( self::CRON_HOOK );
	}

	/**
	 * Send diagnostics payload from scheduled event.
	 */
	public static function send_scheduled_payload(): bool {
		if ( ! self::has_explicit_consent() ) {
			self::clear_monthly_event();
			return false;
		}

		return self::send_payload( 'monthly' );
	}

	/**
	 * Send diagnostics payload to the configured endpoint.
	 *
	 * @param string $trigger Send trigger.
	 */
	public static function send_payload( string $trigger = 'manual' ): bool {
		if ( ! self::has_explicit_consent() ) {
			return false;
		}

		$endpoint = self::get_endpoint();
		if ( '' === $endpoint ) {
			self::update_last_status( 'skipped', 0, 'endpoint_missing', $trigger );
			return false;
		}

		$payload            = self::build_payload();
		$payload['trigger'] = sanitize_key( $trigger );

		$response = wp_remote_post(
			$endpoint,
			array(
				'timeout' => 12,
				'headers' => array(
					'content-type' => 'application/json',
					'accept'       => 'application/json',
				),
				'body'    => wp_json_encode( $payload ),
			)
		);

		if ( is_wp_error( $response ) ) {
			self::update_last_status( 'failed', 0, $response->get_error_code(), $trigger );
			return false;
		}

		$status_code = (int) wp_remote_retrieve_response_code( $response );
		if ( $status_code >= 200 && $status_code < 300 ) {
			update_option( self::OPTION_LAST_SENT_AT, gmdate( 'c' ) );
			self::update_last_status( 'success', $status_code, '', $trigger );
			return true;
		}

		self::update_last_status( 'failed', $status_code, 'http_error', $trigger );
		return false;
	}

	/**
	 * Build payload only when diagnostics sharing is enabled.
	 *
	 * @return array<string,mixed>
	 */
	public static function build_payload_if_enabled(): array {
		if ( ! self::has_explicit_consent() ) {
			return array();
		}

		return self::build_payload();
	}

	/**
	 * Build diagnostics payload.
	 *
	 * @return array<string,mixed>
	 */
	public static function build_payload(): array {
		$payload = array(
			'schema_version' => self::SCHEMA_VERSION,
			'generated_at'   => gmdate( 'c' ),
			'site'           => self::get_site_data(),
			'wordpress'      => self::get_wordpress_data(),
			'server'         => self::get_server_data(),
			'theme'          => self::get_theme_data(),
			'plugins'        => self::get_plugin_data(),
		);

		/**
		 * Filter diagnostics payload before it is sent.
		 *
		 * @param array<string,mixed> $payload Technical diagnostics payload.
		 */
		$filtered_payload = apply_filters( 'foodord_diagnostics_payload', $payload );

		return is_array( $filtered_payload ) ? $filtered_payload : $payload;
	}

	/**
	 * Build site-level diagnostics data.
	 *
	 * @return array<string,mixed>
	 */
	private static function get_site_data(): array {
		$home_url = home_url();
		$site_url = site_url();

		return array(
			'home_host' => self::get_host_from_url( $home_url ),
			'site_host' => self::get_host_from_url( $site_url ),
			'site_hash' => hash( 'sha256', untrailingslashit( $home_url ) ),
			'multisite' => is_multisite(),
		);
	}

	/**
	 * Build WordPress diagnostics data.
	 *
	 * @return array<string,mixed>
	 */
	private static function get_wordpress_data(): array {
		return array(
			'version'          => sanitize_text_field( (string) get_bloginfo( 'version' ) ),
			'locale'           => sanitize_text_field( (string) get_locale() ),
			'timezone'         => sanitize_text_field( (string) wp_timezone_string() ),
			'environment_type' => sanitize_text_field( (string) wp_get_environment_type() ),
			'debug_enabled'    => defined( 'WP_DEBUG' ) && WP_DEBUG,
			'memory_limit'     => defined( 'WP_MEMORY_LIMIT' ) ? sanitize_text_field( (string) WP_MEMORY_LIMIT ) : '',
			'max_memory_limit' => defined( 'WP_MAX_MEMORY_LIMIT' ) ? sanitize_text_field( (string) WP_MAX_MEMORY_LIMIT ) : '',
		);
	}

	/**
	 * Build server diagnostics data.
	 *
	 * @return array<string,mixed>
	 */
	private static function get_server_data(): array {
		return array(
			'php_version'        => sanitize_text_field( PHP_VERSION ),
			'php_sapi'           => sanitize_text_field( PHP_SAPI ),
			'memory_limit'       => self::get_ini_value( 'memory_limit' ),
			'max_execution_time' => self::get_ini_value( 'max_execution_time' ),
			'post_max_size'      => self::get_ini_value( 'post_max_size' ),
			'upload_max_filesize' => self::get_ini_value( 'upload_max_filesize' ),
			'extensions'         => self::get_extension_data(),
		);
	}

	/**
	 * Build current theme diagnostics data.
	 *
	 * @return array<string,mixed>
	 */
	private static function get_theme_data(): array {
		if ( ! function_exists( 'wp_get_theme' ) ) {
			return array();
		}

		$theme = wp_get_theme();
		if ( ! $theme || ! $theme->exists() ) {
			return array();
		}

		return array(
			'stylesheet'          => sanitize_key( (string) $theme->get_stylesheet() ),
			'template'            => sanitize_key( (string) $theme->get_template() ),
			'version'             => sanitize_text_field( (string) $theme->get( 'Version' ) ),
			'is_child_theme'      => (bool) $theme->parent(),
			'foodord_theme_active' => defined( 'FOODORD_THEME_ACTIVE' ) && FOODORD_THEME_ACTIVE,
		);
	}

	/**
	 * Build plugin diagnostics data.
	 *
	 * @return array<string,mixed>
	 */
	private static function get_plugin_data(): array {
		return array(
			'foodord'     => array(
				'version' => defined( 'FOODORD_VERSION' ) ? sanitize_text_field( (string) FOODORD_VERSION ) : '',
			),
			'foodord_pro' => array(
				'active'  => self::is_foodord_pro_active(),
				'version' => defined( 'FOODORD_PRO_VERSION' ) ? sanitize_text_field( (string) FOODORD_PRO_VERSION ) : '',
			),
			'woocommerce' => array(
				'active'  => class_exists( 'WooCommerce' ) || function_exists( 'wc' ),
				'version' => defined( 'WC_VERSION' ) ? sanitize_text_field( (string) WC_VERSION ) : '',
			),
		);
	}

	/**
	 * Build PHP extension availability data.
	 *
	 * @return array<string,bool>
	 */
	private static function get_extension_data(): array {
		$extensions = array( 'curl', 'dom', 'json', 'mbstring', 'openssl', 'zip' );
		$result     = array();

		foreach ( $extensions as $extension ) {
			$result[ $extension ] = extension_loaded( $extension );
		}

		return $result;
	}

	/**
	 * Determine whether FoodOrd Pro is active.
	 */
	private static function is_foodord_pro_active(): bool {
		if ( function_exists( 'foodord_pro_is_active' ) ) {
			return (bool) foodord_pro_is_active();
		}

		return defined( 'FOODORD_PRO_VERSION' );
	}

	/**
	 * Get sanitized host from a URL.
	 *
	 * @param string $url URL.
	 */
	private static function get_host_from_url( string $url ): string {
		$host = wp_parse_url( $url, PHP_URL_HOST );

		return is_string( $host ) ? sanitize_text_field( strtolower( $host ) ) : '';
	}

	/**
	 * Get sanitized PHP ini value.
	 *
	 * @param string $key PHP ini key.
	 */
	private static function get_ini_value( string $key ): string {
		$value = ini_get( $key );

		return false === $value ? '' : sanitize_text_field( (string) $value );
	}

	/**
	 * Get diagnostics endpoint.
	 */
	private static function get_endpoint(): string {
		$endpoint = defined( 'FOODORD_DIAGNOSTICS_ENDPOINT' )
			? (string) FOODORD_DIAGNOSTICS_ENDPOINT
			: 'https://www.foodord.com/wp-json/foodord/v1/diagnostics';

		/**
		 * Filter diagnostics endpoint.
		 *
		 * @param string $endpoint Diagnostics endpoint URL.
		 */
		$endpoint = apply_filters( 'foodord_diagnostics_endpoint', $endpoint );

		return is_string( $endpoint ) ? esc_url_raw( $endpoint ) : '';
	}

	/**
	 * Persist last send status.
	 *
	 * @param string $status      Send status.
	 * @param int    $status_code HTTP status code.
	 * @param string $error_code  Error code.
	 * @param string $trigger     Send trigger.
	 */
	private static function update_last_status( string $status, int $status_code, string $error_code, string $trigger ): void {
		update_option(
			self::OPTION_LAST_STATUS,
			array(
				'status'      => sanitize_key( $status ),
				'status_code' => $status_code,
				'error_code'  => sanitize_key( $error_code ),
				'trigger'     => sanitize_key( $trigger ),
				'checked_at'  => gmdate( 'c' ),
			)
		);
	}
}
