<?php
/**
 * Bookings REST Controller
 *
 * @package WahraRentalsBookings
 */

defined('ABSPATH') || exit;

/**
 * Bookings controller class
 */
class WAHRB_Bookings_Controller extends WAHRB_REST_Controller {

    /**
     * Route base
     *
     * @var string
     */
    protected $rest_base = 'bookings';

    /**
     * Register routes
     */
    public function register_routes(): void {
        // Vendor: List bookings
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base, [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_vendor_bookings'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => $this->get_collection_params(),
            ],
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'create_manual'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'product_id' => [
                        'type' => 'integer',
                        'required' => true,
                    ],
                    'start_date' => [
                        'type' => 'string',
                        'required' => true,
                    ],
                    'end_date' => [
                        'type' => 'string',
                        'required' => true,
                    ],
                    'qty' => [
                        'type' => 'integer',
                        'default' => 1,
                    ],
                    'customer_data' => [
                        'type' => 'object',
                        'properties' => [
                            'name' => ['type' => 'string'],
                            'email' => ['type' => 'string'],
                            'phone' => ['type' => 'string'],
                        ],
                    ],
                    'status' => [
                        'type' => 'string',
                        'enum' => ['pending', 'confirmed'],
                        'default' => 'confirmed',
                    ],
                ],
            ],
        ]);

        // Vendor: Get single booking
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_booking'],
                'permission_callback' => [$this, 'check_vendor_permission'],
            ],
            [
                'methods' => WP_REST_Server::EDITABLE,
                'callback' => [$this, 'update_booking'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => $this->get_update_args(),
            ],
        ]);

        // Vendor: Check-in
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/checkin', [
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'checkin'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'notes' => [
                        'type' => 'string',
                        'default' => '',
                    ],
                ],
            ],
        ]);

        // Vendor: Check-out
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/checkout', [
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'checkout'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'damage_amount' => [
                        'type' => 'number',
                        'default' => 0,
                    ],
                    'damage_notes' => [
                        'type' => 'string',
                        'default' => '',
                    ],
                ],
            ],
        ]);

        // Vendor: Cancel booking
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/cancel', [
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'cancel'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'reason' => [
                        'type' => 'string',
                        'default' => '',
                    ],
                ],
            ],
        ]);

        // Vendor: Update payment
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/payment', [
            [
                'methods' => WP_REST_Server::EDITABLE,
                'callback' => [$this, 'update_payment'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'field' => [
                        'type' => 'string',
                        'required' => true,
                        'enum' => ['deposit_status', 'security_status', 'balance_status', 'balance_payment_method', 'notes'],
                    ],
                    'value' => [
                        'type' => 'string',
                        'required' => true,
                    ],
                ],
            ],
        ]);

        // Vendor: Complete booking
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/complete', [
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'complete_booking'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'notes' => [
                        'type' => 'string',
                        'default' => '',
                    ],
                    'adjusted_total' => [
                        'type' => 'number',
                        'default' => null,
                        'description' => __('Vendor-adjusted final total at restitution.', 'wahra-rentals-bookings'),
                    ],
                ],
            ],
        ]);

        // Vendor: Refund booking
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/refund', [
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'refund'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'amount' => [
                        'type' => 'number',
                        'required' => true,
                    ],
                    'reason' => [
                        'type' => 'string',
                        'default' => '',
                    ],
                ],
            ],
        ]);

        // Vendor: Get calendar events (View-port optimized)
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/calendar', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_calendar_events'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'start' => [
                        'type' => 'string',
                        'required' => true,
                    ],
                    'end' => [
                        'type' => 'string',
                        'required' => true,
                    ],
                    'product_ids' => [
                        'type' => 'string',
                    ],
                    'status' => [
                        'type' => 'string',
                        'default' => 'all',
                    ],
                ],
            ],
        ]);

        // Vendor: Get booking stats
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/stats', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_stats'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'period' => [
                        'type' => 'string',
                        'default' => 'month',
                        'enum' => ['week', 'month', 'quarter', 'year'],
                    ],
                    'product_id' => [
                        'type' => 'integer',
                        'default' => 0,
                    ],
                ],
            ],
        ]);

        // Admin: List all bookings
        register_rest_route($this->namespace, '/admin/' . $this->rest_base, [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_all_bookings'],
                'permission_callback' => [$this, 'check_admin_permission'],
                'args' => $this->get_collection_params(),
            ],
        ]);

        // Admin: Get booking stats (cross-vendor)
        register_rest_route($this->namespace, '/admin/' . $this->rest_base . '/stats', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_admin_stats'],
                'permission_callback' => [$this, 'check_admin_permission'],
                'args' => [
                    'period' => [
                        'type' => 'string',
                        'default' => 'month',
                        'enum' => ['week', 'month', 'quarter', 'year'],
                    ],
                    'vendor_id' => [
                        'type' => 'integer',
                        'default' => 0,
                    ],
                ],
            ],
        ]);

        // ───────────────────────────────────────────
        // Booking Detail — Notes, Email Logs, SMS Logs
        // ───────────────────────────────────────────

        // Vendor: Get booking detail (full: notes, email logs, sms logs, payment)
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/detail', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_booking_detail'],
                'permission_callback' => [$this, 'check_vendor_permission'],
            ],
        ]);

        // Vendor: List notes for a booking
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/notes', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_booking_notes'],
                'permission_callback' => [$this, 'check_vendor_permission'],
            ],
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'add_booking_note'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'content' => [
                        'type' => 'string',
                        'required' => true,
                        'sanitize_callback' => 'sanitize_textarea_field',
                    ],
                    'is_private' => [
                        'type' => 'boolean',
                        'default' => false,
                    ],
                ],
            ],
        ]);

        // Vendor: Delete a note
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/notes/(?P<note_id>\d+)', [
            [
                'methods' => WP_REST_Server::DELETABLE,
                'callback' => [$this, 'delete_booking_note'],
                'permission_callback' => [$this, 'check_vendor_permission'],
            ],
        ]);

        // Vendor: Get email logs for a booking
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/email-logs', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_booking_email_logs'],
                'permission_callback' => [$this, 'check_vendor_permission'],
            ],
        ]);

        // Vendor: Send ad-hoc email for a booking
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/send-email', [
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'send_booking_email'],
                'permission_callback' => [$this, 'check_vendor_permission'],
                'args' => [
                    'recipient' => [
                        'type' => 'string',
                        'required' => true,
                        'format' => 'email',
                    ],
                    'subject' => [
                        'type' => 'string',
                        'required' => true,
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                    'body' => [
                        'type' => 'string',
                        'required' => true,
                        'sanitize_callback' => 'sanitize_textarea_field',
                    ],
                ],
            ],
        ]);

        // Vendor: Get SMS logs for a booking
        register_rest_route($this->namespace, '/vendor/' . $this->rest_base . '/(?P<id>\d+)/sms-logs', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_booking_sms_logs'],
                'permission_callback' => [$this, 'check_vendor_permission'],
            ],
        ]);

        // Public endpoint: Customer booking request submission.
        // Uses __return_true because unauthenticated customers need to submit
        // booking requests from the public product page. Data is validated
        // and sanitized within the callback. Creates a pending booking.
        register_rest_route($this->namespace, '/' . $this->rest_base . '/request', [
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'create_customer_request'],
                'permission_callback' => '__return_true', // Public endpoint for guest bookings
                'args' => [
                    'product_id' => [
                        'type' => 'integer',
                        'required' => true,
                    ],
                    'start_date' => [
                        'type' => 'string',
                        'required' => true,
                    ],
                    'end_date' => [
                        'type' => 'string',
                        'required' => true,
                    ],
                    'qty' => [
                        'type' => 'integer',
                        'default' => 1,
                    ],
                    'customer_name' => [
                        'type' => 'string',
                        'required' => true,
                    ],
                    'customer_email' => [
                        'type' => 'string',
                        'required' => true,
                    ],
                    'customer_phone' => [
                        'type' => 'string',
                        'default' => '',
                    ],
                    'message' => [
                        'type' => 'string',
                        'default' => '',
                    ],
                ],
            ],
        ]);
    }

    /**
     * Get collection params
     *
     * @return array
     */
    public function get_collection_params(): array {
        return [
            'page' => [
                'description' => __('Page number', 'wahra-rentals-bookings'),
                'type' => 'integer',
                'default' => 1,
            ],
            'per_page' => [
                'description' => __('Items per page', 'wahra-rentals-bookings'),
                'type' => 'integer',
                'default' => 20,
                'maximum' => 100,
            ],
            'status' => [
                'description' => __('Filter by status', 'wahra-rentals-bookings'),
                'type' => 'string',
                'enum' => ['pending', 'confirmed', 'checked_in', 'checked_out', 'completed', 'cancelled'],
            ],
            'product_id' => [
                'description' => __('Filter by product', 'wahra-rentals-bookings'),
                'type' => 'integer',
            ],
            'product_ids' => [
                'description' => __('Filter by multiple products', 'wahra-rentals-bookings'),
                'type' => 'string', 
                'sanitize_callback' => 'sanitize_text_field',
            ],
            'date_from' => [
                'description' => __('Filter from date', 'wahra-rentals-bookings'),
                'type' => 'string',
            ],
            'date_to' => [
                'description' => __('Filter to date', 'wahra-rentals-bookings'),
                'type' => 'string',
            ],
            'search' => [
                'description' => __('Search term', 'wahra-rentals-bookings'),
                'type' => 'string',
            ],
            'orderby' => [
                'description' => __('Order by field', 'wahra-rentals-bookings'),
                'type' => 'string',
                'default' => 'created_at',
                'enum' => ['created_at', 'start_dt', 'end_dt'],
            ],
            'order' => [
                'description' => __('Order direction', 'wahra-rentals-bookings'),
                'type' => 'string',
                'default' => 'DESC',
                'enum' => ['ASC', 'DESC'],
            ],
        ];
    }

    /**
     * Get update args
     *
     * @return array
     */
    private function get_update_args(): array {
        return [
            'status' => [
                'type' => 'string',
                'enum' => ['pending', 'confirmed', 'checked_in', 'checked_out', 'completed', 'cancelled'],
            ],
            'notes' => [
                'type' => 'string',
            ],
        ];
    }

    /**
     * Get vendor bookings
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function get_vendor_bookings($request) {
        $vendor_id = $this->get_vendor_id();
        $pagination = $this->get_pagination_params($request);

        global $wpdb;
        $db = WAHRB_Database::instance();

        // Match by vendor_id OR by product owned by this vendor (post_author)
        $where = ["(b.vendor_id = %d OR p.post_author = %d)"];
        $values = [$vendor_id, $vendor_id];

        // Filters
        if ($status = $request->get_param('status')) {
            $where[] = 'status = %s';
            $values[] = sanitize_text_field($status);
        }

        if ($product_ids = $request->get_param('product_ids')) {
            $ids = wp_parse_id_list($product_ids);
            if (!empty($ids)) {
                $placeholders = implode(',', array_fill(0, count($ids), '%d'));
                $where[] = "product_id IN ($placeholders)";
                $values = array_merge($values, $ids);
            }
        } elseif ($product_id = $request->get_param('product_id')) {
            $where[] = 'product_id = %d';
            $values[] = absint($product_id);
        }

        if ($date_from = $this->sanitize_date($request->get_param('date_from'))) {
            $where[] = 'start_dt >= %s';
            $values[] = $date_from;
        }

        if ($date_to = $this->sanitize_date($request->get_param('date_to'))) {
            $where[] = 'end_dt <= %s';
            $values[] = $date_to;
        }

        if ($search = $request->get_param('search')) {
            $where[] = '(customer_name LIKE %s OR customer_email LIKE %s OR booking_number LIKE %s)';
            $search_term = '%' . $wpdb->esc_like(sanitize_text_field($search)) . '%';
            $values[] = $search_term;
            $values[] = $search_term;
            $values[] = $search_term;
        }

        $where_sql = implode(' AND ', $where);
        $allowed_orderby = ['created_at', 'start_dt', 'end_dt', 'booking_number', 'status'];
        $raw_orderby = $request->get_param('orderby') ?: 'created_at';
        $raw_order = strtoupper($request->get_param('order') ?: 'DESC') === 'ASC' ? 'ASC' : 'DESC';
        $orderby = in_array($raw_orderby, $allowed_orderby, true) ? "b.{$raw_orderby} {$raw_order}" : 'b.created_at DESC';
        $offset = ($pagination['page'] - 1) * $pagination['per_page'];

        // Count total (with JOIN to check product ownership)
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom tables with dynamic where clause
        $total = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(DISTINCT b.booking_id) 
             FROM {$db->table('bookings')} b
             LEFT JOIN {$wpdb->posts} p ON b.product_id = p.ID
             WHERE {$where_sql}",
            ...$values
        ));

        // Get bookings (with JOIN to check product ownership)
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom tables with dynamic where clause
        $bookings_raw = $wpdb->get_results($wpdb->prepare(
            "SELECT b.*, p.post_title as product_name 
             FROM {$db->table('bookings')} b
             LEFT JOIN {$wpdb->posts} p ON b.product_id = p.ID
             WHERE {$where_sql}
             ORDER BY {$orderby}
             LIMIT %d OFFSET %d",
            ...array_merge($values, [$pagination['per_page'], $offset])
        ), ARRAY_A);

        $bookings = array_map([$this, 'prepare_booking_response'], $bookings_raw ?: []);

        $response = $this->success($bookings);
        return $this->add_pagination_headers($response, $total, $pagination['per_page']);
    }

    /**
     * Get single booking
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function get_booking($request) {
        $booking_id = absint($request->get_param('id'));
        $vendor_id = $this->get_vendor_id();

        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return $this->error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), 404);
        }

        if (!$this->is_admin() && (int) $booking->get('vendor_id') !== $vendor_id) {
            return $this->error('forbidden', __('Access denied.', 'wahra-rentals-bookings'), 403);
        }

        return $this->success($this->prepare_booking_response($booking->get_data(), true));
    }

    /**
     * Update booking
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function update_booking($request) {
        $booking_id = absint($request->get_param('id'));
        $vendor_id = $this->get_vendor_id();

        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return $this->error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), 404);
        }

        if (!$this->is_admin()) {
            // Check if vendor owns the booking OR the product
            $booking_vendor_id = (int) $booking->get('vendor_id');
            $product_author = (int) get_post_field('post_author', $booking->get('product_id'));
            
            if ($booking_vendor_id !== $vendor_id && $product_author !== $vendor_id) {
                return $this->error('forbidden', __('Access denied.', 'wahra-rentals-bookings'), 403);
            }
        }

        // Update status if provided
        if ($status = $request->get_param('status')) {
            $current = $booking->get('status');
            if ($status === 'confirmed' && $current === 'pending') {
                $booking->confirm();
            } elseif ($status === 'checked_in' && $current === 'confirmed') {
                $result = $booking->check_in($request->get_param('notes') ?: '');
                if (is_wp_error($result)) {
                    return $result;
                }
            } elseif ($status === 'checked_out' && $current === 'checked_in') {
                $booking->check_out($request->get_param('notes') ?: '');
            } elseif ($status === 'completed' && $current === 'checked_out') {
                $adj = $request->get_param('adjusted_total');
                $booking->complete($request->get_param('notes') ?: '', $adj !== null ? floatval($adj) : null);
            } elseif ($status === 'cancelled' && in_array($current, ['pending', 'confirmed', 'checked_in'], true)) {
                $booking->cancel($request->get_param('notes') ?: '');
            }
        }

        // Update notes if provided
        if ($notes = $request->get_param('notes')) {
            $booking->add_note($notes, 'vendor', $vendor_id);
        }

        return $this->success($this->prepare_booking_response($booking->get_data(), true));
    }

    /**
     * Check-in booking
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function checkin($request) {
        $booking_id = absint($request->get_param('id'));
        $vendor_id = $this->get_vendor_id();
        $notes = sanitize_textarea_field($request->get_param('notes'));

        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return $this->error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), 404);
        }

        if (!$this->is_admin() && (int) $booking->get('vendor_id') !== $vendor_id) {
            return $this->error('forbidden', __('Access denied.', 'wahra-rentals-bookings'), 403);
        }

        if ($booking->get('status') !== 'confirmed') {
            return $this->error('invalid_status', __('Only confirmed bookings can be checked in.', 'wahra-rentals-bookings'), 400);
        }

        $result = $booking->check_in($notes);

        if (is_wp_error($result)) {
            return $result;
        }

        if (!$result) {
            return $this->error('checkin_failed', __('Failed to check in booking.', 'wahra-rentals-bookings'), 500);
        }

        return $this->success([
            'message' => __('Check-in completed successfully.', 'wahra-rentals-bookings'),
            'booking' => $this->prepare_booking_response($booking->get_data(), true),
        ]);
    }

    /**
     * Check-out booking
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function checkout($request) {
        $booking_id = absint($request->get_param('id'));
        $vendor_id = $this->get_vendor_id();
        $damage_amount = floatval($request->get_param('damage_amount'));
        $damage_notes = sanitize_textarea_field($request->get_param('damage_notes'));

        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return $this->error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), 404);
        }

        if (!$this->is_admin() && (int) $booking->get('vendor_id') !== $vendor_id) {
            return $this->error('forbidden', __('Access denied.', 'wahra-rentals-bookings'), 403);
        }

        $result = $booking->check_out($damage_notes, $damage_amount);

        if (is_wp_error($result)) {
            return $result;
        }

        return $this->success([
            'message' => __('Check-out completed successfully.', 'wahra-rentals-bookings'),
            'booking' => $this->prepare_booking_response($booking->get_data(), true),
        ]);
    }

    /**
     * Cancel booking
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function cancel($request) {
        $booking_id = absint($request->get_param('id'));
        $vendor_id = $this->get_vendor_id();
        $reason = sanitize_textarea_field($request->get_param('reason'));

        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return $this->error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), 404);
        }

        if (!$this->is_admin() && (int) $booking->get('vendor_id') !== $vendor_id) {
            return $this->error('forbidden', __('Access denied.', 'wahra-rentals-bookings'), 403);
        }

        $result = $booking->cancel($reason);

        if (is_wp_error($result)) {
            return $result;
        }

        return $this->success([
            'message' => __('Booking cancelled.', 'wahra-rentals-bookings'),
            'refund_amount' => (float) ($booking->get('refund_amount') ?? 0),
            'booking' => $this->prepare_booking_response($booking->get_data()),
        ]);
    }

    /**
     * Complete booking
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function complete_booking($request) {
        $booking_id = absint($request->get_param('id'));
        $vendor_id = $this->get_vendor_id();
        $notes = sanitize_textarea_field($request->get_param('notes'));
        $adjusted_total = $request->get_param('adjusted_total');

        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return $this->error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), 404);
        }

        if (!$this->is_admin() && (int) $booking->get('vendor_id') !== $vendor_id) {
            return $this->error('forbidden', __('Access denied.', 'wahra-rentals-bookings'), 403);
        }

        if ($booking->get('status') !== 'checked_out') {
            return $this->error('invalid_status', __('Booking must be in checked-out status to complete.', 'wahra-rentals-bookings'), 400);
        }

        // Validate adjusted_total if provided
        $final_adjusted = null;
        if ($adjusted_total !== null && $adjusted_total !== '') {
            $final_adjusted = floatval($adjusted_total);
            if ($final_adjusted < 0) {
                return $this->error('invalid_amount', __('Adjusted total cannot be negative.', 'wahra-rentals-bookings'), 400);
            }
        }

        $result = $booking->complete($notes, $final_adjusted);

        if (!$result) {
            return $this->error('complete_failed', __('Failed to complete booking.', 'wahra-rentals-bookings'), 500);
        }

        $response_data = [
            'message' => __('Booking completed successfully.', 'wahra-rentals-bookings'),
            'booking' => $this->prepare_booking_response($booking->get_data(), true),
        ];

        if ($final_adjusted !== null) {
            $response_data['price_adjusted'] = true;
            /* translators: %s: adjusted total amount */
            $response_data['adjustment_message'] = sprintf(__('Final price adjusted to %s.', 'wahra-rentals-bookings'), wc_price($final_adjusted));
        }

        return $this->success($response_data);
    }

    /**
     * Update payment field
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function update_payment($request) {
        $booking_id = absint($request->get_param('id'));
        $vendor_id = $this->get_vendor_id();
        $field = sanitize_text_field($request->get_param('field'));
        $value = sanitize_text_field($request->get_param('value'));

        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return $this->error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), 404);
        }

        if (!$this->is_admin() && (int) $booking->get('vendor_id') !== $vendor_id) {
            return $this->error('forbidden', __('Access denied.', 'wahra-rentals-bookings'), 403);
        }

        $result = $booking->update_payment_field($field, $value);

        if (is_wp_error($result)) {
            return $result;
        }

        return $this->success([
            'message' => __('Payment updated successfully.', 'wahra-rentals-bookings'),
            'booking' => $this->prepare_booking_response($booking->get_data(), true),
        ]);
    }

    /**
     * Refund booking
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function refund($request) {
        $booking_id = absint($request->get_param('id'));
        $vendor_id = $this->get_vendor_id();
        $amount = floatval($request->get_param('amount'));
        $reason = sanitize_textarea_field($request->get_param('reason'));

        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return $this->error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), 404);
        }

        if (!$this->is_admin() && (int) $booking->get('vendor_id') !== $vendor_id) {
            return $this->error('forbidden', __('Access denied.', 'wahra-rentals-bookings'), 403);
        }

        // Get payment schedule
        $schedule = $booking->get_payment_schedule();
        if (!$schedule) {
            return $this->error('no_payment', __('No associated payment.', 'wahra-rentals-bookings'));
        }

        // Refund via WooCommerce
        $order_id = $schedule['deposit_order_id'];
        if ($order_id && $amount > 0) {
            $order = wc_get_order($order_id);
            if ($order) {
                $refund = wc_create_refund([
                    'amount' => $amount,
                    'reason' => $reason ?: __('Booking refund', 'wahra-rentals-bookings'),
                    'order_id' => $order_id,
                    'refund_payment' => true,
                ]);

                if (is_wp_error($refund)) {
                    return $refund;
                }
            }
        }

        // Log refund
        $db = WAHRB_Database::instance();
        $db->log('manual_refund', 'booking', $booking_id, [
            'amount' => $amount,
            'reason' => $reason,
            'by' => $vendor_id,
        ]);

        return $this->success([
            /* translators: %s: refund amount formatted with currency symbol */
            'message' => sprintf(__('Refund of %s processed.', 'wahra-rentals-bookings'), wc_price($amount)),
        ]);
    }

    /**
     * Create manual booking
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function create_manual($request) {
        $vendor_id = $this->get_vendor_id();
        $product_id = absint($request->get_param('product_id'));
        $start_date = $this->sanitize_date($request->get_param('start_date'));
        $end_date = $this->sanitize_date($request->get_param('end_date'));
        $qty = absint($request->get_param('qty')) ?: 1;
        $status = sanitize_text_field($request->get_param('status'));
        $customer_data = $request->get_param('customer_data') ?: [];

        // Verify ownership
        $product = wc_get_product($product_id);
        if (!$product || (int) get_post_field('post_author', $product_id) !== $vendor_id) {
            return $this->error('forbidden', __('Product not found or access denied.', 'wahra-rentals-bookings'), 403);
        }

        // Prepare data
        $data = [
            'vendor_id' => $vendor_id,
            'product_id' => $product_id,
            'start_dt' => $start_date . ' 14:00:00', // Default start time
            'end_dt' => $end_date . ' 11:00:00',     // Default end time
            'qty' => $qty,
            'customer_name' => sanitize_text_field($customer_data['name'] ?? ''),
            'customer_email' => sanitize_email($customer_data['email'] ?? ''),
            'customer_phone' => sanitize_text_field($customer_data['phone'] ?? ''),
            'notes_vendor' => __('Manual booking', 'wahra-rentals-bookings'),
            'booking_mode' => get_post_meta($product_id, '_wahrb_booking_mode', true) ?: 'day',
        ];

        // Create booking
        $booking = WAHRB_Booking::create($data);

        if (is_wp_error($booking)) {
            return $booking;
        }

        // Set status
        if ($status === 'confirmed') {
            $booking->confirm();
        }

        // Mark as manual meta
        $booking->update_meta('is_manual_booking', true);

        return $this->success([
            'message' => __('Manual booking created successfully.', 'wahra-rentals-bookings'),
            'booking' => $this->prepare_booking_response($booking->get_data()),
        ]);
    }

    /**
     * Create customer booking request (PUBLIC endpoint)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function create_customer_request($request) {
        // Rate limiting: max 5 booking requests per minute per IP
        $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'] ?? ''));
        $rate_key = 'wahrb_req_rate_' . md5($ip);
        $attempts = (int) get_transient($rate_key);
        if ($attempts >= 5) {
            return $this->error('rate_limited', __('Too many requests. Please try again later.', 'wahra-rentals-bookings'), 429);
        }
        set_transient($rate_key, $attempts + 1, 60);

        $product_id = absint($request->get_param('product_id'));
        $start_date = $this->sanitize_date($request->get_param('start_date'));
        $end_date = $this->sanitize_date($request->get_param('end_date'));
        $qty = absint($request->get_param('qty')) ?: 1;
        $customer_name = sanitize_text_field($request->get_param('customer_name'));
        $customer_email = sanitize_email($request->get_param('customer_email'));
        $customer_phone = sanitize_text_field($request->get_param('customer_phone'));
        $message = sanitize_textarea_field($request->get_param('message'));

        // Validate product exists
        $product = wc_get_product($product_id);
        if (!$product) {
            return $this->error('invalid_product', __('Product not found.', 'wahra-rentals-bookings'), 404);
        }

        // Get vendor ID from product - use Dokan function if available, else post author
        $vendor_id = 0;
        if (wahrb_is_dokan_active() && function_exists('dokan_get_vendor_by_product')) {
            $vendor = dokan_get_vendor_by_product($product_id);
            if ($vendor && $vendor->get_id()) {
                $vendor_id = $vendor->get_id();
            }
        }
        // Fallback to post author (works for both modes)
        if (!$vendor_id) {
            $vendor_id = (int) get_post_field('post_author', $product_id);
        }

        // Validate email
        if (!is_email($customer_email)) {
            return $this->error('invalid_email', __('Invalid email address.', 'wahra-rentals-bookings'), 400);
        }

        // Calculate price
        $pricing_engine = WAHRB_Pricing_Engine::instance();
        $price_data = $pricing_engine->calculate($product_id, $start_date, $end_date, null, $qty);

        // ── Coupon validation & discount ──
        $coupon_code = sanitize_text_field($request->get_param('coupon_code') ?? '');
        $discount_amount = 0;
        $coupon_info = null;

        $coupon_settings = get_option('wahrb_settings_coupons', ['enabled' => false]);
        if (!empty($coupon_settings['enabled']) && $coupon_code !== '') {
            $coupon_manager = WAHRB_Coupon_Manager::instance();
            $validation = $coupon_manager->validate_coupon($coupon_code, $product_id, $price_data['total'] ?? 0, $customer_email);

            if ($validation['valid']) {
                $discount_amount = $coupon_manager->calculate_discount($validation['coupon'], $price_data['total'] ?? 0);
                $coupon_info = [
                    'coupon_id'       => (int) $validation['coupon']['coupon_id'],
                    'code'            => $coupon_code,
                    'discount_type'   => $validation['coupon']['discount_type'],
                    'discount_value'  => $validation['coupon']['discount_value'],
                    'discount_amount' => $discount_amount,
                ];
            }
            // If coupon is invalid, we silently ignore (don't block booking)
        }

        $final_total = max(0, ($price_data['total'] ?? 0) - $discount_amount);

        // Prepare booking data
        $data = [
            'vendor_id' => $vendor_id,
            'product_id' => $product_id,
            'start_dt' => $start_date . ' 12:00:00',
            'end_dt' => $end_date . ' 12:00:00',
            'qty' => $qty,
            'customer_name' => $customer_name,
            'customer_email' => $customer_email,
            'customer_phone' => $customer_phone,
            'notes_customer' => $message,
            'booking_mode' => get_post_meta($product_id, '_wahrb_booking_mode', true) ?: 'day',
            'total_amount' => $final_total,
            'status' => 'pending', // Customer requests start as pending
        ];

        // Create booking (triggers wahrb_booking_created hook -> emails)
        $booking = WAHRB_Booking::create($data);

        if (is_wp_error($booking)) {
            return $booking;
        }

        // ── Store custom form fields ──
        $form_template_id = absint($request->get_param('form_template_id') ?? 0);
        $raw_form_data    = $request->get_param('form_data');

        $form_builder_settings = get_option('wahrb_settings_form_builder', ['enabled' => false]);
        if (!empty($form_builder_settings['enabled']) && $form_template_id > 0 && is_array($raw_form_data) && !empty($raw_form_data)) {
            $form_builder = WAHRB_Form_Builder::instance();
            $template = $form_builder->get_template($form_template_id);

            if ($template) {
                $form_result = $form_builder->validate_submission($template, $raw_form_data);
                if ($form_result['valid']) {
                    $form_builder->save_form_data($booking, $template, $form_result['sanitized']);
                }
                // If validation fails, we still proceed with booking (form data is optional enhancement)
            }
        }

        // ── Record coupon usage ──
        if ($coupon_info) {
            $coupon_manager = WAHRB_Coupon_Manager::instance();
            $coupon_manager->record_usage($coupon_info['coupon_id'], $booking->get_id(), $customer_email, $discount_amount);

            // Store coupon info in booking meta
            $booking->update_meta('coupon', $coupon_info);
        }

        // Return success with booking reference
        return $this->success([
            'success' => true,
            'message' => __('Your booking request has been sent successfully. You will receive a confirmation by email.', 'wahra-rentals-bookings'),
            'booking_id' => $booking->get_id(),
            'booking_number' => $booking->get('booking_number'),
            'total' => $final_total,
            'discount' => $discount_amount,
            'formatted_total' => wc_price($final_total),
        ]);
    }
    
    /**
     * Get calendar events (Optimized for FullCalendar)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function get_calendar_events($request) {
        $vendor_id = $this->get_vendor_id();
        $start_date = $this->sanitize_date($request->get_param('start'));
        $end_date = $this->sanitize_date($request->get_param('end'));
        
        // FullCalendar sends ISO strings, sanitize simply
        $start_date = substr($start_date, 0, 10);
        $end_date = substr($end_date, 0, 10);

        if (!$start_date || !$end_date) {
            return $this->error('invalid_dates', __('Dates invalides.', 'wahra-rentals-bookings'));
        }

        global $wpdb;
        $db = WAHRB_Database::instance();

        $where = ['vendor_id = %d'];
        $values = [$vendor_id];

        // Date Range (Viewport fetching)
        // Events that overlap with the view: start < viewEnd AND end > viewStart
        $where[] = 'start_dt < %s AND end_dt > %s';
        $values[] = $end_date . ' 23:59:59';
        $values[] = $start_date . ' 00:00:00';

        // Filter by products
        if ($product_ids = $request->get_param('product_ids')) {
            $ids = wp_parse_id_list($product_ids);
            if (!empty($ids)) {
                $placeholders = implode(',', array_fill(0, count($ids), '%d'));
                $where[] = "product_id IN ($placeholders)";
                $values = array_merge($values, $ids);
            }
        }

        // Filter by status
        $status_param = sanitize_text_field($request->get_param('status') ?? 'all');
        $allowed_statuses = ['pending', 'confirmed', 'checked_in', 'checked_out', 'completed', 'cancelled'];
        if ($status_param && $status_param !== 'all' && in_array($status_param, $allowed_statuses, true)) {
            $where[] = 'status = %s';
            $values[] = $status_param;
        } elseif ($status_param === 'all') {
            // Show all except refunded/failed
            $where[] = "status NOT IN ('refunded', 'failed')";
        } else {
            // Default: exclude cancelled & refunded
            $where[] = "status NOT IN ('cancelled', 'refunded')";
        }

        $where_sql = implode(' AND ', $where);

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom tables with dynamic where clause
        $bookings = $wpdb->get_results($wpdb->prepare(
            "SELECT booking_id, product_id, start_dt, end_dt, status, customer_name, qty 
             FROM {$db->table('bookings')}
             WHERE {$where_sql}",
            ...$values
        ));

        $events = [];
        foreach ($bookings as $booking) {
            $product_name = get_the_title($booking->product_id) ?: __('Unknown product', 'wahra-rentals-bookings');
            
            // Determine color based on status (class names match wahrb-calendar.css)
            $class_name = 'fc-event-confirmed';
            if ($booking->status === 'pending') $class_name = 'fc-event-pending';
            if ($booking->status === 'checked_in') $class_name = 'fc-event-active';
            if ($booking->status === 'checked_out') $class_name = 'fc-event-checkout';
            if ($booking->status === 'completed') $class_name = 'fc-event-completed';
            if ($booking->status === 'cancelled') $class_name = 'fc-event-cancelled';

            $events[] = [
                'id' => $booking->booking_id,
                'title' => sprintf('#%s - %s (%s)', $booking->booking_id, $product_name, $booking->customer_name),
                'start' => $booking->start_dt,
                'end' => $booking->end_dt,
                'className' => $class_name,
                'extendedProps' => [
                    'status' => $booking->status,
                    'customer' => $booking->customer_name,
                    'qty' => $booking->qty
                ]
            ];
        }

        return $this->success($events);
    }

    /**
     * Get booking stats (vendor)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function get_stats($request) {
        try {
            $vendor_id = $this->get_vendor_id();
            $period = $request->get_param('period') ?: 'month';
            $product_id = absint($request->get_param('product_id'));

            $cache_suffix = $product_id ? "_{$product_id}" : '';
            $cache_key = "wahrb_stats_{$vendor_id}_{$period}{$cache_suffix}";
            $cached_stats = get_transient($cache_key);

            if ($cached_stats !== false) {
                return $this->success($cached_stats);
            }

            $stats = $this->build_stats($vendor_id, $period, $product_id);

            set_transient($cache_key, $stats, 900);

            return $this->success($stats);
        } catch (\Throwable $e) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional debug logging
            error_log('[WAHRB] Vendor stats error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
            return new WP_Error('stats_error', $e->getMessage(), ['status' => 500]);
        }
    }

    /**
     * Get booking stats (admin - cross-vendor)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function get_admin_stats($request) {
        try {
            $period = $request->get_param('period') ?: 'month';
            $vendor_id = absint($request->get_param('vendor_id'));

            $cache_key = "wahrb_stats_v2_{$period}" . ($vendor_id ? "_{$vendor_id}" : '');
            $cached_stats = get_transient($cache_key);

            if ($cached_stats !== false) {
                return $this->success($cached_stats);
            }

            $stats = $this->build_stats($vendor_id, $period, 0);

            // Add active vendors count for admin
            global $wpdb;
            $db = WAHRB_Database::instance();
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table
            $stats['active_vendors'] = (int) $wpdb->get_var("SELECT COUNT(DISTINCT vendor_id) FROM {$db->table('bookings')} WHERE deleted_at IS NULL");

            // Check for DB errors
            if (!empty($wpdb->last_error)) {
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional debug logging
                error_log('[WAHRB] Admin stats DB error: ' . $wpdb->last_error);
                return new WP_Error('db_error', __('Database error while loading statistics.', 'wahra-rentals-bookings'), ['status' => 500]);
            }

            set_transient($cache_key, $stats, 900);

            return $this->success($stats);
        } catch (\Throwable $e) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional debug logging
            error_log('[WAHRB] Admin stats error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
            return new WP_Error('stats_error', $e->getMessage(), ['status' => 500]);
        }
    }

    /**
     * Build stats data for a vendor or admin
     *
     * @param int    $vendor_id  Vendor ID (0 for all vendors)
     * @param string $period     Period filter
     * @param int    $product_id Product ID filter (0 for all)
     * @return array Stats data
     */
    private function build_stats(int $vendor_id, string $period, int $product_id): array {
        global $wpdb;
        $db = WAHRB_Database::instance();
        $table = $db->table('bookings');

        // Calculate date range
        $now = current_time('mysql');
        switch ($period) {
            case 'week':
                $start = gmdate('Y-m-d', strtotime('-1 week'));
                break;
            case 'quarter':
                $start = gmdate('Y-m-d', strtotime('-3 months'));
                break;
            case 'year':
                $start = gmdate('Y-m-d', strtotime('-1 year'));
                break;
            default:
                $start = gmdate('Y-m-d', strtotime('-1 month'));
        }

        // Build dynamic WHERE clause
        $where_parts = ['created_at >= %s', 'deleted_at IS NULL'];
        $where_args = [$start];

        if ($vendor_id > 0) {
            $where_parts[] = 'vendor_id = %d';
            $where_args[] = $vendor_id;
        }
        if ($product_id > 0) {
            $where_parts[] = 'product_id = %d';
            $where_args[] = $product_id;
        }

        $where_sql = implode(' AND ', $where_parts);

        // Total bookings
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table, dynamic WHERE
        $total_bookings = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE {$where_sql}", ...$where_args));

        // Status breakdown
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table, dynamic WHERE
        $status_rows = $wpdb->get_results($wpdb->prepare("SELECT status, COUNT(*) as cnt FROM {$table} WHERE {$where_sql} GROUP BY status", ...$where_args)) ?: [];

        $status_breakdown = array_fill_keys(['pending', 'confirmed', 'checked_in', 'checked_out', 'completed', 'cancelled', 'failed'], 0);
        foreach ($status_rows as $row) {
            if (isset($status_breakdown[$row->status])) {
                $status_breakdown[$row->status] = (int) $row->cnt;
            }
        }

        $confirmed_bookings = $status_breakdown['confirmed'] + $status_breakdown['checked_in'] + $status_breakdown['checked_out'] + $status_breakdown['completed'];

        // Revenue from pricing_snapshot JSON (bookings table has no total_amount column)
        $rev_where = $where_sql . " AND status NOT IN ('cancelled', 'failed') AND pricing_snapshot IS NOT NULL";
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table, dynamic WHERE
        $total_revenue = (float) $wpdb->get_var($wpdb->prepare("SELECT COALESCE(SUM(CAST(JSON_UNQUOTE(JSON_EXTRACT(pricing_snapshot, '$.total')) AS DECIMAL(10,2))), 0) FROM {$table} WHERE {$rev_where}", ...$where_args));

        // Upcoming bookings
        $up_parts = ["start_dt > %s", "status = 'confirmed'", 'deleted_at IS NULL'];
        $up_args = [$now];
        if ($vendor_id > 0) {
            $up_parts[] = 'vendor_id = %d';
            $up_args[] = $vendor_id;
        }
        if ($product_id > 0) {
            $up_parts[] = 'product_id = %d';
            $up_args[] = $product_id;
        }
        $up_where = implode(' AND ', $up_parts);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table
        $upcoming = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE {$up_where}", ...$up_args));

        // Today's check-ins
        $today = gmdate('Y-m-d');
        $ci_parts = ['DATE(start_dt) = %s', "status = 'confirmed'", 'deleted_at IS NULL'];
        $ci_args = [$today];
        if ($vendor_id > 0) {
            $ci_parts[] = 'vendor_id = %d';
            $ci_args[] = $vendor_id;
        }
        if ($product_id > 0) {
            $ci_parts[] = 'product_id = %d';
            $ci_args[] = $product_id;
        }
        $ci_where = implode(' AND ', $ci_parts);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table
        $today_checkins = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE {$ci_where}", ...$ci_args));

        // Today's check-outs
        $co_parts = ['DATE(end_dt) = %s', "status = 'checked_in'", 'deleted_at IS NULL'];
        $co_args = [$today];
        if ($vendor_id > 0) {
            $co_parts[] = 'vendor_id = %d';
            $co_args[] = $vendor_id;
        }
        if ($product_id > 0) {
            $co_parts[] = 'product_id = %d';
            $co_args[] = $product_id;
        }
        $co_where = implode(' AND ', $co_parts);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table
        $today_checkouts = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE {$co_where}", ...$co_args));

        // Monthly data (last 12 months, always)
        $m_start = gmdate('Y-m-d', strtotime('-12 months'));
        $m_parts = ['created_at >= %s', 'deleted_at IS NULL'];
        $m_args = [$m_start];
        if ($vendor_id > 0) {
            $m_parts[] = 'vendor_id = %d';
            $m_args[] = $vendor_id;
        }
        if ($product_id > 0) {
            $m_parts[] = 'product_id = %d';
            $m_args[] = $product_id;
        }
        $m_where = implode(' AND ', $m_parts);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table
        $monthly_rows = $wpdb->get_results($wpdb->prepare("SELECT DATE_FORMAT(created_at, '%%Y-%%m') as month_key, COUNT(*) as bookings, COALESCE(SUM(CASE WHEN status NOT IN ('cancelled','failed') AND pricing_snapshot IS NOT NULL THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(pricing_snapshot, '$.total')) AS DECIMAL(10,2)) ELSE 0 END), 0) as revenue FROM {$table} WHERE {$m_where} GROUP BY month_key ORDER BY month_key", ...$m_args)) ?: [];

        // Build full 12-month array with gaps filled
        $month_lookup = [];
        foreach ($monthly_rows as $row) {
            $month_lookup[$row->month_key] = [
                'bookings' => (int) $row->bookings,
                'revenue' => (float) $row->revenue,
            ];
        }
        $monthly_data = [];
        for ($i = 11; $i >= 0; $i--) {
            $m = gmdate('Y-m', strtotime("-{$i} months"));
            $monthly_data[] = [
                'month' => $m,
                'bookings' => $month_lookup[$m]['bookings'] ?? 0,
                'revenue' => $month_lookup[$m]['revenue'] ?? 0,
            ];
        }

        // Top products (top 10 by booking count in period)
        $tp_parts = ['created_at >= %s', 'deleted_at IS NULL'];
        $tp_args = [$start];
        if ($vendor_id > 0) {
            $tp_parts[] = 'vendor_id = %d';
            $tp_args[] = $vendor_id;
        }
        if ($product_id > 0) {
            $tp_parts[] = 'product_id = %d';
            $tp_args[] = $product_id;
        }
        $tp_where = implode(' AND ', $tp_parts);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table
        $top_rows = $wpdb->get_results($wpdb->prepare("SELECT product_id, COUNT(*) as bookings, COALESCE(SUM(CASE WHEN status NOT IN ('cancelled','failed') AND pricing_snapshot IS NOT NULL THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(pricing_snapshot, '$.total')) AS DECIMAL(10,2)) ELSE 0 END), 0) as revenue FROM {$table} WHERE {$tp_where} GROUP BY product_id ORDER BY bookings DESC LIMIT 10", ...$tp_args)) ?: [];

        $top_products = [];
        foreach ($top_rows as $row) {
            $product = wc_get_product($row->product_id);
            $name = $product ? $product->get_name() : sprintf(
                /* translators: %d: product ID */
                __('Product #%d (deleted)', 'wahra-rentals-bookings'),
                $row->product_id
            );
            $top_products[] = [
                'product_id' => (int) $row->product_id,
                'name' => $name,
                'bookings' => (int) $row->bookings,
                'revenue' => (float) $row->revenue,
            ];
        }

        // Category breakdown (group bookings by platform category)
        $category_breakdown = [];
        if (class_exists('WAHRB_Platform_Categories')) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table
            $cat_rows = $wpdb->get_results($wpdb->prepare("SELECT product_id, COUNT(*) as bookings, COALESCE(SUM(CASE WHEN status NOT IN ('cancelled','failed') AND pricing_snapshot IS NOT NULL THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(pricing_snapshot, '$.total')) AS DECIMAL(10,2)) ELSE 0 END), 0) as revenue FROM {$table} WHERE {$where_sql} GROUP BY product_id", ...$where_args)) ?: [];

            $temp_cats = [];
            foreach ($cat_rows as $row) {
                $cat_slug = WAHRB_Platform_Categories::get_product_category((int) $row->product_id);
                if (!isset($temp_cats[$cat_slug])) {
                    $cat_def = WAHRB_Platform_Categories::get_category($cat_slug);
                    $temp_cats[$cat_slug] = [
                        'slug' => $cat_slug,
                        'label' => $cat_def ? $cat_def['label'] : $cat_slug,
                        'emoji' => $cat_def ? $cat_def['emoji'] : '',
                        'bookings' => 0,
                        'revenue' => 0.0,
                    ];
                }
                $temp_cats[$cat_slug]['bookings'] += (int) $row->bookings;
                $temp_cats[$cat_slug]['revenue'] += (float) $row->revenue;
            }
            $category_breakdown = array_values($temp_cats);
        }

        return [
            'period' => $period,
            'total_bookings' => $total_bookings,
            'confirmed_bookings' => $confirmed_bookings,
            'conversion_rate' => $total_bookings > 0 ? round(($confirmed_bookings / $total_bookings) * 100, 1) : 0,
            'total_revenue' => $total_revenue,
            'upcoming' => $upcoming,
            'today_checkins' => $today_checkins,
            'today_checkouts' => $today_checkouts,
            'status_breakdown' => $status_breakdown,
            'monthly_data' => $monthly_data,
            'top_products' => $top_products,
            'category_breakdown' => $category_breakdown,
        ];
    }

    /**
     * Get all bookings (admin)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function get_all_bookings($request) {
        $pagination = $this->get_pagination_params($request);

        global $wpdb;
        $db = WAHRB_Database::instance();

        $where = ['1=1'];
        $values = [];

        // Filters
        if ($status = $request->get_param('status')) {
            $where[] = 'status = %s';
            $values[] = sanitize_text_field($status);
        }

        if ($vendor_id = $request->get_param('vendor_id')) {
            $where[] = 'vendor_id = %d';
            $values[] = absint($vendor_id);
        }

        if ($product_ids = $request->get_param('product_ids')) {
            $ids = wp_parse_id_list($product_ids);
            if (!empty($ids)) {
                $placeholders = implode(',', array_fill(0, count($ids), '%d'));
                $where[] = "product_id IN ($placeholders)";
                $values = array_merge($values, $ids);
            }
        } elseif ($product_id = $request->get_param('product_id')) {
            $where[] = 'product_id = %d';
            $values[] = absint($product_id);
        }

        if ($date_from = $this->sanitize_date($request->get_param('date_from'))) {
            $where[] = 'start_dt >= %s';
            $values[] = $date_from;
        }

        if ($date_to = $this->sanitize_date($request->get_param('date_to'))) {
            $where[] = 'end_dt <= %s';
            $values[] = $date_to;
        }

        $where_sql = implode(' AND ', $where);
        $allowed_orderby = ['created_at', 'start_dt', 'end_dt', 'booking_number', 'status'];
        $raw_orderby = $request->get_param('orderby') ?: 'created_at';
        $raw_order = strtoupper($request->get_param('order') ?: 'DESC') === 'ASC' ? 'ASC' : 'DESC';
        $orderby = in_array($raw_orderby, $allowed_orderby, true) ? "{$raw_orderby} {$raw_order}" : 'created_at DESC';
        $offset = ($pagination['page'] - 1) * $pagination['per_page'];

        // Count total
        $total_query = "SELECT COUNT(*) FROM {$db->table('bookings')} WHERE {$where_sql}";
        if (empty($values)) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query without parameters
            $total = (int) $wpdb->get_var($total_query);
        } else {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with safe parameters
            $total = (int) $wpdb->get_var($wpdb->prepare($total_query, ...$values));
        }

        // Get bookings
        $query = "SELECT * FROM {$db->table('bookings')}
                  WHERE {$where_sql}
                  ORDER BY {$orderby}
                  LIMIT %d OFFSET %d";

        if (empty($values)) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with safe parameters
            $bookings_raw = $wpdb->get_results($wpdb->prepare($query, $pagination['per_page'], $offset), ARRAY_A);
        } else {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with safe parameters
            $bookings_raw = $wpdb->get_results($wpdb->prepare($query, ...array_merge($values, [$pagination['per_page'], $offset])), ARRAY_A);
        }

        $bookings = array_map([$this, 'prepare_booking_response'], $bookings_raw ?: []);

        $response = $this->success($bookings);
        return $this->add_pagination_headers($response, $total, $pagination['per_page']);
    }

    /**
     * Prepare booking for response
     *
     * @param array $booking Booking data
     * @param bool $include_payment Include payment details
     * @return array
     */
    private function prepare_booking_response(array $booking, bool $include_payment = false): array {
        $product = wc_get_product($booking['product_id']);

        $response = [
            'id' => (int) $booking['booking_id'],
            'booking_number' => $booking['booking_number'],
            'status' => $booking['status'],
            'product' => [
                'id' => (int) $booking['product_id'],
                'name' => $product ? $product->get_name() : __('Deleted product', 'wahra-rentals-bookings'),
                'image' => $product ? wp_get_attachment_url($product->get_image_id()) : null,
            ],
            'customer' => [
                'id' => (int) ($booking['customer_id'] ?? 0),
                'name' => $booking['customer_name'],
                'email' => $booking['customer_email'],
                'phone' => $booking['customer_phone'],
            ],
            'dates' => [
                'start' => $booking['start_dt'],
                'end' => $booking['end_dt'],
                'days' => $this->calculate_days($booking['start_dt'], $booking['end_dt']),
            ],
            'qty' => (int) $booking['qty'],
            'amounts' => [
                'subtotal' => (float) ($booking['subtotal_amount'] ?? 0),
                'discount' => (float) ($booking['discount_amount'] ?? 0),
                'total' => (float) ($booking['total_amount'] ?? 0),
                'currency' => $booking['currency_code'] ?? get_woocommerce_currency(),
            ],
            'booking_mode' => $booking['booking_mode'] ?? 'day',
            'created_at' => $booking['created_at'] ?? null,
            'notes_customer' => $booking['notes_customer'] ?? null,
            'notes_vendor' => $booking['notes_vendor'] ?? null,
            'checkin_dt' => $booking['checkin_dt'] ?? null,
            'checkin_notes' => $booking['checkin_notes'] ?? null,
            'checkout_dt' => $booking['checkout_dt'] ?? null,
            'checkout_notes' => $booking['checkout_notes'] ?? null,
            'cancellation_reason' => $booking['cancellation_reason'] ?? null,
        ];

        // Always include payment data for lifecycle tracking
        $booking_obj = new WAHRB_Booking($booking['booking_id']);
        $schedule = $booking_obj->get_payment_schedule();

        if ($schedule) {
            $response['amounts'] = [
                'subtotal' => (float) ($schedule['subtotal_amount'] ?? $response['amounts']['subtotal']),
                'discount' => (float) ($booking['discount_amount'] ?? 0),
                'total' => (float) ($schedule['total_amount'] ?? $response['amounts']['total']),
                'currency' => $schedule['currency_code'] ?? $response['amounts']['currency'],
            ];

            $response['payment'] = [
                'total' => (float) ($schedule['total_amount'] ?? 0),
                'currency' => $schedule['currency_code'] ?? get_woocommerce_currency(),
                'deposit' => [
                    'enabled' => (bool) ($schedule['deposit_enabled'] ?? false),
                    'amount' => (float) ($schedule['deposit_amount_final'] ?? 0),
                    'status' => $schedule['deposit_status'] ?? 'not_due',
                    'paid_at' => $schedule['deposit_paid_at'] ?? null,
                    'refundable' => (bool) ($schedule['deposit_refundable'] ?? false),
                ],
                'security_deposit' => [
                    'enabled' => (bool) ($schedule['security_deposit_enabled'] ?? false),
                    'amount' => (float) ($schedule['security_deposit_amount_final'] ?? 0),
                    'status' => $schedule['security_status'] ?? 'not_due',
                    'mode' => $schedule['security_mode'] ?? 'capture',
                    'damage_claim' => (float) ($schedule['security_damage_claim'] ?? 0),
                    'damage_notes' => $schedule['security_damage_notes'] ?? null,
                ],
                'balance' => [
                    'enabled' => (bool) ($schedule['balance_enabled'] ?? false),
                    'amount' => (float) ($schedule['balance_amount_final'] ?? 0),
                    'status' => $schedule['balance_status'] ?? 'not_due',
                    'due_at' => $schedule['balance_due_at'] ?? null,
                    'paid_at' => $schedule['balance_paid_at'] ?? null,
                    'allow_cod' => (bool) ($schedule['balance_allow_cod'] ?? false),
                    'payment_method' => $schedule['balance_payment_method'] ?? null,
                ],
                'notes' => $schedule['notes'] ?? null,
            ];
        } else {
            $response['payment'] = null;
        }

        return $response;
    }

    /**
     * Calculate days between dates
     *
     * @param string $start Start date
     * @param string $end End date
     * @return int
     */
    private function calculate_days(string $start, string $end): int {
        $start_dt = new DateTime($start);
        $end_dt = new DateTime($end);
        return (int) $start_dt->diff($end_dt)->days + 1;
    }

    // ───────────────────────────────────────────
    // Phase 1: Booking Detail Endpoints
    // ───────────────────────────────────────────

    /**
     * Get full booking detail (booking + payment + notes + email logs + sms logs)
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function get_booking_detail(WP_REST_Request $request) {
        $booking_id = (int) $request['id'];
        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return new WP_Error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), ['status' => 404]);
        }

        // Vendor ownership check
        $vendor_id = get_current_user_id();
        if (!current_user_can('manage_woocommerce') && (int) $booking->get('vendor_id') !== $vendor_id) {
            return new WP_Error('forbidden', __('You do not own this booking.', 'wahra-rentals-bookings'), ['status' => 403]);
        }

        $notes_handler = WAHRB_Booking_Notes::instance();
        $emailer = WAHRB_Emailer::instance();

        $data = $this->prepare_booking_response($booking->get_data(), true);
        $data['notes'] = $notes_handler->get_notes($booking_id);
        $data['email_logs'] = $emailer->get_email_logs($booking_id);
        $data['sms_logs'] = $emailer->get_sms_logs($booking_id);
        $data['notes_count'] = $notes_handler->count($booking_id);

        return new WP_REST_Response($data, 200);
    }

    /**
     * Get notes for a booking
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function get_booking_notes(WP_REST_Request $request) {
        $booking_id = (int) $request['id'];
        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return new WP_Error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), ['status' => 404]);
        }

        $vendor_id = get_current_user_id();
        if (!current_user_can('manage_woocommerce') && (int) $booking->get('vendor_id') !== $vendor_id) {
            return new WP_Error('forbidden', __('You do not own this booking.', 'wahra-rentals-bookings'), ['status' => 403]);
        }

        $notes_handler = WAHRB_Booking_Notes::instance();
        $notes = $notes_handler->get_notes($booking_id);

        return new WP_REST_Response($notes, 200);
    }

    /**
     * Add a note to a booking
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function add_booking_note(WP_REST_Request $request) {
        $booking_id = (int) $request['id'];
        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return new WP_Error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), ['status' => 404]);
        }

        $vendor_id = get_current_user_id();
        if (!current_user_can('manage_woocommerce') && (int) $booking->get('vendor_id') !== $vendor_id) {
            return new WP_Error('forbidden', __('You do not own this booking.', 'wahra-rentals-bookings'), ['status' => 403]);
        }

        $content = $request->get_param('content');
        $is_private = (bool) $request->get_param('is_private');

        $notes_handler = WAHRB_Booking_Notes::instance();
        $note_id = $notes_handler->add($booking_id, $content, 'manual', $is_private, $vendor_id);

        if (!$note_id) {
            return new WP_Error('insert_failed', __('Failed to add note.', 'wahra-rentals-bookings'), ['status' => 500]);
        }

        $note = $notes_handler->get_note($note_id);

        return new WP_REST_Response($note, 201);
    }

    /**
     * Delete a booking note
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function delete_booking_note(WP_REST_Request $request) {
        $booking_id = (int) $request['id'];
        $note_id = (int) $request['note_id'];
        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return new WP_Error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), ['status' => 404]);
        }

        $vendor_id = get_current_user_id();
        if (!current_user_can('manage_woocommerce') && (int) $booking->get('vendor_id') !== $vendor_id) {
            return new WP_Error('forbidden', __('You do not own this booking.', 'wahra-rentals-bookings'), ['status' => 403]);
        }

        $notes_handler = WAHRB_Booking_Notes::instance();
        $deleted = $notes_handler->delete($note_id, $booking_id);

        if (!$deleted) {
            return new WP_Error('delete_failed', __('Failed to delete note.', 'wahra-rentals-bookings'), ['status' => 500]);
        }

        return new WP_REST_Response(['deleted' => true], 200);
    }

    /**
     * Get email logs for a booking
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function get_booking_email_logs(WP_REST_Request $request) {
        $booking_id = (int) $request['id'];
        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return new WP_Error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), ['status' => 404]);
        }

        $vendor_id = get_current_user_id();
        if (!current_user_can('manage_woocommerce') && (int) $booking->get('vendor_id') !== $vendor_id) {
            return new WP_Error('forbidden', __('You do not own this booking.', 'wahra-rentals-bookings'), ['status' => 403]);
        }

        $emailer = WAHRB_Emailer::instance();

        return new WP_REST_Response($emailer->get_email_logs($booking_id), 200);
    }

    /**
     * Send an ad-hoc email for a booking
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function send_booking_email(WP_REST_Request $request) {
        $booking_id = (int) $request['id'];
        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return new WP_Error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), ['status' => 404]);
        }

        $vendor_id = get_current_user_id();
        if (!current_user_can('manage_woocommerce') && (int) $booking->get('vendor_id') !== $vendor_id) {
            return new WP_Error('forbidden', __('You do not own this booking.', 'wahra-rentals-bookings'), ['status' => 403]);
        }

        $recipient = sanitize_email($request->get_param('recipient'));
        $subject = $request->get_param('subject');
        $body = $request->get_param('body');

        if (!is_email($recipient)) {
            return new WP_Error('invalid_email', __('Invalid recipient email.', 'wahra-rentals-bookings'), ['status' => 400]);
        }

        $emailer = WAHRB_Emailer::instance();
        $sent = $emailer->send_adhoc($booking, $recipient, $subject, $body);

        return new WP_REST_Response([
            'sent' => $sent,
            'message' => $sent ? __('Email sent successfully.', 'wahra-rentals-bookings') : __('Failed to send email.', 'wahra-rentals-bookings'),
        ], $sent ? 200 : 500);
    }

    /**
     * Get SMS logs for a booking
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function get_booking_sms_logs(WP_REST_Request $request) {
        $booking_id = (int) $request['id'];
        $booking = new WAHRB_Booking($booking_id);

        if (!$booking->exists()) {
            return new WP_Error('not_found', __('Booking not found.', 'wahra-rentals-bookings'), ['status' => 404]);
        }

        $vendor_id = get_current_user_id();
        if (!current_user_can('manage_woocommerce') && (int) $booking->get('vendor_id') !== $vendor_id) {
            return new WP_Error('forbidden', __('You do not own this booking.', 'wahra-rentals-bookings'), ['status' => 403]);
        }

        $emailer = WAHRB_Emailer::instance();

        return new WP_REST_Response($emailer->get_sms_logs($booking_id), 200);
    }
}
