<?php
/**
 * Webhook Manager
 *
 * Handles processes related to Webhooks.
 *
 * @package WP_Ultimo
 * @subpackage Managers/Webhook_Manager
 * @since 2.0.0
 */

namespace WP_Ultimo\Managers;

use WP_Ultimo\Managers\Base_Manager;
use WP_Ultimo\Models\Webhook;
use WP_Ultimo\Logger;

// Exit if accessed directly
defined('ABSPATH') || exit;

/**
 * Handles processes related to webhooks.
 *
 * @since 2.0.0
 */
class Webhook_Manager extends Base_Manager {

	use \WP_Ultimo\Apis\Rest_Api;
	use \WP_Ultimo\Apis\WP_CLI;
	use \WP_Ultimo\Traits\Singleton;

	/**
	 * The manager slug.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $slug = 'webhook';

	/**
	 * The model class associated to this manager.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $model_class = \WP_Ultimo\Models\Webhook::class;

	/**
	 * Holds the list of available events for webhooks.
	 *
	 * @since 2.0.0
	 * @var array
	 */
	protected $events = [];

	/**
	 * Holds the list of all webhooks.
	 *
	 * @since 2.0.0
	 * @var array
	 */
	protected $webhooks = [];

	/**
	 * Instantiate the necessary hooks.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function init(): void {

		$this->enable_rest_api();

		$this->enable_wp_cli();

		add_action('init', [$this, 'register_webhook_listeners']);

		add_action('wp_ajax_wu_send_test_event', [$this, 'send_test_event']);
	}

	/**
	 * Adds the listeners to the webhook callers, extend this by adding actions to wu_register_webhook_listeners
	 *
	 * @todo This needs to have a switch, allowing us to turn it on and off.
	 * @return void.
	 */
	public function register_webhook_listeners(): void {

		foreach (wu_get_event_types() as $key => $event) {
			add_action('wu_event_' . $key, [$this, 'send_webhooks']);
		}
	}

	/**
	 * Sends all the webhooks that are triggered by a specific event.
	 *
	 * @since 2.0.0
	 *
	 * @param array $args with events slug and payload.
	 * @return void
	 */
	public function send_webhooks($args): void {

		$webhooks = Webhook::get_all();

		foreach ($webhooks as $webhook) {
			if ('wu_event_' . $webhook->get_event() === current_filter()) {
				$blocking = wu_get_setting('webhook_calls_blocking', false);

				$this->send_webhook($webhook, $args, $blocking);
			}
		}
	}

	/**
	 * Sends a specific webhook.
	 *
	 * @since 2.0.0
	 *
	 * @param Webhook $webhook The webhook to send.
	 * @param array   $data Key-value array of data to send.
	 * @param boolean $blocking Decides if we want to wait for a response to keep a log.
	 * @param boolean $count If we should update the webhook event count.
	 * @return string|null.
	 */
	public function send_webhook($webhook, $data, $blocking = true, $count = true) {

		if ( ! $data) {
			return;
		}

		$request = wp_remote_post(
			$webhook->get_webhook_url(),
			[
				'method'      => 'POST',
				'timeout'     => 45,
				'redirection' => 5,
				'headers'     => [
					'Content-Type' => 'application/json',
				],
				'cookies'     => [],
				'body'        => wp_json_encode($data),
				'blocking'    => $blocking,
			]
		);

		if (is_wp_error($request)) {
			$error_message = $request->get_error_message();

			if ($count) {
				$this->create_event(
					$webhook->get_event(),
					$webhook->get_id(),
					$webhook->get_webhook_url(),
					$data,
					$error_message,
					true
				);
			}

			return $error_message;
		}

		$response = '';

		// if blocking, we have a response
		if ($blocking) {
			$response = wp_remote_retrieve_body($request);
		}

		if ($count) {
			$this->create_event(
				$webhook->get_event(),
				$webhook->get_id(),
				$webhook->get_webhook_url(),
				$data,
				$response
			);

			$new_count = $webhook->get_event_count() + 1;

			$webhook->set_event_count($new_count);
			$webhook->save();
		}

		return $response;
	}

	/**
	 * Send a test event of the webhook
	 *
	 * @return void
	 */
	public function send_test_event(): void {

		if ( ! current_user_can('manage_network')) {
			wp_send_json(
				[
					'response' => __('You do not have enough permissions to send a test event.', 'wp-ultimo'),
					'webhooks' => Webhook::get_items_as_array(),
				]
			);
		}

		$event = wu_get_event_type($_POST['webhook_event']);

		$webhook_data = [
			'webhook_url' => $_POST['webhook_url'],
			'event'       => $_POST['webhook_event'],
			'active'      => true,
		];

		$webhook = new Webhook($webhook_data);

		$response = $this->send_webhook($webhook, wu_maybe_lazy_load_payload($event['payload']), true, false);

		wp_send_json(
			[
				'response' => htmlentities2($response),
				'id'       => wu_request('webhook_id'),
			]
		);
	}

	/**
	 * Reads the log file and displays the content.
	 *
	 * @return void.
	 */
	public function serve_logs(): void {

		echo '<style>
			body {
				font-family: monospace;
				line-height: 20px;
			}
			pre {
				background: #ececec;
				border: solid 1px #ccc;
				padding: 10px;
				border-radius: 3px;
			}
			hr {
				margin: 25px 0;
				border-top: 1px solid #cecece;
				border-bottom: transparent;
			}
		</style>
		';

		if ( ! current_user_can('manage_network')) {
			echo __('You do not have enough permissions to read the logs of this webhook.', 'wp-ultimo');

			exit;
		}

		$id = absint($_REQUEST['id']);

		$logs = array_map(
			function ($line): string {

				$line = str_replace(' - ', ' </strong> - ', $line);

				$matches = [];

				$line = str_replace('\'', '\\\'', $line);
				$line = preg_replace('~(\{(?:[^{}]|(?R))*\})~', '<pre><script>document.write(JSON.stringify(JSON.parse(\'${1}\'), null, 2));</script></pre>', $line);

				return '<strong>' . $line . '<hr>';
			},
			Logger::read_lines("webhook-$id", 5)
		);

		echo implode('', $logs);

		exit;
	}

	/**
	 * Log a webhook sent for later reference.
	 *
	 * @since 2.0.0
	 *
	 * @param string $event_name          The name of the event.
	 * @param int    $id                  The id of the webhook sent.
	 * @param string $url                 The URL called by the webhook.
	 * @param array  $data                The array with data to be sent.
	 * @param string $response            The response got on webhook call.
	 * @param bool   $is_error            If the response is a WP_Error message.
	 * @return void
	 */
	protected function create_event($event_name, $id, $url, $data, $response, $is_error = false) {

		$message = sprintf('Sent a %s event to the URL %s with data: %s ', $event_name, $url, json_encode($data));

		if ( ! $is_error) {
			$message .= empty($response) ? sprintf('Got response: %s', $response) : 'To debug the remote server response, turn the "Wait for Response" option on the WP Multisite WaaS Settings > API & Webhooks Tab';
		} else {
			$message .= sprintf('Got error: %s', $response);
		}

		$event_data = [
			'object_id'   => $id,
			'object_type' => $this->slug,
			'slug'        => $event_name,
			'payload'     => [
				'message' => $message,
			],
		];

		wu_create_event($event_data);
	}
}