<?php
/**
 * Handles the webhook simulation.
 *
 * @package WooCommerce\PayPalCommerce\Webhooks\Status
 */

declare(strict_types=1);

namespace WooCommerce\PayPalCommerce\Webhooks\Status;

use Exception;
use UnexpectedValueException;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Webhook;
use WooCommerce\PayPalCommerce\ApiClient\Entity\WebhookEvent;

/**
 * Class WebhookSimulation
 */
class WebhookSimulation {

	public const STATE_WAITING  = 'waiting';
	public const STATE_RECEIVED = 'received';

	private const OPTION_ID = 'ppcp-webhook-simulation';

	/**
	 * The webhooks endpoint.
	 *
	 * @var WebhookEndpoint
	 */
	private $webhook_endpoint;

	/**
	 * Our registered webhook.
	 *
	 * @var Webhook|null
	 */
	private $webhook;

	/**
	 * The event type that will be simulated, such as CHECKOUT.ORDER.APPROVED.
	 *
	 * @var string
	 */
	private $event_type;

	/**
	 * The event resource version, such as 2.0.
	 *
	 * @var string|null
	 */
	private $resource_version;

	/**
	 * WebhookSimulation constructor.
	 *
	 * @param WebhookEndpoint $webhook_endpoint The webhooks endpoint.
	 * @param Webhook|null    $webhook Our registered webhook.
	 * @param string          $event_type The event type that will be simulated, such as CHECKOUT.ORDER.APPROVED.
	 * @param string|null     $resource_version The event resource version, such as 2.0.
	 */
	public function __construct(
		WebhookEndpoint $webhook_endpoint,
		?Webhook $webhook,
		string $event_type,
		?string $resource_version
	) {
		$this->webhook_endpoint = $webhook_endpoint;
		$this->webhook          = $webhook;
		$this->event_type       = $event_type;
		$this->resource_version = $resource_version;
	}

	/**
	 * Starts the simulation by sending request to PayPal and saving the simulation data with STATE_WAITING.
	 *
	 * @throws Exception If failed to start simulation.
	 */
	public function start(): void {
		if ( ! $this->webhook ) {
			throw new Exception( 'Webhooks not registered' );
		}

		$event = $this->webhook_endpoint->simulate( $this->webhook, $this->event_type, $this->resource_version );

		$this->save(
			array(
				'id'    => $event->id(),
				'state' => self::STATE_WAITING,
			)
		);
	}

	/**
	 * Returns true if the given event matches the expected simulation event.
	 *
	 * @param WebhookEvent $event The webhook event.
	 * @return bool
	 */
	public function is_simulation_event( WebhookEvent $event ): bool {
		try {
			$data = $this->load();

			return isset( $data['id'] ) && $event->id() === $data['id'];
		} catch ( Exception $exception ) {
			return false;
		}
	}

	/**
	 * Sets the simulation state to STATE_RECEIVED if the given event matches the expected simulation event.
	 *
	 * @param WebhookEvent $event The webhook event.
	 *
	 * @return bool
	 * @throws Exception If failed to save new state.
	 */
	public function receive( WebhookEvent $event ): bool {
		if ( ! $this->is_simulation_event( $event ) ) {
			return false;
		}

		$this->set_state( self::STATE_RECEIVED );

		return true;
	}

	/**
	 * Returns the current simulation state, one of the STATE_ constants.
	 *
	 * @return string
	 * @throws Exception If failed to load state.
	 */
	public function get_state(): string {
		$data = $this->load();

		return $data['state'];
	}

	/**
	 * Saves the new state.
	 *
	 * @param string $state One of the STATE_ constants.
	 *
	 * @throws Exception If failed to load state.
	 */
	private function set_state( string $state ): void {
		$data = $this->load();

		$data['state'] = $state;

		$this->save( $data );
	}

	/**
	 * Saves the simulation data.
	 *
	 * @param array $data The simulation data.
	 */
	private function save( array $data ): void {
		update_option( self::OPTION_ID, $data );
	}

	/**
	 * Returns the current simulation data.
	 *
	 * @return array
	 * @throws UnexpectedValueException If failed to load.
	 */
	private function load(): array {
		$data = get_option( self::OPTION_ID );
		if ( ! $data ) {
			throw new UnexpectedValueException( 'Webhook simulation data not found.' );
		}

		return $data;
	}
}
