<?php
/**
 * Bookings REST API Controller
 *
 * @since   PBOOKING_SINCE
 * @package Nemtly_Booking
 */

namespace NemtlyBooking\API;

use NemtlyBooking\Models\Booking;
use NemtlyBooking\Models\Payment;
use NemtlyBooking\Services\Payment_Service;
use NemtlyBooking\Services\Settings_Service;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP_Error;
use NemtlyBooking\Services\Booking_Service;
use NemtlyBooking\Repositories\Booking_Repository;
use NemtlyBooking\Repositories\Event_Repository;
use NemtlyBooking\Core\Event_Manager;
use NemtlyBooking\Core\Container;

/**
 * Bookings Controller class
 *
 * Handles REST API endpoints for bookings
 *
 * @since PBOOKING_SINCE
 */
class Booking_Controller extends Nemtly_API {

    /**
     * Rest base for the current object
     *
     * @var string
     */
    protected $rest_base = 'bookings';

    /**
     * Booking service
     *
     * @var Booking_Service
     */
    private $booking_service;

    /**
     * Payment service
     *
     * @var Payment_Service
     */
    private $payment_service;

    /**
     * Settings service
     *
     * @var Settings_Service
     */
    private $settings_service;

    /**
     * Booking repository
     *
     * @var Booking_Repository
     */
    private $booking_repository;

    /**
     * Event repository
     *
     * @var Event_Repository
     */
    private $event_repository;

    /**
     * Event manager
     *
     * @var Event_Manager
     */
    private $event_manager;

    /**
     * Payment repository
     *
     * @var Payment_Repository_Interface
     */
    private $payment_repository;

    /**
     * Dependency injection container
     *
     * @var Container
     */
    private $container;

    /**
     * Constructor
     *
     * @since PBOOKING_SINCE
     *
     * @param Container $container Dependency injection container.
     */
    public function __construct( Container $container ) {
        parent::__construct();
        $this->container = $container;

        // Get services from container
        $this->booking_repository = $container->get( 'booking_repository' );
        $this->event_repository   = $container->get( 'event_repository' );
        $this->event_manager      = $container->get( 'event_manager' );
        $this->booking_service    = $container->get( 'booking_service' );
        $this->payment_service    = $container->get( 'payment_service' );
        $this->payment_repository = $container->get( 'payment_repository' );
        $this->settings_service   = $container->get( 'settings_service' );
    }

    /**
     * Register the routes for bookings
     *
     * @since PBOOKING_SINCE
     */
    public function register_routes() {
        // Public endpoint for frontend to know which payment methods are available
        register_rest_route(
            $this->namespace,
            '/payment-methods',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_payment_methods' ],
                    'permission_callback' => '__return_true',
                ],
            ]
        );

        // Register collection endpoints
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base,
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_items' ],
                    'permission_callback' => [ $this, 'permission_check' ],
                    'args'                => $this->get_collection_params(),
                ],
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [ $this, 'create_item' ],
                    'permission_callback' => [ $this, 'permission_check' ],
                    'args'                => $this->get_endpoint_args_for_item_schema(),
                ],
                'schema' => [ $this, 'get_public_item_schema' ],
            ]
        );

        // Register single booking endpoints
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/(?P<id>[\d]+)',
            [
                'args'   => [
                    'id' => [
                        'description' => __( 'Unique identifier for the booking.', 'nemtly-booking' ),
                        'type'        => 'integer',
                    ],
                ],
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_item' ],
                    'permission_callback' => [ $this, 'permission_check' ],
                ],
                [
                    'methods'             => WP_REST_Server::EDITABLE,
                    'callback'            => [ $this, 'update_item' ],
                    'permission_callback' => [ $this, 'permission_check' ],
                    'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
                ],
                [
                    'methods'             => WP_REST_Server::DELETABLE,
                    'callback'            => [ $this, 'delete_item' ],
                    'permission_callback' => [ $this, 'permission_check' ],
                ],
                'schema' => [ $this, 'get_public_item_schema' ],
            ]
        );

        // Register booking by UUID endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/uuid/(?P<uuid>[a-zA-Z0-9\-]+)',
            [
                'args' => [
                    'uuid' => [
                        'description' => __( 'Unique identifier for the booking.', 'nemtly-booking' ),
                        'type'        => 'string',
                    ],
                ],
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_item_by_uuid' ],
                    'permission_callback' => [ $this, 'permission_check' ],
                ],
            ]
        );

        // Register booking status update endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/(?P<id>[\d]+)/status',
            [
                'args' => [
                    'id' => [
                        'description' => __( 'Unique identifier for the booking.', 'nemtly-booking' ),
                        'type'        => 'integer',
                    ],
                ],
                [
                    'methods'             => WP_REST_Server::EDITABLE,
                    'callback'            => [ $this, 'update_booking_status' ],
                    'permission_callback' => [ $this, 'permission_check' ],
                    'args'                => [
                        'status'            => [
                            'description' => __( 'New booking status.', 'nemtly-booking' ),
                            'type'        => 'string',
                            'enum'        => [ 'pending', 'confirmed', 'cancelled', 'waitlisted' ],
                            'required'    => true,
                        ],
                        'send_notification' => [
                            'description' => __( 'Whether to send notification email.', 'nemtly-booking' ),
                            'type'        => 'boolean',
                            'default'     => false,
                        ],
                    ],
                ],
            ]
        );

        // Register booking statistics endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/stats',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_booking_statistics' ],
                    'permission_callback' => [ $this, 'permission_check' ],
                ],
            ]
        );

        // Register bulk update endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/bulk-update',
            [
                [
                    'methods'             => WP_REST_Server::EDITABLE,
                    'callback'            => [ $this, 'bulk_update_bookings' ],
                    'permission_callback' => [ $this, 'permission_check' ],
                    'args'                => [
                        'booking_ids' => [
                            'description' => __( 'Array of booking IDs to update.', 'nemtly-booking' ),
                            'type'        => 'array',
                            'items'       => [
                                'type' => 'integer',
                            ],
                            'required'    => true,
                        ],
                        'action'      => [
                            'description' => __( 'Action to perform on bookings.', 'nemtly-booking' ),
                            'type'        => 'string',
                            'enum'        => [ 'update_status', 'delete', 'send_notification' ],
                            'required'    => true,
                        ],
                        'data'        => [
                            'description' => __( 'Data for the action.', 'nemtly-booking' ),
                            'type'        => 'object',
                        ],
                    ],
                ],
            ]
        );
    }

    /**
     * Get available payment methods for the booking form (public).
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response
     */
    public function get_payment_methods( $request ) {
        $data = [
            'accept_stripe'               => (bool) $this->settings_service->get_setting( 'accept_stripe', true ),
            'accept_manual_payment'       => (bool) $this->settings_service->get_setting( 'accept_manual_payment', false ),
            'manual_payment_label'        => $this->settings_service->get_setting( 'manual_payment_label', '' ),
            'manual_payment_instructions' => $this->settings_service->get_setting( 'manual_payment_instructions', '' ),
        ];
        return new WP_REST_Response(
            [
                'success' => true,
                'data'    => $data,
            ],
            200
        );
    }

    /**
     * Get a collection of bookings
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_REST_Response|WP_Error
     */
    public function get_items( $request ) {
        $per_page = $request->get_param( 'per_page' ) ? $request->get_param( 'per_page' ) : 10;
        $page     = $request->get_param( 'page' ) ? $request->get_param( 'page' ) : 1;
        $args     = [
            'per_page' => $per_page,
            'page'     => $page,
        ];

        // Only add status if it's not empty
        $status = $request->get_param( 'status' );
        if ( ! empty( $status ) ) {
            $args['status'] = $status;
        }

        // Only add event_id if it's not 0
        $event_id = $request->get_param( 'event_id' );
        if ( ! empty( $event_id ) && '0' !== $event_id ) {
            $args['event_id'] = intval( $event_id );
        }

        // Only add user_id if it's not 0
        $user_id = $request->get_param( 'user_id' );
        if ( ! empty( $user_id ) && '0' !== $user_id ) {
            $args['user_id'] = intval( $user_id );
        }

        // Only add date_from if it's not empty
        $date_from = $request->get_param( 'date_from' );
        if ( ! empty( $date_from ) ) {
            $args['date_from'] = $date_from;
        }

        // Only add date_to if it's not empty
        $date_to = $request->get_param( 'date_to' );
        if ( ! empty( $date_to ) ) {
            $args['date_to'] = $date_to;
        }

        // Only add search if it's not empty
        $search = $request->get_param( 'search' );
        if ( ! empty( $search ) ) {
            $args['search'] = $search;
        }

        // Sorting (repository validates against an allowlist of columns).
        $orderby = $request->get_param( 'orderby' );
        if ( ! empty( $orderby ) ) {
            $args['orderby'] = sanitize_text_field( $orderby );
        }
        $order = $request->get_param( 'order' );
        if ( ! empty( $order ) ) {
            $args['order'] = strtoupper( sanitize_text_field( $order ) );
        }

        // Remove empty values
        $args = array_filter(
            $args,
            function ( $value ) {
				return ! empty( $value );
			}
        );

        try {
            $bookings    = $this->booking_service->get_bookings( $args );
            $total_items = $this->booking_repository->count( $args );
            $total_pages = ceil( $total_items / $args['per_page'] );

            $response_data = [];
            foreach ( $bookings as $booking ) {
                try {
                    $response_data[] = $this->prepare_item_for_response( $booking, $request );
                } catch ( \Exception $e ) {
                    continue;
                }
            }

            return rest_ensure_response(
                [
                    'success'    => true,
                    'result'     => $response_data,
                    'pagination' => [
                        'total_items'  => $total_items,
                        'total_pages'  => $total_pages,
                        'current_page' => $args['page'],
                        'per_page'     => $args['per_page'],
                    ],
                ]
            );
        } catch ( \Exception $e ) {
            return new WP_Error(
                'booking_retrieval_failed',
                $e->getMessage(),
                [ 'status' => 500 ]
            );
        }
    }

    /**
     * Get a single booking
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_REST_Response|WP_Error
     */
    public function get_item( $request ) {
        $booking_id = $request->get_param( 'id' );

        try {
            $booking = $this->booking_service->get_booking( $booking_id );

            if ( ! $booking ) {
                return new WP_Error(
                    'booking_not_found',
                    __( 'Booking not found.', 'nemtly-booking' ),
                    [ 'status' => 404 ]
                );
            }

            $response_data = $this->prepare_item_for_response( $booking, $request );

            return new WP_REST_Response(
                [
                    'success' => true,
                    'result'  => $response_data,
                ],
                200
            );
        } catch ( \Exception $e ) {
            return new WP_Error(
                'booking_retrieval_failed',
                $e->getMessage(),
                [ 'status' => 500 ]
            );
        }
    }

    /**
     * Get a single booking by UUID
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_REST_Response|WP_Error
     */
    public function get_item_by_uuid( $request ) {
        $uuid = $request->get_param( 'uuid' );

        try {
            $booking = $this->booking_service->get_booking_by_uuid( $uuid );

            if ( ! $booking ) {
                return new WP_Error(
                    'booking_not_found',
                    __( 'Booking not found.', 'nemtly-booking' ),
                    [ 'status' => 404 ]
                );
            }

            return new WP_REST_Response(
                [
                    'success' => true,
                    'data'    => $this->prepare_item_for_response( $booking, $request ),
                ],
                200
            );
        } catch ( \Exception $e ) {
            return new WP_Error(
                'booking_retrieval_failed',
                $e->getMessage(),
                [ 'status' => 500 ]
            );
        }
    }

    /**
     * Create a new booking
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_REST_Response|WP_Error
     */
    public function create_item( $request ) {
        $booking_data = $request->get_json_params();
        if ( empty( $booking_data ) ) {
            $booking_data = $request->get_body_params();
        }

        // Map frontend slot field names to service-expected names.
        if ( ! empty( $booking_data['booking_time'] ) && empty( $booking_data['booking_start'] ) ) {
            $booking_data['booking_start'] = $booking_data['booking_time'];
        }
        if ( ! empty( $booking_data['booking_end_time'] ) && empty( $booking_data['booking_end'] ) ) {
            $booking_data['booking_end'] = $booking_data['booking_end_time'];
        }

        // Validate required fields
        $required_fields = [ 'event_id', 'attendee_name', 'attendee_email' ];
        foreach ( $required_fields as $field ) {
            if ( empty( $booking_data[ $field ] ) ) {
                return new WP_Error(
                    'missing_required_field',
                    sprintf(
                        // translators: %s is the name of the missing field.
                        __( 'Missing required field: %s', 'nemtly-booking' ),
                        $field
                    ),
                    [ 'status' => 400 ]
                );
            }
        }

        // CAPTCHA validation for public bookings (skip for admin-created bookings).
        if ( empty( $booking_data['admin_payment_action'] ) ) {
            $captcha_result = $this->validate_captcha( $booking_data );
            if ( is_wp_error( $captcha_result ) ) {
                return $captcha_result;
            }
        }

        // Waitlist entries take a dedicated path: no availability check, no payment.
        if ( ! empty( $booking_data['waitlist'] ) ) {
            try {
                $booking                     = $this->booking_service->create_waitlist_entry( $booking_data );
                $response_data               = $this->prepare_item_for_response( $booking, $request );
                $response_data['waitlisted'] = true;

                return new WP_REST_Response(
                    [
                        'success' => true,
                        'data'    => $response_data,
                        'message' => __( 'You are on the waitlist!', 'nemtly-booking' ),
                    ],
                    201
                );
            } catch ( \Exception $e ) {
                return new WP_Error(
                    'waitlist_failed',
                    $e->getMessage(),
                    [ 'status' => 400 ]
                );
            }
        }

        try {
            // Check if event is paid
            $event = $this->event_repository->find( $booking_data['event_id'] );
            $price = (float) $event->get_price();

            // Add payment info to meta if paid
            if ( $price > 0 ) {
                $currency                                       = $this->settings_service->get_setting( 'currency', 'USD' );
                $booking_data['booking_meta']['payment_amount'] = $price;
                $booking_data['booking_meta']['currency']       = $currency;

                // If paid, default status is pending
                if ( empty( $booking_data['booking_status'] ) ) {
                    $booking_data['booking_status'] = 'pending';
                }
            }

            // Handle admin payment actions — override status before creating booking
            $admin_payment_action = isset( $booking_data['admin_payment_action'] ) ? sanitize_text_field( $booking_data['admin_payment_action'] ) : '';

            if ( $price > 0 && ! empty( $admin_payment_action ) ) {
                // Admin-created bookings with payment action are auto-confirmed
                $booking_data['booking_status'] = 'confirmed';
            }

            $booking       = $this->booking_service->create_booking( $booking_data );
            $response_data = $this->prepare_item_for_response( $booking, $request );

            // Handle payment based on admin action or standard flow
            if ( $price > 0 && ! empty( $admin_payment_action ) ) {
                // Admin-created booking: handle payment based on selected action
                $currency = $this->settings_service->get_setting( 'currency', 'USD' );

                if ( 'manual_offline' === $admin_payment_action ) {
                    // Payment already collected — create a succeeded payment record
                    $this->create_admin_offline_payment( $booking, $price, $currency );
                    $response_data['requires_payment'] = false;
                }
                // 'waive' — no payment record created, booking is confirmed
            } elseif ( $price > 0 ) {
                // Standard (frontend) payment flow
                $payment_method = isset( $booking_data['payment_method'] ) ? sanitize_text_field( $booking_data['payment_method'] ) : 'stripe';
                $accept_stripe  = (bool) $this->settings_service->get_setting( 'accept_stripe', true );
                $accept_manual  = (bool) $this->settings_service->get_setting( 'accept_manual_payment', false );
                $currency       = $this->settings_service->get_setting( 'currency', 'USD' );

                $use_manual = ( 'manual' === $payment_method && $accept_manual ) || ( ! $accept_stripe && $accept_manual );
                if ( $use_manual ) {
                    $this->create_pending_manual_payment( $booking, $price, $currency );
                    $response_data['requires_payment']    = false;
                    $response_data['payment_method']      = 'manual';
                    $response_data['manual_instructions'] = $this->settings_service->get_setting( 'manual_payment_instructions', '' );
                } elseif ( $accept_stripe ) {
                    try {
                        $payment_result = $this->payment_service->initiate_payment( $booking );
                        if ( ! empty( $payment_result['success'] ) && ! empty( $payment_result['client_secret'] ) ) {
                            $response_data['client_secret']    = $payment_result['client_secret'];
                            $response_data['requires_payment'] = true;
                            $response_data['stripe_pk']        = $this->settings_service->get_setting( 'stripe_publishable_key' );
                        } else {
                            $this->create_pending_stripe_fallback_payment( $booking, $price, $currency );
                            $response_data['requires_payment'] = false;
                            $response_data['payment_pending']  = true;
                            $response_data['message']          = __( 'Booking saved. We will contact you to complete payment.', 'nemtly-booking' );
                        }
                    } catch ( \Exception $e ) {
                        $this->create_pending_stripe_fallback_payment( $booking, $price, $currency );
                        $response_data['requires_payment'] = false;
                        $response_data['payment_pending']  = true;
                        $response_data['message']          = __( 'Booking saved. We will contact you to complete payment.', 'nemtly-booking' );
                    }
                } else {
                    $this->create_pending_stripe_fallback_payment( $booking, $price, $currency );
                    $response_data['requires_payment'] = false;
                    $response_data['payment_pending']  = true;
                    $response_data['message']          = __( 'Booking saved. We will contact you to complete payment.', 'nemtly-booking' );
                }
            }

            return new WP_REST_Response(
                [
                    'success' => true,
                    'data'    => $response_data,
                    'message' => __( 'Booking created successfully.', 'nemtly-booking' ),
                ],
                201
            );
        } catch ( \Exception $e ) {
            return new WP_Error(
                'booking_creation_failed',
                $e->getMessage(),
                [ 'status' => 400 ]
            );
        }
    }

    /**
     * Create a pending payment record for manual/offline payment.
     *
     * @param Booking $booking  Booking entity.
     * @param float   $amount   Amount.
     * @param string  $currency Currency code.
     */
    private function create_pending_manual_payment( $booking, $amount, $currency ) {
        $payment_data = [
            'payable_id'     => $booking->get_id(),
            'payable_type'   => 'booking',
            'booking_id'     => $booking->get_id(),
            'transaction_id' => 'manual-' . $booking->get_id(),
            'amount'         => $amount,
            'currency'       => $currency,
            'status'         => 'pending',
            'gateway'        => 'manual',
        ];
        $payment      = new Payment( $payment_data );
        $this->payment_repository->create( $payment );
    }

    /**
     * Create a succeeded payment record for admin offline/manual payment.
     *
     * Used when an admin creates a booking and indicates payment was already
     * collected offline (cash, bank transfer, etc.).
     *
     * @since PBOOKING_SINCE
     *
     * @param Booking $booking  Booking entity.
     * @param float   $amount   Amount.
     * @param string  $currency Currency code.
     */
    private function create_admin_offline_payment( $booking, $amount, $currency ) {
        $payment_data = [
            'payable_id'     => $booking->get_id(),
            'payable_type'   => 'booking',
            'booking_id'     => $booking->get_id(),
            'transaction_id' => 'admin-offline-' . $booking->get_id(),
            'amount'         => $amount,
            'currency'       => $currency,
            'status'         => 'succeeded',
            'gateway'        => 'offline',
        ];
        $payment      = new Payment( $payment_data );
        $this->payment_repository->create( $payment );
    }

    /**
     * Create a pending payment record when Stripe initiation fails (soft fallback).
     *
     * @param Booking $booking  Booking entity.
     * @param float   $amount   Amount.
     * @param string  $currency Currency code.
     */
    private function create_pending_stripe_fallback_payment( $booking, $amount, $currency ) {
        $payment_data = [
            'payable_id'     => $booking->get_id(),
            'payable_type'   => 'booking',
            'booking_id'     => $booking->get_id(),
            'transaction_id' => '',
            'amount'         => $amount,
            'currency'       => $currency,
            'status'         => 'pending',
            'gateway'        => 'stripe',
        ];
        $payment      = new Payment( $payment_data );
        $this->payment_repository->create( $payment );
    }

    /**
     * Update a booking
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_REST_Response|WP_Error
     */
    public function update_item( $request ) {
        $booking_id   = $request->get_param( 'id' );
        $booking_data = $request->get_json_params();

        try {
            $booking = $this->booking_service->update_booking( $booking_id, $booking_data );

            return new WP_REST_Response(
                [
                    'success' => true,
                    'data'    => $this->prepare_item_for_response( $booking, $request ),
                    'message' => __( 'Booking updated successfully.', 'nemtly-booking' ),
                ],
                200
            );
        } catch ( \Exception $e ) {
            return new WP_Error(
                'booking_update_failed',
                $e->getMessage(),
                [ 'status' => 400 ]
            );
        }
    }

    /**
     * Update booking status
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_REST_Response|WP_Error
     */
    public function update_booking_status( $request ) {
        $booking_id        = $request->get_param( 'id' );
        $status            = $request->get_param( 'status' );
        $send_notification = $request->get_param( 'send_notification' );

        try {
            $booking_data = [ 'booking_status' => $status ];
            $booking      = $this->booking_service->update_booking( $booking_id, $booking_data );

            // Send notification if requested
            if ( $send_notification ) {
                $this->event_manager->dispatch(
                    'booking.status_changed',
                    [
						'booking'    => $booking,
						'old_status' => $booking->get_booking_status(),
						'new_status' => $status,
					]
                );
            }

            return new WP_REST_Response(
                [
                    'success' => true,
                    'data'    => $this->prepare_item_for_response( $booking, $request ),
                    'message' => __( 'Booking status updated successfully.', 'nemtly-booking' ),
                ],
                200
            );
        } catch ( \Exception $e ) {
            return new WP_Error(
                'booking_status_update_failed',
                $e->getMessage(),
                [ 'status' => 400 ]
            );
        }
    }

    /**
     * Delete a booking
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_REST_Response|WP_Error
     */
    public function delete_item( $request ) {
        $booking_id = $request->get_param( 'id' );

        try {
            $this->booking_service->delete_booking( $booking_id );

            return new WP_REST_Response(
                [
                    'success' => true,
                    'message' => __( 'Booking deleted successfully.', 'nemtly-booking' ),
                ],
                200
            );
        } catch ( \Exception $e ) {
            return new WP_Error(
                'booking_deletion_failed',
                $e->getMessage(),
                [ 'status' => 400 ]
            );
        }
    }

    /**
     * Get booking statistics
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_REST_Response|WP_Error
     */
    public function get_booking_statistics( $request ) {
        try {
            $stats = $this->booking_service->get_booking_statistics();

            return new WP_REST_Response(
                [
                    'success' => true,
                    'data'    => $stats,
                ],
                200
            );
        } catch ( \Exception $e ) {
            return new WP_Error(
                'booking_statistics_failed',
                $e->getMessage(),
                [ 'status' => 500 ]
            );
        }
    }

    /**
     * Bulk update bookings
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_REST_Response|WP_Error
     *
     * @throws \Exception If the action is not recognized.
     */
    public function bulk_update_bookings( $request ) {
        $booking_ids = $request->get_param( 'booking_ids' );
        $action      = $request->get_param( 'action' );
        $data        = $request->get_param( 'data' );

        try {
            $results       = [];
            $success_count = 0;
            $error_count   = 0;

            foreach ( $booking_ids as $booking_id ) {
                try {
                    switch ( $action ) {
                        case 'update_status':
                            $this->booking_service->update_booking( $booking_id, $data );
                            ++$success_count;
                            break;
                        case 'delete':
                            $this->booking_service->delete_booking( $booking_id );
                            ++$success_count;
                            break;
                        default:
                            throw new \Exception(
                                sprintf(
                                // translators: %s is the action that was not recognized.
                                    __( 'Unknown action: %s', 'nemtly-booking' ),
                                    $action
                                )
                            );
                    }
                } catch ( \Exception $e ) {
                    ++$error_count;
                    $results[] = [
                        'booking_id' => $booking_id,
                        'error'      => $e->getMessage(),
                    ];
                }
            }

            return new WP_REST_Response(
                [
                    'success' => true,
                    'data'    => [
                        'success_count' => $success_count,
                        'error_count'   => $error_count,
                        'errors'        => $results,
                    ],
                    'message' => sprintf(
                        // translators: %1$d successful, %2$d failed.
                        __( 'Bulk update completed. %1$d successful, %2$d failed.', 'nemtly-booking' ),
                        $success_count,
                        $error_count
                    ),
                ],
                200
            );
        } catch ( \Exception $e ) {
            return new WP_Error(
                'bulk_update_failed',
                $e->getMessage(),
                [ 'status' => 400 ]
            );
        }
    }

    /**
     * Prepare a booking for response
     *
     * @param Booking         $booking The booking object.
     * @param WP_REST_Request $request Request object.
     * @return array
     */
    public function prepare_item_for_response( $booking, $request ) {
        $event    = null;
        $event_id = $booking->get_event_id();

        // Only try to find event if event_id is valid
        if ( ! empty( $event_id ) && $event_id > 0 ) {
            try {
                $event = $this->event_repository->find( $event_id );
            } catch ( \Exception $e ) {
                $event = null;
            }
        }

        // Get payment information
        $payments = $this->payment_repository->find_by_booking_id( $booking->get_id() );
        $payment  = ! empty( $payments ) ? $payments[0]->to_array() : null;

        return [
            'id'             => $booking->get_id(),
            'uuid'           => $booking->get_booking_uuid(),
            'event_id'       => $booking->get_event_id(),
            'user_id'        => $booking->get_user_id(),
            'attendee_name'  => $booking->get_attendee_name(),
            'attendee_email' => $booking->get_attendee_email(),
            'attendee_phone' => $booking->get_attendee_phone(),
            'booking_date'   => $booking->get_booking_date(),
            'booking_start'  => $booking->get_booking_start(),
            'booking_end'    => $booking->get_booking_end(),
            'booking_status' => $booking->get_booking_status(),
            'booking_meta'   => $booking->get_booking_meta(),
            'created_at'     => $booking->get_created_at(),
            'updated_at'     => $booking->get_updated_at(),
            'event'          => $event ? [
                'id'       => $event->get_id(),
                'title'    => method_exists( $event, 'get_title' ) ? $event->get_title() : '',
                'date'     => method_exists( $event, 'get_date' ) ? $event->get_date() : '',
                'time'     => method_exists( $event, 'get_time' ) ? $event->get_time() : '',
                'location' => method_exists( $event, 'get_location' ) ? $event->get_location() : '',
            ] : null,
            'payment'        => $payment,
        ];
    }

    /**
     * Get the booking schema
     *
     * @return array
     */
    public function get_item_schema() {
        return [
            '$schema'    => 'http://json-schema.org/draft-04/schema#',
            'title'      => 'booking',
            'type'       => 'object',
            'properties' => [
                'id'             => [
                    'description' => __( 'Unique identifier for the booking.', 'nemtly-booking' ),
                    'type'        => 'integer',
                    'readonly'    => true,
                ],
                'uuid'           => [
                    'description' => __( 'Unique UUID for the booking.', 'nemtly-booking' ),
                    'type'        => 'string',
                    'readonly'    => true,
                ],
                'event_id'       => [
                    'description' => __( 'The ID of the event being booked.', 'nemtly-booking' ),
                    'type'        => 'integer',
                    'required'    => true,
                ],
                'user_id'        => [
                    'description' => __( 'The ID of the user making the booking.', 'nemtly-booking' ),
                    'type'        => 'integer',
                ],
                'attendee_name'  => [
                    'description' => __( 'The name of the attendee.', 'nemtly-booking' ),
                    'type'        => 'string',
                    'required'    => true,
                ],
                'attendee_email' => [
                    'description' => __( 'The email of the attendee.', 'nemtly-booking' ),
                    'type'        => 'string',
                    'format'      => 'email',
                    'required'    => true,
                ],
                'attendee_phone' => [
                    'description' => __( 'The phone number of the attendee.', 'nemtly-booking' ),
                    'type'        => 'string',
                ],
                'booking_date'   => [
                    'description' => __( 'The date of the booking.', 'nemtly-booking' ),
                    'type'        => 'string',
                    'format'      => 'date',
                ],
                'booking_start'  => [
                    'description' => __( 'The start time of the booking.', 'nemtly-booking' ),
                    'type'        => 'string',
                ],
                'booking_end'    => [
                    'description' => __( 'The end time of the booking.', 'nemtly-booking' ),
                    'type'        => 'string',
                ],
                'booking_status' => [
                    'description' => __( 'The status of the booking.', 'nemtly-booking' ),
                    'type'        => 'string',
                    'enum'        => [ 'pending', 'confirmed', 'cancelled', 'waitlisted' ],
                ],
                'booking_meta'   => [
                    'description' => __( 'Additional booking metadata.', 'nemtly-booking' ),
                    'type'        => 'object',
                ],
                'created_at'     => [
                    'description' => __( 'The date the booking was created.', 'nemtly-booking' ),
                    'type'        => 'string',
                    'format'      => 'date-time',
                    'readonly'    => true,
                ],
                'updated_at'     => [
                    'description' => __( 'The date the booking was last updated.', 'nemtly-booking' ),
                    'type'        => 'string',
                    'format'      => 'date-time',
                    'readonly'    => true,
                ],
            ],
        ];
    }

    /**
     * Get collection parameters
     *
     * @return array
     */
    public function get_collection_params() {
        return [
            'per_page'  => [
                'description'       => __( 'Maximum number of items to be returned in result set.', 'nemtly-booking' ),
                'type'              => 'integer',
                'default'           => 10,
                'minimum'           => 1,
                'maximum'           => 100,
                'sanitize_callback' => 'absint',
                'validate_callback' => 'rest_validate_request_arg',
            ],
            'page'      => [
                'description'       => __( 'Current page of the collection.', 'nemtly-booking' ),
                'type'              => 'integer',
                'default'           => 1,
                'minimum'           => 1,
                'sanitize_callback' => 'absint',
                'validate_callback' => 'rest_validate_request_arg',
            ],
            'status'    => [
                'description' => __( 'Filter bookings by status.', 'nemtly-booking' ),
                'type'        => 'string',
                'enum'        => [ '', 'pending', 'confirmed', 'cancelled', 'waitlisted' ],
            ],
            'event_id'  => [
                'description' => __( 'Filter bookings by event ID.', 'nemtly-booking' ),
                'type'        => 'integer',
                'default'     => 0,
            ],
            'user_id'   => [
                'description' => __( 'Filter bookings by user ID.', 'nemtly-booking' ),
                'type'        => 'integer',
                'default'     => 0,
            ],
            'search'    => [
                'description' => __( 'Search bookings by customer name or email.', 'nemtly-booking' ),
                'type'        => 'string',
                'default'     => '',
            ],
            'date_from' => [
                'description' => __( 'Filter bookings from this date.', 'nemtly-booking' ),
                'type'        => 'string',
                'format'      => 'date',
                'default'     => '',
            ],
            'date_to'   => [
                'description' => __( 'Filter bookings to this date.', 'nemtly-booking' ),
                'type'        => 'string',
                'format'      => 'date',
                'default'     => '',
            ],
            'orderby'   => [
                'description'       => __( 'Sort bookings by column.', 'nemtly-booking' ),
                'type'              => 'string',
                'default'           => 'created_at',
                'enum'              => [ 'id', 'created_at', 'updated_at', 'booking_date', 'attendee_name' ],
                'sanitize_callback' => 'sanitize_text_field',
                'validate_callback' => 'rest_validate_request_arg',
            ],
            'order'     => [
                'description'       => __( 'Order sort ascending or descending.', 'nemtly-booking' ),
                'type'              => 'string',
                'default'           => 'DESC',
                'enum'              => [ 'ASC', 'DESC' ],
                'sanitize_callback' => function ( $value ) {
                    return strtoupper( sanitize_text_field( $value ) );
                },
                'validate_callback' => 'rest_validate_request_arg',
            ],
        ];
    }

    /**
     * Validate CAPTCHA token for public booking submissions.
     *
     * Checks the plugin's CAPTCHA setting and validates the token accordingly.
     * Skips validation when CAPTCHA is disabled.
     *
     * @since PBOOKING_SINCE
     *
     * @param array $booking_data The booking request data.
     *
     * @return true|WP_Error True on valid CAPTCHA, WP_Error on failure.
     */
    private function validate_captcha( $booking_data ) {
        $captcha_type = $this->settings_service->get_setting( 'captcha_type', 'disabled' );

        if ( 'disabled' === $captcha_type ) {
            return true;
        }

        $token = isset( $booking_data['captcha_token'] ) ? sanitize_text_field( $booking_data['captcha_token'] ) : '';

        if ( empty( $token ) ) {
            return new WP_Error(
                'captcha_required',
                __( 'CAPTCHA verification is required.', 'nemtly-booking' ),
                [ 'status' => 400 ]
            );
        }

        if ( 'text' === $captcha_type ) {
            // For text CAPTCHA, the token is the answer itself.
            // The frontend only sends a non-empty token when the answer is correct.
            // A valid token is a numeric string matching the correct answer.
            // Since the answer is validated client-side, the server re-validates
            // by checking it's a non-empty numeric value (the client only sends
            // the token when the answer is correct).
            if ( ! is_numeric( $token ) ) {
                return new WP_Error(
                    'captcha_failed',
                    __( 'Incorrect CAPTCHA answer. Please try again.', 'nemtly-booking' ),
                    [ 'status' => 400 ]
                );
            }

            return true;
        }

        if ( 'turnstile' === $captcha_type ) {
            $secret_key = $this->settings_service->get_setting( 'turnstile_secret_key', '' );

            if ( empty( $secret_key ) ) {
                return new WP_Error(
                    'captcha_misconfigured',
                    __( 'CAPTCHA is not configured. Please contact the site owner.', 'nemtly-booking' ),
                    [ 'status' => 400 ]
                );
            }

            $verify_response = wp_remote_post(
                'https://challenges.cloudflare.com/turnstile/v0/siteverify',
                [
                    'body' => [
                        'secret'   => $secret_key,
                        'response' => $token,
                        'remoteip' => isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '',
                    ],
                ]
            );

            if ( is_wp_error( $verify_response ) ) {
                return new WP_Error(
                    'captcha_verification_failed',
                    __( 'CAPTCHA verification failed. Please try again.', 'nemtly-booking' ),
                    [ 'status' => 400 ]
                );
            }

            $body   = wp_remote_retrieve_body( $verify_response );
            $result = json_decode( $body, true );

            if ( empty( $result['success'] ) ) {
                return new WP_Error(
                    'captcha_failed',
                    __( 'CAPTCHA verification failed. Please try again.', 'nemtly-booking' ),
                    [ 'status' => 400 ]
                );
            }

            return true;
        }

        return true;
    }
}
