<?php
/**
 * FE Admin Sales 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\Storage\PluginSettings;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * FE Admin Sales Statistics class.
 */
class StatSalesController extends WP_REST_Controller {

	const FE_STAT_SALES       = 'fe_stat_sales_';
	const FE_STAT_SALES_GROUP = 'fe_stat_sales_group_';

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

	/**
	 * Admin Sales Statistics constructor.
	 *
	 * @param AuthHelper     $auth     The auth helper.
	 * @param PluginSettings $settings The plugin settings.
	 *
	 * @since 1.0
	 */
	public function __construct(
		private readonly AuthHelper $auth,
		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]+)/sales',
			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_SALES );
					},
					'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]+)/sales/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 Sales 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 sales 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;
		}

		// 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 ) );
		}

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

		// Look up the orders of the event.
		$orders = $wpdb->get_results( $wpdb->prepare( "SELECT payment_status, input_fields FROM {$wpdb->prefix}fe_event_orders WHERE event_id = %d", $request['id'] ) );

		return rest_ensure_response( $this->prepare_item_for_response( $orders, $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 2.0
	 */
	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_SALES_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 AND group_type IN (1,2)",
				$this->event->event_group
			)
		);

		// Look up the orders of the event group.
		$ids_array = array_map( 'absint', $ids );
		$orders    = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT payment_status, input_fields FROM {$wpdb->prefix}fe_event_orders WHERE event_id IN (" .
				implode( ',', $ids_array ) . ')' // phpcs:ignore
			)
		);

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


	/**
	 * Prepares sales 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
	 * @since 2.0.0 Added orders and amount.
	 * @since 2.2.0 Added discount.
	 */
	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, 'sales' ) ) {
			$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;
		}

		// Walk through all orders.
		$tot = array();
		$ord = array();
		foreach ( $item as $order ) {

			if ( ! isset( $ord[ $order->payment_status ] ) ) {
				$ord[ $order->payment_status ] ['total_orders']   = 0;
				$ord[ $order->payment_status ] ['total_tickets']  = 0;
				$ord[ $order->payment_status ] ['total_amount']   = 0.00;
				$ord[ $order->payment_status ] ['total_discount'] = 0.00;
			}

			$ord[ $order->payment_status ] ['total_orders'] += 1;

			// All tickets of this order.
			$json = json_decode( $order->input_fields );
			foreach ( $json->tickets as $ticket ) {

				if ( ! isset( $tot[ $order->payment_status ] [ $ticket->name ] ) ) {
					$tot[ $order->payment_status ] [ $ticket->name ] ['sold']   = 0;
					$tot[ $order->payment_status ] [ $ticket->name ] ['orders'] = 0;
					$tot[ $order->payment_status ] [ $ticket->name ] ['amount'] = 0.00;
				}

				if ( 0 !== (int) $ticket->count ) {
					$ord[ $order->payment_status ] ['total_tickets']            += (int) $ticket->count;
					$tot[ $order->payment_status ] [ $ticket->name ] ['sold']   += (int) $ticket->count;
					$tot[ $order->payment_status ] [ $ticket->name ] ['orders'] += 1;
					$tot[ $order->payment_status ] [ $ticket->name ] ['amount'] += ( (int) $ticket->count * (float) $ticket->price );
					$ord[ $order->payment_status ] ['total_amount']             += ( (int) $ticket->count * (float) $ticket->price );
				}
			}

			// Are there discounts?
			if ( ! empty( $json->discounts ) ) {
				foreach ( $json->discounts as $discount ) {
					$ord[ $order->payment_status ] ['total_discount'] += (float) $discount->amount;
				}
			}
		}

		// Prepare for the response.
		$resp = array();
		foreach ( $ord as $key => $value ) {
			$tmp                   = array();
			$tmp['payment_status'] = $key;
			$tmp['total_orders']   = $value['total_orders'];
			$tmp['total_amount']   = $value['total_amount'];
			$tmp['total_tickets']  = $value['total_tickets'];
			$tmp['total_discount'] = $value['total_discount'];
			$temp_tickets          = array();
			foreach ( $tot[ $key ] as $key2 => $value2 ) {
				$tmp2                = array();
				$tmp2['ticket_type'] = $key2;
				$tmp2['sold']        = $value2['sold'];
				$tmp2['orders']      = $value2['orders'];
				$tmp2['amount']      = $value2['amount'];
				$temp_tickets[]      = $tmp2;
			}
			$tmp['tickets'] = $temp_tickets;
			$resp[]         = $tmp;
		}

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

		$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_SALES_GROUP . $this->event->event_group, $response, $this->settings->misc_cache_time() );
		} else {
			set_transient( self::FE_STAT_SALES . $this->event->id, $response, $this->settings->misc_cache_time() );
		}

		return $response;
	}


	/**
	 * Prepares links for the sales stats request.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return array Links for sales 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/sales/group' : '%s/%s/%d/sales';

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


	/**
	 * Return the sales statistics schema.
	 *
	 * @return array
	 * @since 2.0.0 Added orders and amount per ticket type.
	 * @since 2.2.0 Added discount.
	 *
	 * @since 1.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'      => 'stats/sales',
			'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 the event belongs to.', 'fast-events' ),
					'context'     => array( 'view' ),
					'type'        => 'string',
					'readonly'    => true,
				),
				'orders'      => array(
					'description' => __( 'Array of totals per payment status.', 'fast-events' ),
					'context'     => array( 'view', 'embed' ),
					'type'        => 'array',
					'readonly'    => true,
					'items'       => array(
						'type'       => 'object',
						'properties' => array(
							'payment_status'  => array(
								'description' => __( 'The unique id of the ticket.', 'fast-events' ),
								'context'     => array( 'view', 'embed' ),
								'type'        => 'string',
								'readonly'    => true,
							),
							'total_orders'    => array(
								'description' => __( 'The total number of orders.', 'fast-events' ),
								'context'     => array( 'view', 'embed' ),
								'type'        => 'integer',
								'readonly'    => true,
							),
							'total_amount'    => array(
								'description' => __( 'The total amount value.', 'fast-events' ),
								'context'     => array( 'view', 'embed' ),
								'type'        => 'number',
								'readonly'    => true,
							),
							'total_tickets'   => array(
								'description' => __( 'The total number of tickets.', 'fast-events' ),
								'context'     => array( 'view', 'embed' ),
								'type'        => 'integer',
								'readonly'    => true,
							),
							'total_discounts' => array(
								'description' => __( 'The total discount.', 'fast-events' ),
								'context'     => array( 'view', 'embed' ),
								'type'        => 'integer',
								'readonly'    => true,
							),
							'tickets'         => array(
								'description' => __( 'Information per ticket type.', '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,
										),
										'sold'        => array(
											'description' => __( 'Sold tickets for this ticket type.', 'fast-events' ),
											'context'     => array( 'view', 'embed' ),
											'type'        => 'integer',
											'readonly'    => true,
										),
										'orders'      => array(
											'description' => __( 'The number of orders.', 'fast-events' ),
											'context'     => array( 'view', 'embed' ),
											'type'        => 'integer',
											'readonly'    => true,
										),
										'amount'      => array(
											'description' => __( 'The amount value.', 'fast-events' ),
											'context'     => array( 'view', 'embed' ),
											'type'        => 'number',
											'readonly'    => true,
										),
									),
								),
							),
						),
					),
				),
			),
		);

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