<?php
/**
 * FE Admin Ordering Shortcodes controller.
 *
 * @since 3.0
 */

declare( strict_types=1 );
namespace FeData\FastEvents\RestAPI\Admin;

defined( 'ABSPATH' ) || exit;

use stdClass;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * FE Admin Ordering Shortcodes class.
 */
class OrderingShortcodesController extends WP_REST_Controller {
	private const SHORTCODE_PREFIX = 'fast_events_ordering_';


	/**
	 * Admin Ordering Shortcodes constructor.
	 *
	 * @since 3.0
	 */
	public function __construct() {
		$this->namespace = 'fast-events/v1';
		$this->rest_base = 'admin/shortcodes';
	}


	/**
	 * Register the routes for the objects of the controller.
	 *
	 * @since 3.0
	 */
	public function register_routes(): void {

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => function () {
						return current_user_can( 'manage_options' );
					},
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => function () {
						return current_user_can( 'manage_options' );
					},
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'   => array(
					'id' => array(
						'description'       => __( 'The unique id of the order', 'fast-events' ),
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => function () {
						return current_user_can( 'manage_options' );
					},
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => function () {
						return current_user_can( 'manage_options' );
					},
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => function () {
						return current_user_can( 'manage_options' );
					},
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}


	/**
	 * Get all shortcodes.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return WP_REST_Response|WP_Error|array
	 *
	 * @since 3.0
	 */
	public function get_items( $request ): WP_Error|WP_REST_Response|array {
		global $wpdb;

		$result = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT option_id AS shortcode_id, option_name AS token, option_value AS shortcode FROM $wpdb->options WHERE option_name LIKE %s ",
				$wpdb->esc_like( self::SHORTCODE_PREFIX ) . '%'
			)
		);
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		$shortcodes = array();

		foreach ( $result as $item ) {
			$data         = $this->prepare_item_for_response( $item, $request );
			$shortcodes[] = $this->prepare_response_for_collection( $data );
		}

		$response = rest_ensure_response( $shortcodes );

		// Generate the totals headers.
		$response->header( 'X-WP-Total', count( $shortcodes ) );
		$response->header( 'X-WP-TotalPages', 1 );

		return $response;
	}


	/**
	 * Get a single shortcode by ID.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return WP_REST_Response|WP_Error|array
	 *
	 * @since 2.0
	 */
	public function get_item( $request ): WP_Error|WP_REST_Response|array {
		global $wpdb;

		$result = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT option_id AS shortcode_id, option_name AS token, option_value AS shortcode FROM $wpdb->options WHERE option_id = %d ",
				$request['id']
			)
		);
		if ( empty( $result ) ) {
			return new WP_Error( 'not_found', __( 'Shortcode not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		return rest_ensure_response( $this->prepare_item_for_response( $result, $request ) );
	}


	/**
	 * Create a new shortcode.
	 *
	 * @param  WP_REST_Request $request  Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 *
	 * @since 3.0
	 */
	public function create_item( $request ): WP_Error|WP_REST_Response {
		global $wpdb;

		$shortcode                 = array();
		$shortcode['option_name']  = self::SHORTCODE_PREFIX . $request['token'];
		$shortcode['option_value'] = $request['shortcode'];
		$shortcode['autoload']     = 'off';

		// Insert into database.
		$result = $wpdb->insert( $wpdb->options, $shortcode );
		if ( false === $result ) {
			return new WP_Error( 'failed', __( 'Insert shortcode failed', 'fast-events' ) . ': ' . $wpdb->last_error, array( 'status' => 400 ) );
		}

		$request->set_param( 'context', 'view' );

		$item               = new stdClass();
		$item->shortcode_id = $wpdb->insert_id;
		$item->token        = $request['token'];
		$item->shortcode    = $request['shortcode'];

		return rest_ensure_response( $this->prepare_item_for_response( $item, $request ) );
	}

	/**
	 * Update a single shortcode by ID.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 *
	 * @since 3.0
	 */
	public function update_item( $request ): WP_Error|WP_REST_Response {
		global $wpdb;

		// Look up the shortcode.
		$shortcode = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT option_id AS shortcode_id, option_name AS token, option_value AS shortcode FROM $wpdb->options WHERE option_id = %d ",
				$request['id']
			)
		);
		if ( empty( $shortcode ) ) {
			return new WP_Error( 'not_found', __( 'Shortcode not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		// Check if the option_name starts with the right prefix!
		if ( ! str_starts_with( $shortcode->token, self::SHORTCODE_PREFIX ) ) {
			return new WP_Error( 'invalid_shortcode', __( 'Invalid shortcode', 'fast-events' ), array( 'status' => 400 ) );
		}

		$changed_shortcode             = array();
		$changed_shortcode['autoload'] = 'off';

		// Did the token change?
		if ( isset( $request['token'] ) && $shortcode->token !== $request['token'] ) {
			$changed_shortcode['option_name'] = self::SHORTCODE_PREFIX . $request['token'];
			$shortcode->token                 = $request['token'];
		}

		// Did the shortcode change?
		if ( isset( $request['shortcode'] ) && $shortcode->shortcode !== $request['shortcode'] ) {
			$changed_shortcode['option_value'] = $request['shortcode'];
			$shortcode->shortcode              = $request['shortcode'];
		}

		if ( empty( $changed_shortcode ) ) {
			return new WP_Error( 'no_changes', __( 'No changes found', 'fast-events' ), array( 'status' => 400 ) );
		}

		// Update the data.
		$result = $wpdb->update( $wpdb->options, $changed_shortcode, array( 'option_id' => $request['id'] ) );

		// Analyse the update.
		if ( false === $result ) {
			return new WP_Error( 'failed', __( 'Shortcode update failed: ', 'fast-events' ) . $wpdb->last_error, array( 'status' => 400 ) );
		}
		if ( 0 === $result ) {
			return new WP_Error( 'nothing_to_update', __( 'Shortcode update: nothing to update', 'fast-events' ), array( 'status' => 400 ) );
		}

		$request->set_param( 'context', 'edit' );

		return rest_ensure_response( $this->prepare_item_for_response( $shortcode, $request ) );
	}

	/**
	 * Delete a shortcode by ID.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array Response object on success, or WP_Error object on failure.
	 *
	 * @since 3.0
	 */
	public function delete_item( $request ): WP_Error|WP_REST_Response|bool|array {
		global $wpdb;

		// Look up the shortcode.
		$shortcode = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT option_id AS shortcode_id, option_name AS token FROM $wpdb->options WHERE option_id = %d ",
				$request['id']
			)
		);
		if ( empty( $shortcode ) ) {
			return new WP_Error( 'not_found', __( 'Shortcode not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		// Check if the option_name start with the right prefix!
		if ( ! str_starts_with( $shortcode->token, self::SHORTCODE_PREFIX ) ) {
			return new WP_Error( 'invalid_shortcode', __( 'Invalid shortcode', 'fast-events' ), array( 'status' => 400 ) );
		}

		$wpdb->delete( $wpdb->options, array( 'option_id' => $request['id'] ) );
		$response = new WP_REST_Response();
		$response->set_data(
			array(
				'deleted' => true,
			)
		);

		return $response;
	}


	/**
	 * Retrieves the query params for the collections.
	 *
	 * @return array Query parameters for the collection.
	 *
	 * @since 3.0
	 */
	public function get_collection_params(): array {
		return array(
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
		);
	}


	/**
	 * Prepares shortcode output for response.
	 *
	 * @param mixed           $item    Shortcode objects.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 *
	 * @since 3.0
	 */
	public function prepare_item_for_response( $item, $request ): WP_REST_Response {
		$data   = array();
		$fields = $this->get_fields_for_response( $request );

		if ( rest_is_field_included( 'shortcode_id', $fields ) ) {
			$data['shortcode_id'] = (int) $item->shortcode_id;
		}

		if ( rest_is_field_included( 'token', $fields ) ) {
			if ( str_starts_with( $item->token, self::SHORTCODE_PREFIX ) ) {
				$data['token'] = substr( $item->token, strlen( self::SHORTCODE_PREFIX ) );
			} else {
				$data['token'] = $item->token;
			}
		}

		if ( rest_is_field_included( 'shortcode', $fields ) ) {
			$data['shortcode'] = $item->shortcode;
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'embed';

		$data = $this->add_additional_fields_to_object( $data, $request );
		$data = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $item ) );
		}

		return $response;
	}


	/**
	 * Prepares links for the shortcodes request.
	 *
	 * @param stdClass $shortcode Shortcode object.
	 * @return array Links for the given shortcode.
	 *
	 * @since 3.0
	 */
	protected function prepare_links( stdClass $shortcode ): array {
		return array(
			'self'       => array(
				'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $shortcode->shortcode_id ) ),
			),
			'collection' => array(
				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
			),
		);
	}


	/**
	 * Return the ordering shortcodes schema.
	 *
	 * @return array
	 *
	 * @since 3.0
	 */
	public function get_item_schema(): array {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'shortcodes',
			'type'       => 'object',
			'properties' => array(
				'shortcode_id' => array(
					'description' => __( 'The unique id of the shortcode.', 'fast-events' ),
					'context'     => array( 'view', 'embed' ),
					'type'        => 'integer',
					'readonly'    => true,
				),
				'token'        => array(
					'description' => __( 'The token of the shortcode.', 'fast-events' ),
					'required'    => true,
					'context'     => array( 'view', 'edit' ),
					'type'        => 'string',
					'pattern'     => '^[\w-]{3,32}$',
				),
				'shortcode'    => array(
					'description' => __( 'The shortcode.', 'fast-events' ),
					'required'    => true,
					'context'     => array( 'view', 'edit' ),
					'type'        => 'string',
					'maxLength'   => 128,
				),
			),
		);

		return $this->add_additional_fields_schema( $this->schema );
	}
}
