<?php
/**
 * BitForgeNet Guest Tickets - REST API used by the in-admin camera scanner.
 *
 * @package BitForgeNet_Guest_Tickets
 * @since   1.0.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Registers the plugin's REST routes.
 */
class BFNET_Rest {

	const NAMESPACE_V1 = 'bitforgenet-guest-tickets/v1';

	/**
	 * Hook up.
	 */
	public function __construct() {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
	}

	/**
	 * Register routes.
	 *
	 * @return void
	 */
	public function register_routes() {
		register_rest_route(
			self::NAMESPACE_V1,
			'/scan',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'scan' ),
				'permission_callback' => array( $this, 'can_scan' ),
				'args'                => array(
					'token'    => array( 'type' => 'string', 'required' => true ),
					'event_id' => array( 'type' => 'integer', 'default' => 0 ),
					'gate'     => array( 'type' => 'string', 'default' => '' ),
					'peek'     => array( 'type' => 'boolean', 'default' => false ),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/event/(?P<id>\d+)/stats',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'stats' ),
				'permission_callback' => array( $this, 'can_scan' ),
			)
		);

		register_rest_route(
			self::NAMESPACE_V1,
			'/event/(?P<id>\d+)/search',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'search' ),
				'permission_callback' => array( $this, 'can_scan' ),
				'args'                => array(
					'q' => array( 'type' => 'string', 'required' => true ),
				),
			)
		);
	}

	/**
	 * Permission callback for scanner routes.
	 *
	 * @return bool|WP_Error
	 */
	public function can_scan() {
		if ( ! is_user_logged_in() ) {
			return new WP_Error( 'bfnet_unauthenticated', __( 'Please sign in.', 'bitforgenet-guest-tickets' ), array( 'status' => 401 ) );
		}
		if ( ! BFNET_Security::can( 'bfnet_scan_tickets' ) ) {
			return new WP_Error( 'bfnet_forbidden', __( 'Your account cannot scan tickets.', 'bitforgenet-guest-tickets' ), array( 'status' => 403 ) );
		}
		return true;
	}

	/**
	 * Validate (and optionally consume) a scanned token.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function scan( WP_REST_Request $request ) {
		if ( ! BFNET_Security::rate_limit( 'rest_scan', 600, HOUR_IN_SECONDS, 'user:' . get_current_user_id() ) ) {
			return new WP_Error( 'bfnet_rate_limited', __( 'Too many scans. Please slow down.', 'bitforgenet-guest-tickets' ), array( 'status' => 429 ) );
		}

		$raw = (string) $request->get_param( 'token' );

		// Accept either a bare token or the full URL burned into the QR code.
		if ( false !== strpos( $raw, '/' ) ) {
			$path    = (string) wp_parse_url( $raw, PHP_URL_PATH );
			$pieces  = array_values( array_filter( explode( '/', $path ) ) );
			$raw     = $pieces ? end( $pieces ) : $raw;
		}

		$token = preg_replace( '/[^A-Za-z0-9]/', '', $raw );

		if ( strlen( $token ) < 32 ) {
			BFNET_Ticket::log_scan( 0, (int) $request->get_param( 'event_id' ), 'invalid', (string) $request->get_param( 'gate' ), $token );
			return rest_ensure_response(
				array(
					'ok'      => false,
					'result'  => 'invalid',
					'message' => __( 'That code is not a BitForgeNet Guest Tickets ticket.', 'bitforgenet-guest-tickets' ),
				)
			);
		}

		$event_id = (int) $request->get_param( 'event_id' );
		$gate     = (string) $request->get_param( 'gate' );

		$outcome = $request->get_param( 'peek' )
			? BFNET_Ticket::verify( $token, $event_id )
			: BFNET_Ticket::check_in( $token, $event_id, $gate );

		return rest_ensure_response( $this->shape( $outcome ) );
	}

	/**
	 * Live door statistics for an event.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function stats( WP_REST_Request $request ) {
		$event_id = (int) $request['id'];
		$stats    = BFNET_Order::stats( $event_id );

		return rest_ensure_response(
			array(
				'event_id'   => $event_id,
				'expected'   => (int) $stats['seats_paid'],
				'checked_in' => (int) $stats['tickets_checked_in'],
				'remaining'  => max( 0, (int) $stats['seats_paid'] - (int) $stats['tickets_checked_in'] ),
				'pending'    => (int) $stats['seats_pending'],
			)
		);
	}

	/**
	 * Look a guest up by name or ticket code, for manual door entry.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function search( WP_REST_Request $request ) {
		$event_id = (int) $request['id'];
		$query    = trim( (string) $request->get_param( 'q' ) );

		if ( mb_strlen( $query ) < 2 ) {
			return rest_ensure_response( array( 'results' => array() ) );
		}

		$tickets = BFNET_Ticket::query(
			array(
				'event_id' => $event_id,
				'search'   => $query,
				'limit'    => 15,
				'orderby'  => 'id',
				'order'    => 'ASC',
			)
		);

		$results = array();
		foreach ( $tickets as $ticket ) {
			$results[] = array(
				'ticket_id'  => (int) $ticket->id,
				'name'       => BFNET_Util::full_name( $ticket->first_name, $ticket->last_name ),
				'serial'     => $ticket->serial,
				'status'     => $ticket->status,
				'checked_in' => (bool) $ticket->checked_in,
				'checked_at' => $ticket->checked_in_at ? BFNET_Util::date( $ticket->checked_in_at ) : '',
			);
		}

		return rest_ensure_response( array( 'results' => $results ) );
	}

	/**
	 * Flatten a verification payload into a JSON-safe response.
	 *
	 * @param array $outcome Verification payload.
	 * @return array
	 */
	private function shape( array $outcome ) {
		$ticket   = isset( $outcome['ticket'] ) ? $outcome['ticket'] : null;
		$attendee = isset( $outcome['attendee'] ) ? $outcome['attendee'] : null;
		$order    = isset( $outcome['order'] ) ? $outcome['order'] : null;
		$event    = isset( $outcome['event'] ) ? $outcome['event'] : null;

		return array(
			'ok'         => ! empty( $outcome['ok'] ),
			'result'     => $outcome['result'],
			'message'    => $outcome['message'],
			'guest'      => $attendee ? BFNET_Util::full_name( $attendee->first_name, $attendee->last_name ) : '',
			'position'   => $attendee ? (int) $attendee->position : 0,
			'party_size' => $order ? (int) $order->quantity : 0,
			'serial'     => $ticket ? $ticket->serial : '',
			'ticket_id'  => $ticket ? (int) $ticket->id : 0,
			'order_ref'  => $order ? $order->reference : '',
			'event'      => $event ? $event->title : '',
			'scan_count' => $ticket ? (int) $ticket->scan_count : 0,
			'first_seen' => ( $ticket && $ticket->checked_in_at ) ? BFNET_Util::date( $ticket->checked_in_at ) : '',
		);
	}
}
