<?php
/**
 * FE Admin Scan statistics.
 *
 * @since 1.0
 * @since 1.4 Event authorization restriction
 */

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

defined( 'ABSPATH' ) || exit;

use FeData\FastEvents\Helpers\AuthHelper;
use FeData\FastEvents\Helpers\OrderHelper;
use FeData\FastEvents\Storage\PluginSettings;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;


/**
 * FE Admin Scans Statistics class.
 */
class StatScansController extends WP_REST_Controller {

	const FE_STAT_SCANS       = 'fe_stat_scans_';
	const FE_STAT_SCANS_GROUP = 'fe_stat_scans_group_';

	/**
	 * Event information.
	 *
	 * @since 1.0
	 *
	 * @var mixed
	 */
	private mixed $event;

	/**
	 * Admin Scan Statistics constructor.
	 *
	 * @param AuthHelper     $auth         The auth helper.
	 * @param OrderHelper    $order_helper The order helper.
	 * @param PluginSettings $settings     The plugin settings.
	 *
	 * @since 1.0
	 */
	public function __construct(
		private readonly AuthHelper $auth,
		private readonly OrderHelper $order_helper,
		private readonly PluginSettings $settings
	) {
		$this->namespace = 'fast-events/v1';
		$this->rest_base = 'admin/events';
	}


	/**
	 * Register the routes for the objects of the controller.
	 *
	 * @since 1.0
	 * @since 2.0 Refactored for a new permission system
	 */
	public function register_routes(): void {

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)/scans',
			array(
				'args'   => array(
					'id' => array(
						'description'       => __( 'The unique id of the event', 'fast-events' ),
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::TOTAL_SCANS );
					},
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)/scans/group',
			array(
				'args'   => array(
					'id' => array(
						'description'       => __( 'The unique id of the event', 'fast-events' ),
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_group' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::TOTAL_SCANS );
					},
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}


	/**
	 * Get the authorization for the Scans Statistics endpoints.
	 *
	 * @param int $action The action on the controller.
	 * @return WP_Error|bool
	 *
	 * @since 1.0
	 * @since 2.0 Refactored for a new permission system
	 */
	public function fast_events_admin_permissions( int $action ): WP_Error|bool {
		return $this->auth->authorize( $action );
	}


	/**
	 * Retrieve scan statistics.
	 *
	 * @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 1.0
	 * @since 1.4 Event authorization restriction
	 */
	public function get_item( $request ): WP_Error|WP_REST_Response {
		global $wpdb;

		// Check the event authorization.
		$resp = $this->auth->check_event_authorization( $request['id'] );
		if ( is_wp_error( $resp ) ) {
			return $resp;
		}

		// Check cache.
		$result = get_transient( self::FE_STAT_SCANS . $request['id'] );
		if ( false !== $result ) {
			return rest_ensure_response( $result );
		}

		// Look up the event.
		$this->event = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT id, event_group FROM {$wpdb->prefix}fe_events WHERE id = %d",
				$request['id']
			)
		);
		if ( empty( $this->event ) ) {
			return new WP_Error( 'not_found', __( 'Event not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		// Look up the scans of the event.
		$scans = $this->order_helper->order_scans( array( $request['id'] ) );

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


	/**
	 * Retrieve scan statistics of an event group.
	 *
	 * @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 1.0
	 * @since 1.4 Event authorization restriction
	 */
	public function get_group( WP_REST_Request $request ): WP_Error|WP_REST_Response {
		global $wpdb;

		// Check the event authorization.
		$resp = $this->auth->check_event_authorization( $request['id'] );
		if ( is_wp_error( $resp ) ) {
			return $resp;
		}

		// Look up the event.
		$this->event = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT id, event_group FROM {$wpdb->prefix}fe_events WHERE id = %d",
				$request['id']
			)
		);
		if ( empty( $this->event ) ) {
			return new WP_Error( 'not_found', __( 'Event not found', 'fast-events' ), array( 'status' => 404 ) );
		}
		if ( empty( $this->event->event_group ) ) {
			return new WP_Error( 'not_found', __( 'This event is not part of group', 'fast-events' ), array( 'status' => 400 ) );
		}

		// Check cache.
		$result = get_transient( self::FE_STAT_SCANS_GROUP . $this->event->event_group );
		if ( false !== $result ) {
			return rest_ensure_response( $result );
		}

		// Look up the event ids that are part of the group.
		$ids = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT id FROM {$wpdb->prefix}fe_events WHERE event_group = %s",
				$this->event->event_group
			)
		);

		// Look up the scans of the event.
		$scans = $this->order_helper->order_scans( array_map( 'absint', $ids ) );

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


	/**
	 * Prepares scan statistics output for response.
	 *
	 * @param mixed           $item Orders object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 *
	 * @since 1.0
	 */
	public function prepare_item_for_response( $item, $request ): WP_REST_Response {

		$data   = array();
		$fields = $this->get_fields_for_response( $request );
		$route  = $request->get_route();

		if ( rest_is_field_included( 'id', $fields ) && str_ends_with( $route, 'scans' ) ) {
			$data['id'] = (int) $this->event->id;
		}

		if ( rest_is_field_included( 'event_group', $fields ) && str_ends_with( $route, 'group' ) ) {
			$data['event_group'] = $this->event->event_group;
		}

		// Sort the scans.
		usort( $item, fn( $a, $b ) => strcmp( $a->ticket_type, $b->ticket_type ) );

		// Walk through all scans.
		$t_name   = '';
		$tickets  = array();
		$s_ticket = array();
		$tmp      = array();
		foreach ( $item as $scan ) {
			if ( $t_name !== $scan->ticket_type ) {
				if ( '' !== $t_name ) {
					$s_ticket['scans'] = $tmp;
					$tickets[]         = $s_ticket;
				}
				$s_ticket['ticket_type'] = $scan->ticket_type;
				$tmp                     = array();
				$t_name                  = $scan->ticket_type;
			}
			$tmp2['scan_location'] = $scan->scan_location;
			$tmp2['scan_level']    = (int) $scan->scan_level;
			$tmp2['scan_total']    = (int) $scan->total;
			$tmp[]                 = $tmp2;
		}
		if ( '' !== $t_name ) {
			$s_ticket['scans'] = $tmp;
			$tickets[]         = $s_ticket;
		}

		if ( in_array( 'tickets', $fields, true ) ) {
			$data['tickets'] = $tickets;
		}

		$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( $request ) );
		}

		// Cache response.
		if ( str_ends_with( $route, 'group' ) ) {
			set_transient( self::FE_STAT_SCANS_GROUP . $this->event->event_group, $response, $this->settings->misc_cache_time() );
		} else {
			set_transient( self::FE_STAT_SCANS . $this->event->id, $response, $this->settings->misc_cache_time() );
		}

		return $response;
	}


	/**
	 * Prepares links for the scan stats request.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return array Links for scans stats.
	 *
	 * @since 1.0
	 */
	protected function prepare_links( WP_REST_Request $request ): array {
		$is_group = str_ends_with( $request->get_route(), 'group' );
		$route    = $is_group ? '%s/%s/%d/scans/group' : '%s/%s/%d/scans';

		return array(
			'self' => array(
				'href' => rest_url( sprintf( $route, $this->namespace, $this->rest_base, $request['id'] ) ),
			),
		);
	}


	/**
	 * Return the scans' statistics schema.
	 *
	 * @return array
	 *
	 * @since 1.0
	 * @since 2.4.0 Added level 7 (Temporarily leave) and level 8 (Re-enter)
	 */
	public function get_item_schema(): array {

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'stats/scans',
			'type'       => 'object',
			'properties' => array(
				'id'          => array(
					'description' => __( 'The unique id of the event.', 'fast-events' ),
					'context'     => array( 'view' ),
					'type'        => 'integer',
					'readonly'    => true,
				),
				'event_group' => array(
					'description' => __( 'The group name to which the event belongs.', 'fast-events' ),
					'context'     => array( 'view' ),
					'type'        => 'string',
					'readonly'    => true,
				),
				'tickets'     => array(
					'description' => __( 'Array of tickets scanned.', 'fast-events' ),
					'context'     => array( 'view', 'embed' ),
					'type'        => 'array',
					'readonly'    => true,
					'items'       => array(
						'type'       => 'object',
						'properties' => array(
							'ticket_type' => array(
								'description' => __( 'The name of the ticket.', 'fast-events' ),
								'context'     => array( 'view', 'embed' ),
								'type'        => 'string',
								'readonly'    => true,
							),
							'scans'       => array(
								'description' => __( 'Scans per location.', 'fast-events' ),
								'context'     => array( 'view', 'embed' ),
								'type'        => 'array',
								'readonly'    => true,
								'items'       => array(
									'type'       => 'object',
									'properties' => array(
										'scan_location' => array(
											'description' => __( 'The location of the scan.', 'fast-events' ),
											'context'     => array( 'view', 'embed' ),
											'type'        => 'string',
											'readonly'    => true,
										),
										'scan_level'    => array(
											'description' => __( 'The scan level (0, 1, 7, 8 or 9).', 'fast-events' ),
											'context'     => array( 'view', 'embed' ),
											'type'        => 'integer',
											'enum'        => array( 0, 1, 7, 8, 9 ),
											'readonly'    => true,
										),
										'scan_total'    => array(
											'description' => __( 'The total number of scans.', 'fast-events' ),
											'context'     => array( 'view', 'embed' ),
											'type'        => 'integer',
											'readonly'    => true,
										),
									),
								),
							),
						),
					),
				),
			),
		);

		$this->schema = $schema;

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