<?php

namespace SugarCalendarBookings\Integrations\Webhooks;

use SugarCalendarBookings\Admin\Area;
use SugarCalendarBookings\Integrations\CalendarConflictProviderInterface;
use SugarCalendarBookings\Integrations\IntegrationCapabilityRegistry;
use SugarCalendarBookings\Integrations\OAuthRelay\OAuthConnectionModel;
use SugarCalendarBookings\Integrations\OAuthRelay\OAuthRelayClient;
use SugarCalendarBookings\Support\Admin\Notices;
use SugarCalendarBookings\Support\Bootable;
use SugarCalendarBookings\Support\Helpers\Helpers;

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

/**
 * Webhook URL monitor.
 *
 * Detects when the site's REST API base URL changes (e.g. permalink structure
 * change, REST prefix filter) and re-registers webhook URLs with the Product
 * API relay so incoming webhooks continue to be delivered.
 *
 * Also displays an admin notice on the Integrations settings page when the
 * REST API is unreachable via a self-test.
 *
 * @since 1.4.0
 */
class WebhookUrlMonitor implements Bootable {

	/**
	 * Option key for storing the last-known REST API base URL.
	 *
	 * @since 1.4.0
	 *
	 * @var string
	 */
	const OPTION_REST_BASE_URL = 'sugar_calendar_bookings_webhook_rest_base_url';

	/**
	 * Option key for storing the REST API health check result.
	 *
	 * @since 1.4.0
	 *
	 * @var string
	 */
	const OPTION_REST_API_HEALTHY = 'sugar_calendar_bookings_rest_api_healthy';

	/**
	 * Integration capability registry.
	 *
	 * @since 1.4.0
	 *
	 * @var IntegrationCapabilityRegistry
	 */
	private IntegrationCapabilityRegistry $registry;

	/**
	 * Notices instance.
	 *
	 * @since 1.4.0
	 *
	 * @var Notices
	 */
	private Notices $notices;

	/**
	 * Admin Area instance.
	 *
	 * @since 1.4.0
	 *
	 * @var Area
	 */
	private Area $area;

	/**
	 * Constructor.
	 *
	 * @since 1.4.0
	 *
	 * @param IntegrationCapabilityRegistry $registry Capability registry.
	 * @param Notices                       $notices  Notices instance.
	 * @param Area                          $area     Admin Area instance.
	 */
	public function __construct( IntegrationCapabilityRegistry $registry, Notices $notices, Area $area ) {

		$this->registry = $registry;
		$this->notices  = $notices;
		$this->area     = $area;
	}

	/**
	 * Register hooks.
	 *
	 * @since 1.4.0
	 */
	public function hooks(): void {

		add_action( 'update_option_permalink_structure', [ $this, 'on_permalink_change' ], 10, 0 );
		add_action( 'admin_init', [ $this, 'maybe_display_rest_api_notice' ] );
	}

	/**
	 * Handle permalink structure change.
	 *
	 * When the permalink structure changes, the REST API base URL changes too.
	 * Re-register all active webhook URLs with the Product API relay.
	 *
	 * @since 1.4.0
	 */
	public function on_permalink_change(): void {

		$current_base = $this->get_current_rest_base_url();
		$stored_base  = get_option( self::OPTION_REST_BASE_URL, '' );

		// If the base URL hasn't actually changed, skip.
		if ( $current_base === $stored_base ) {
			return;
		}

		$this->re_register_webhooks();

		update_option( self::OPTION_REST_BASE_URL, $current_base, true );

		// Run REST API health check after permalink change.
		$this->run_rest_api_health_check();
	}

	/**
	 * Display admin notice when the REST API health check has failed.
	 *
	 * Only shown on the Integrations settings page.
	 *
	 * @since 1.4.0
	 */
	public function maybe_display_rest_api_notice(): void {

		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		if ( ! $this->is_integrations_page() ) {
			return;
		}

		// Only show notice if we have active webhook connections.
		if ( ! $this->has_active_webhook_connections() ) {
			return;
		}

		// Ensure stored base URL is current.
		$this->maybe_sync_rest_base_url();

		// Check health status.
		$is_healthy = get_option( self::OPTION_REST_API_HEALTHY, '' );

		// If never checked, run the check now.
		if ( $is_healthy === '' ) {
			$is_healthy = $this->run_rest_api_health_check();
		}

		if ( $is_healthy ) {
			return;
		}

		$this->notices->warning(
			sprintf(
				wp_kses( /* translators: %1$s - REST API test URL; %2$s - URL to the REST API/webhook troubleshooting documentation page. */
					__(
						'The WordPress REST API appears to be unreachable on this site. Integration webhooks require the REST API to function. Please ensure the REST API is not disabled by a security plugin or server configuration. <br>Tested URL: <code>%1$s</code> <br><a href="%2$s" target="_blank" rel="noopener noreferrer">Learn how to troubleshoot this</a>',
						'sugar-calendar-bookings-scheduling-appointments-lite'
					),
					[
						'br'   => [],
						'code' => [],
						'a'    => [
							'href'   => [],
							'target' => [],
							'rel'    => [],
						],
					]
				),
				esc_html( rest_url( IncomingWebhookHandler::REST_NAMESPACE . '/webhooks/test' ) ),
				esc_url( Helpers::get_utm_url( 'https://sugarcalendar.com/docs/bookings/troubleshooting-rest-api-webhook-connection-issues/', [ 'content' => 'rest-api-unreachable' ] ) )
			)
		);
	}

	/**
	 * Sync the stored REST base URL if it has drifted.
	 *
	 * Catches changes caused by rest_url_prefix filter or other edge cases
	 * not triggered by permalink_structure update.
	 *
	 * @since 1.4.0
	 */
	private function maybe_sync_rest_base_url(): void {

		$current_base = $this->get_current_rest_base_url();
		$stored_base  = get_option( self::OPTION_REST_BASE_URL, '' );

		if ( $current_base === $stored_base ) {
			return;
		}

		// URL changed without triggering the permalink hook. Re-register and health-check.
		$this->re_register_webhooks();

		update_option( self::OPTION_REST_BASE_URL, $current_base, true );

		$this->run_rest_api_health_check();
	}

	/**
	 * Re-register webhook URLs for all active connections.
	 *
	 * @since 1.4.0
	 */
	private function re_register_webhooks(): void {

		$providers = $this->registry->get( CalendarConflictProviderInterface::class );

		if ( empty( $providers ) ) {
			return;
		}

		$relay = new OAuthRelayClient();

		foreach ( $providers as $provider ) {
			$slug        = $provider->get_provider_slug();
			$webhook_url = IncomingWebhookHandler::get_webhook_url( $slug );
			$connections = $this->get_active_connections( $slug );

			foreach ( $connections as $connection ) {
				$relay->register_webhook( $slug, $connection->account_id, $webhook_url );
			}
		}
	}

	/**
	 * Run a REST API health check via a loopback request.
	 *
	 * @since 1.4.0
	 *
	 * @return bool True if healthy.
	 */
	private function run_rest_api_health_check(): bool {

		$test_url = rest_url( IncomingWebhookHandler::REST_NAMESPACE . '/webhooks/test' );

		$response = wp_remote_get(
			$test_url,
			[
				'timeout'   => 5,
				'sslverify' => false,
			]
		);

		// A 404 from the REST API router is fine — it means the REST API is reachable.
		// We're just testing that the REST API infrastructure responds, not the specific route.
		$is_healthy = ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) < 500;

		update_option( self::OPTION_REST_API_HEALTHY, $is_healthy ? 1 : 0, true );

		return $is_healthy;
	}

	/**
	 * Get active OAuth connections for a provider.
	 *
	 * @since 1.4.0
	 *
	 * @param string $provider Provider slug.
	 *
	 * @return OAuthConnectionModel[]
	 */
	private function get_active_connections( string $provider ): array {

		return OAuthConnectionModel::query()
			->where( 'provider', '=', $provider, '%s' )
			->where( 'status', '=', 'active', '%s' )
			->get();
	}

	/**
	 * Check if there are any active connections with webhook-capable providers.
	 *
	 * @since 1.4.0
	 *
	 * @return bool
	 */
	private function has_active_webhook_connections(): bool {

		$providers = $this->registry->get( CalendarConflictProviderInterface::class );

		foreach ( $providers as $provider ) {
			$connections = $this->get_active_connections( $provider->get_provider_slug() );

			if ( ! empty( $connections ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Check if the current page is the integrations settings page.
	 *
	 * @since 1.4.0
	 *
	 * @return bool
	 */
	private function is_integrations_page(): bool {

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$view = isset( $_GET['view'] ) ? sanitize_text_field( wp_unslash( $_GET['view'] ) ) : '';

		return $page === Area::SLUG . '-settings' && $view === 'integrations';
	}

	/**
	 * Get the current REST API base URL.
	 *
	 * Returns the base portion of the REST URL (before any route), which changes
	 * when permalink structure or REST URL prefix changes.
	 *
	 * @since 1.4.0
	 *
	 * @return string
	 */
	private function get_current_rest_base_url(): string {

		return rest_url();
	}
}
