<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! class_exists( 'SBUY_Ajax' ) ) {
	/**
	 * Ajax Class.
	 *
	 * @since 1.0.0
	 */
	class SBUY_Ajax {

		/**
		 * Constructor
		 *
		 * @since 1.0.0
		 */
		public function __construct() {
			add_action( 'wp_ajax_sbuy_search', array( $this, 'get_search_callback' ) );
			add_action( 'wp_ajax_nopriv_sbuy_search', array( $this, 'get_search_callback' ) );

			add_action( 'wp_ajax_sbuy_selected', array( $this, 'get_selected' ) );
			add_action( 'wp_ajax_nopriv_sbuy_selected', array( $this, 'get_selected' ) );

			add_action( 'wp_ajax_sbuy_add_product', array( $this, 'add_product' ) );
			add_action( 'wp_ajax_nopriv_sbuy_add_product', array( $this, 'add_product' ) );

			add_action( 'wp_ajax_sbuy_submit_checkout_book', array( $this, 'submit_checkout_book' ) );
			add_action( 'wp_ajax_nopriv_sbuy_submit_checkout_book', array( $this, 'submit_checkout_book' ) );

			add_action( 'wp_ajax_sbuy_date_selected', array( $this, 'date_selected' ) );
			add_action( 'wp_ajax_nopriv_sbuy_date_selected', array( $this, 'date_selected' ) );

			add_action( 'wp_ajax_sbuy_booking_from_dashboard', array( $this, 'booking_from_dashboard_callback' ) );
			add_action( 'wp_ajax_sbuy_booking_get_token', array( $this, 'booking_get_token_callback' ) );

			add_action( 'wp_ajax_sbuy_bulk_block_day', array( $this, 'bulk_block_day' ) );
			add_action( 'wp_ajax_sbuy_bulk_unblock_all', array( $this, 'bulk_unblock_all' ) );

			add_action( 'wp_ajax_sbuy_bulk_price_preview', array( $this, 'bulk_price_preview' ) );
			add_action( 'wp_ajax_sbuy_bulk_price_apply', array( $this, 'bulk_price_apply' ) );

			add_action( 'wp_ajax_sbuy_review_send_test', array( $this, 'review_send_test' ) );

			add_action( 'wp_ajax_sbuy_test_calendar_connection', array( $this, 'test_calendar_connection' ) );
		}

		/**
		 * Get search result on the cart
		 *
		 * @since 1.0.0
		 */
		public function get_search_callback() {
			if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( [ 'message' => 'Invalid nonce' ], 400 );
			}

			$search_query = isset( $_GET['q'] ) ? sanitize_text_field( wp_unslash( $_GET['q'] ) ) : '';

			$results = array();

			$tag_args = array(
				'taxonomy'   => 'sbuy_product_tag',
				'orderby'    => 'name',
				'hide_empty' => true,
				'search'     => $search_query,
			);
			// Filter by current language when Polylang is active.
			$current_lang = sbuy_get_current_language( $_GET );
			if ( '' !== $current_lang ) {
				$tag_args['lang'] = $current_lang;
			}

			$simplybuy_tags = get_tags( $tag_args );

			// Polylang Free ignores the 'lang' arg in admin-ajax context (is_admin()),
			// so we post-filter the results by per-term language as a robust fallback.
			if ( is_array( $simplybuy_tags ) ) {
				$simplybuy_tags = sbuy_filter_terms_by_language( $simplybuy_tags, $current_lang );
			}

			if ( ! empty( $simplybuy_tags ) ) {
				foreach ( $simplybuy_tags as $tag ) {
					$tag_id    = $tag->term_id ?? 0;
					$name      = $tag->name ?? '';
					$results[] = array(
						$tag_id,
						$name,
						'type' => 'sbuy_product_tag',
					);
				}
			}

			if ( ! empty( $results ) ) {
				shuffle( $results );
				wp_send_json_success( $results );
			} else {
				wp_send_json_error( array(
					/* translators: %s is the user-entered search query */
					'message' => sprintf( esc_html__( 'No product found: %s', 'simply-buy' ), esc_html( $search_query ) )
				) );
			}
		}


		/**
		 * Get the selected product
		 *
		 * @since 1.0.0
		 */
		public function get_selected() {

			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( [ 'message' => 'Invalid nonce' ], 400 );
			}

			$selected_products = array();
			$type              = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';

			switch ( $type ) {
				case 'sbuy_product_tag':
					if ( isset( $_POST['selectedId'] ) ) {
						$tag_id      = sanitize_text_field( wp_unslash( $_POST['selectedId'] ) );
						$slug        = get_term( $tag_id )->name;
						$posts_args  = array(
							'post_type' => 'sbuy_product',
							'tax_query' => array(
								array(
									'taxonomy' => 'sbuy_product_tag',
									'field'    => 'id',
									'terms'    => $tag_id,
								),
							),
						);
						$lang = sbuy_get_current_language( $_POST );
						if ( '' !== $lang ) {
							$posts_args['lang'] = $lang;
						}
						$posts_array = get_posts( $posts_args );
						// Polylang Free ignores 'lang' in admin-ajax context — post-filter.
						if ( is_array( $posts_array ) ) {
							$posts_array = sbuy_filter_posts_by_language( $posts_array, $lang );
						}
						if ( ! empty( $posts_array ) ) {
							foreach ( $posts_array as $product ) {
								$product_id      = $product->ID;
								$product_title   = $product->post_title;
								$product_content = $product->post_content;
								$simplybuy       = json_decode( get_post_meta( $product_id, '_simplybuy', true ) );
								$product_price   = isset( $simplybuy->price ) ? esc_html( $simplybuy->price ) : '';

								$selected_products[] = array(
									'id'      => $product_id,
									'title'   => esc_html( $product_title ),
									'content' => wp_kses_post( $product_content ),
									'price'   => $product_price,
								);
							}
						}
					}
					break;
				case 'sbuy_product_category':
					if ( isset( $_POST['selectedId'] ) ) {
						$category_id = sanitize_text_field( wp_unslash( $_POST['selectedId'] ) );
						$slug        = get_the_category_by_ID( $category_id );
						$posts_args  = array(
							'post_type' => 'sbuy_product',
							'tax_query' => array(
								array(
									'taxonomy' => 'sbuy_product_category',
									'field'    => 'id',
									'terms'    => $category_id,
								),
							),
						);
						$lang = sbuy_get_current_language( $_POST );
						if ( '' !== $lang ) {
							$posts_args['lang'] = $lang;
						}
						$posts_array = get_posts( $posts_args );
						// Polylang Free ignores 'lang' in admin-ajax context — post-filter.
						if ( is_array( $posts_array ) ) {
							$posts_array = sbuy_filter_posts_by_language( $posts_array, $lang );
						}
						if ( ! empty( $posts_array ) ) {
							foreach ( $posts_array as $product ) {
								$product_id      = $product->ID;
								$product_title   = $product->post_title;
								$product_content = $product->post_content;
								$simplybuy       = json_decode( get_post_meta( $product_id, '_simplybuy', true ) );
								$product_price   = isset( $simplybuy->price ) ? esc_html( $simplybuy->price ) : '';

								$selected_products[] = array(
									'id'      => $product_id,
									'title'   => esc_html( $product_title ),
									'content' => wp_kses_post( $product_content ),
									'price'   => $product_price,
								);
							}
						}
					}
					break;
				case 'product':
					if ( isset( $_POST['selectedId'] ) ) {
						$product_id      = sanitize_text_field( wp_unslash( $_POST['selectedId'] ) );
						$product_obj     = get_post( $product_id );
						$slug            = $product_obj->post_name;
						$product_title   = $product_obj->post_title;
						$product_content = $product_obj->post_content;
						$simplybuy       = json_decode( get_post_meta( $product_id, '_simplybuy', true ) );
						$product_price   = isset( $simplybuy->price ) ? esc_html( $simplybuy->price ) : '';

						$selected_products[] = array(
							'id'      => $product_id,
							'title'   => esc_html( $product_title ),
							'content' => wp_kses_post( $product_content ),
							'price'   => $product_price,
						);
					}
					break;
			}

			wp_send_json_success( array(
				'selected' => $slug,
				'products' => $selected_products,
			) );
			sbuy_add_log( 'ajax_selected', 'Selected type: ' . $type . ' with slug: ' . $slug );

			wp_die();
		}

		/**
		 * Add to product in cart
		 *
		 * @since
		 */
		public function add_product() {

			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( [ 'message' => 'Invalid nonce' ], 400 );
			}

			if ( isset( $_POST['productid'] ) && isset( $_POST['productQuantity'] ) ) {

				$product_id    = sanitize_text_field( wp_unslash( $_POST['productid'] ) );
				$quantity      = sanitize_text_field( wp_unslash( $_POST['productQuantity'] ) );
				$product_obj   = get_post( $product_id );
				$title         = esc_html( $product_obj->post_title );
				$simplybuy     = json_decode( get_post_meta( $product_id, '_simplybuy', true ) );
				$product_price = isset( $simplybuy->price ) ? esc_html( $simplybuy->price ) : '';

				$result = array(
					'id'       => $product_id,
					'title'    => $title,
					'price'    => $product_price,
					'quantity' => $quantity,
				);
				sbuy_add_log( 'ajax_add_product', 'Added product ID ' . $product_id . ' quantity ' . $quantity );
				wp_send_json_success( $result );
			}

			wp_die();
		}

		/**
		 * Date selected
		 *
		 * @since 1.0.0
		 */
		public function date_selected() {

			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( [ 'message' => 'Invalid nonce' ], 400 );
			}

			$date = isset( $_POST['date'] ) ? sanitize_text_field( wp_unslash( $_POST['date'] ) ) : '';
			if ( ! empty( $date ) ) {

				$string_date   = sbuy_get_formatted_date( $date );
				$day           = gmdate( 'w', $string_date );
				$booking_order = sbuy_get_booking_order_for_frontend( $string_date );
				if ( $booking_order < 1 ) {
					sbuy_add_log( 'ajax_date_selected_error', 'No available time for date ' . $date );
					wp_send_json_error( array( 'message' => esc_html__( 'No available time', 'simply-buy' ) ) );
				}
				if ( 'morning' === $booking_order ) {
					$time    = sbuy_get_day_time_shift( $day, 'morning' ) ?? '';
					$message = wp_sprintf( '%s %s', esc_html__( 'Morning', 'simply-buy' ), esc_html( $time ) );
					sbuy_add_log( 'ajax_date_selected', 'Morning slot selected for date ' . $date );
					wp_send_json_success( array(
						'message' => $message,
						'time'    => 'morning',
					) );
				} elseif ( 'afternoon' === $booking_order ) {
					$time    = sbuy_get_day_time_shift( $day, 'afternoon' ) ?? '';
					$message = wp_sprintf( '%s %s', esc_html__( 'Afternoon', 'simply-buy' ), esc_html( $time ) );
					sbuy_add_log( 'ajax_date_selected', 'Afternoon slot selected for date ' . $date );
					wp_send_json_success( array(
						'message' => $message,
						'time'    => 'afternoon',
					) );
				}
			}

			wp_die();
		}

		/**
		 * Safely get and sanitize value from $_POST['information']
		 */
		private function get_info_field( $field, $type = 'text' ): string {
			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( [ 'message' => 'Invalid nonce' ], 400 );
			}

			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$info = isset( $_POST['information'] ) ? wp_unslash( $_POST['information'] ) : [];

			if ( empty( $info[ $field ] ) ) {
				return '';
			}

			$raw = $info[ $field ];

			switch ( $type ) {
				case 'textarea':
					return sanitize_textarea_field( $raw );
				default:
					return sanitize_text_field( $raw );
			}
		}

		/**
		 * Submit checkout
		 *
		 * @since 1.0.0
		 */
		public function submit_checkout_book() {

			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( [ 'message' => 'Invalid nonce' ], 400 );
			}

			$ip            = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) );
			$transient_key = 'sbuy_checkout_' . md5( $ip );
			if ( get_transient( $transient_key ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Too many requests. Please wait a moment.', 'simply-buy' ) ), 429 );
				return;
			}
			set_transient( $transient_key, 1, 60 ); // max 1 Buchung pro Minute pro IP

			$email = $this->get_info_field( 'email' );
			if ( ! is_email( $email ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Invalid email address.', 'simply-buy' ) ), 400 );
				return;
			}
			$company             = $this->get_info_field( 'company' );
			$first_name          = $this->get_info_field( 'firstName' );
			$last_name           = $this->get_info_field( 'lastName' );
			$street              = $this->get_info_field( 'streetNo' );
			$postal_code         = $this->get_info_field( 'postalCode' );
			$city                = $this->get_info_field( 'city' );
			$phone               = $this->get_info_field( 'phone' );
			$walkway             = $this->get_info_field( 'walkway' );
			$walkway_text        = $this->get_info_field( 'walkwayText' );
			$elevator            = $this->get_info_field( 'elevator' );
			$elevator_text       = $this->get_info_field( 'elevatorText' );
			$floor               = $this->get_info_field( 'floor' );
			$floor_text          = $this->get_info_field( 'floorText' );
			$note                = $this->get_info_field( 'notes', 'textarea' );
			$planting_tree       = isset( $_POST['information']['plantingTree'] ) && ( 'true' === $_POST['information']['plantingTree'] );
			$doorbell            = $this->get_info_field( 'doorbell' );
			$date                = $this->get_info_field( 'date' );
			$time                = $this->get_info_field( 'time' );
			$shift               = $this->get_info_field( 'shift' );

			// A valid shift is required — UI may send placeholder values when no slot has been picked.
			if ( empty( $shift ) || in_array( $shift, array( 'select-date-first', 'no-available-time' ), true ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Please select a time slot.', 'simply-buy' ) ), 400 );
				return;
			}
			$billing_address     = isset( $_POST['information']['billingAddress'] ) && ( 'true' === $_POST['information']['billingAddress'] );
			$billing_company     = $this->get_info_field( 'billingCompany' );
			$billing_first_name  = $this->get_info_field( 'billingFirstName' );
			$billing_last_name   = $this->get_info_field( 'billingLastName' );
			$billing_street      = $this->get_info_field( 'billingStreet' );
			$billing_postal_code = $this->get_info_field( 'billingPostalCode' );
			$billing_city        = $this->get_info_field( 'billingCity' );

			$name = $first_name . ' ' . $last_name;

			$message = '';

			$message_array = array(
				esc_html__( 'Company', 'simply-buy' )                                      => $company,
				esc_html__( 'First Name', 'simply-buy' )                                   => $first_name,
				esc_html__( 'Last Name', 'simply-buy' )                                    => $last_name,
				esc_html__( 'Street', 'simply-buy' )                                       => $street,
				esc_html__( 'Postal code', 'simply-buy' )                                  => $postal_code,
				esc_html__( 'City', 'simply-buy' )                                         => $city,
				esc_html__( 'Phone', 'simply-buy' )                                        => $phone,
				esc_html__( 'Email', 'simply-buy' )                                        => $email,
				esc_html__( 'Elevator', 'simply-buy' )                                     => $elevator_text,
				esc_html__( 'Floor', 'simply-buy' )                                        => $floor_text !== '' ? $floor_text : $floor,
				esc_html__( 'Walkway between house entrance & parking lot', 'simply-buy' ) => $walkway_text !== '' ? $walkway_text : $walkway,
				esc_html__( 'Name on the doorbell', 'simply-buy' )                         => $doorbell,
				esc_html__( 'Notes', 'simply-buy' )                                        => $note,
				esc_html__( 'Date', 'simply-buy' )                                         => $date,
				esc_html__( 'Time', 'simply-buy' )                                         => $time,
			);

			$custom_checkout = sbuy_get_options( 'enable_custom_checkbox', 'sbuy_settings_checkout' );

			if ( $custom_checkout ) {
				$custom_checkout_label = sbuy_get_options( 'custom_checkbox_label_text', 'sbuy_settings_checkout' );

				if ( $planting_tree ) {
					$planting_text = array(
						$custom_checkout_label => esc_html__( 'Yes', 'simply-buy' ),
					);
				} else {
					$planting_text = array(
						$custom_checkout_label => esc_html__( 'No', 'simply-buy' ),
					);
				}
				$message_array += $planting_text;
			}

			foreach ( $message_array as $key => $value ) {
				if ( $value && strlen( trim( $value ) > 0 ) ) {
					$message .= wp_sprintf( '%1$s: %2$s', $key, $value ) . '<br/>';
				}
			}

			if ( $billing_address ) {

				$message .= '<br/>' . esc_html__( 'Additional information', 'simply-buy' ) . '<br/>';

				$billing_array = array(
					esc_html__( 'Billing Company', 'simply-buy' )     => $billing_company,
					esc_html__( 'Billing First Name', 'simply-buy' )  => $billing_first_name,
					esc_html__( 'Billing Last Name', 'simply-buy' )   => $billing_last_name,
					esc_html__( 'Billing Street', 'simply-buy' )      => $billing_street,
					esc_html__( 'Billing Postal code', 'simply-buy' ) => $billing_postal_code,
					esc_html__( 'Billing City', 'simply-buy' )        => $billing_city,
				);

				foreach ( $billing_array as $key => $value ) {
					if ( $value && strlen( trim( $value ) > 0 ) ) {
						$message .= wp_sprintf( '%1$s: %2$s', $key, $value ) . '<br/>';
					}
				}
			}

			// === Server-side total calculation ==============================
			// Recompute the cart total on the server from real product-meta
			// prices + finance settings, instead of trusting the client.
			// Security: a hostile client could otherwise post `totalPrice=0,01`
			// and still get a booking. See sbuy_calculate_checkout_totals().
			$posted_products    = isset( $_POST['products']['productList'] ) ? rest_sanitize_array( wp_unslash( $_POST['products']['productList'] ) ) : array();
			$walkway_int        = is_numeric( $walkway ) ? (int) $walkway : -1;
			$floor_int          = is_numeric( $floor ) ? (int) $floor : 0;
			$elevator_is_no     = ( 'no' === $elevator );
			$totals             = sbuy_calculate_checkout_totals( $posted_products, $walkway_int, $floor_int, $elevator_is_no );

			$total_price          = number_format( $totals['total'], 2, ',', '' ) . SBUY_CORE_CURRENCY_SYMBOL;
			$min_order_surcharge  = $totals['min_order_surcharge'] > 0 ? number_format( $totals['min_order_surcharge'], 2, ',', '' ) : '';
			$difficulty_surcharge = $totals['difficulty_surcharge'] > 0 ? number_format( $totals['difficulty_surcharge'], 2, ',', '' ) : '';

			if ( ! empty( $posted_products ) ) {
				$message .= '<br/>' . esc_html__( 'Products:', 'simply-buy' ) . '<br/>';

				foreach ( $totals['line_items'] as $item ) {
					$line_total = $item['price'] * $item['quantity'];
					$message   .= esc_html( $item['name'] ) . ' x ' . intval( $item['quantity'] )
						. ' (' . number_format( $item['price'], 2, ',', '' ) . SBUY_CORE_CURRENCY_SYMBOL
						. ' x ' . intval( $item['quantity'] )
						. ' = ' . number_format( $line_total, 2, ',', '' ) . SBUY_CORE_CURRENCY_SYMBOL . ')<br/>';
				}

				if ( $totals['min_order_surcharge'] > 0 ) {
					$message .= esc_html__( 'Surcharge to the minimum pick-up price: ', 'simply-buy' )
						. number_format( $totals['min_order_surcharge'], 2, ',', '' ) . SBUY_CORE_CURRENCY_SYMBOL . '<br/>';
				}

				if ( $totals['difficulty_surcharge'] > 0 ) {
					$message .= esc_html__( 'Difficulty surcharge: ', 'simply-buy' )
						. number_format( $totals['difficulty_surcharge'], 2, ',', '' ) . SBUY_CORE_CURRENCY_SYMBOL . '<br/>';
				}

				if ( $totals['total'] > 0 ) {
					$message .= esc_html__( 'Total price: ', 'simply-buy' ) . $total_price;
				}
			}

			$message .= '<br/>' . esc_html__( 'Payment method: Cash payment', 'simply-buy' );

			$formatted_date = sbuy_get_formatted_date( $date );
			$user_agent     = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ) );
			$device         = sbuy_detect_device( $user_agent );
			$address        = trim( $street . ', ' . $postal_code . ' ' . $city, ', ' );

			sbuy_add_log( 'ajax_checkout', 'Checkout submitted by ' . $name . ' with email ' . $email . ' | Device: ' . $device . ' | Browser: ' . $user_agent );
			sbuy_add_log( 'price_calculation', 'Total: ' . $total_price . ' | Difficulty surcharge (elevator/walkway): ' . $difficulty_surcharge . ' | Min order: ' . $min_order_surcharge );
			do_action( 'sbuy_booking_insert', $formatted_date, $shift );
			do_action( 'sbuy_add_google_calendar_event', $formatted_date, $message, $shift, $total_price, $address, $name );
			do_action( 'sbuy_booking_email', $message, $name, $email );
			do_action( 'sbuy_booking_confirmed', array(
				'name'    => $name,
				'email'   => $email,
				'date'    => $date,
				'address' => $address,
			) );

			wp_die();
		}

		public function booking_get_token_callback() {
			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( [ 'message' => 'Invalid nonce' ], 400 );
			}

			if ( ! is_user_logged_in() && ! current_user_can( 'manage_options' ) ) {
				wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
			}

			$notes     = isset( $_POST['notes'] ) ? sanitize_textarea_field( wp_unslash( $_POST['notes'] ) ) : '';

			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$price_raw = isset( $_POST['price'] ) ? wp_unslash( $_POST['price'] ) : '';

			if ( $price_raw !== '' && ! is_numeric( $price_raw ) ) {
				wp_send_json_error( [ 'message' => 'Invalid price' ], 400 );
			}

			$price = $price_raw !== '' ? floatval( $price_raw ) : 0;

			$checkout_page_id = sbuy_get_localized_page_id( 'simply_buy_checkout_page' );

			if ( empty( $checkout_page_id ) ) {
				sbuy_add_log( 'ajax_booking_token_error', 'Checkout page missing' );
				wp_send_json_error( [ 'message' => esc_html__( 'Checkout page not configured', 'simply-buy' ) ] );
			}

			$checkout_page_url = get_permalink( $checkout_page_id );

			$params = [
				'notes' => $notes,
				'price' => $price,
			];

			$token = sbuy_token_seal( $params );

			sbuy_add_log( 'ajax_booking_get_token', 'Token generated successfully' );

			$url = esc_url_raw( add_query_arg( 'token', sanitize_text_field( $token ), $checkout_page_url ) );

			wp_send_json_success( [ 'url' => $url ] );
		}

		public function booking_from_dashboard_callback() {

			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( [ 'message' => 'Invalid nonce' ], 400 );
			}

			if ( ! is_user_logged_in() && ! current_user_can( 'manage_options' ) ) {
				wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
			}

			global $wpdb;

			$table_name = $wpdb->prefix . 'sbuy_booking';

			$date       = isset( $_POST['date'] ) ? sanitize_text_field( wp_unslash( $_POST['date'] ) ) : '';
			$slot       = isset( $_POST['slot'] ) ? sanitize_text_field( wp_unslash( $_POST['slot'] ) ) : '';
			$index      = isset( $_POST['index'] ) ? sanitize_text_field( wp_unslash( $_POST['index'] ) ) : '';
			$is_checked = isset( $_POST['isChecked'] ) ? sanitize_text_field( wp_unslash( $_POST['isChecked'] ) ) : '';

			$formated_date = strtotime( $date );

			if ( 'true' === $is_checked ) {
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
				$booking_insert = $wpdb->insert( $table_name, array(
					'date' => $formated_date,
					'slot' => $slot,
				) );
				if ( ! is_wp_error( $booking_insert ) ) {
					wp_send_json_success( array( 'message' => esc_html__( 'Successfully insert booking', 'simply-buy' ) ) );
					sbuy_add_log( 'ajax_booking_dashboard', 'Inserted booking from dashboard for date ' . $date . ' slot ' . $slot );
				}
			} elseif ( 'false' === $is_checked ) {
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$booking_delete = $wpdb->delete( $table_name, array(
					'id' => $index,
				) );
				if ( ! is_wp_error( $booking_delete ) ) {
					wp_send_json_success( array( 'message' => esc_html__( 'Successfully delete booking', 'simply-buy' ) ) );
					sbuy_add_log( 'ajax_booking_dashboard', 'Deleted booking from dashboard ID ' . $index );
				}
			}

			wp_send_json_error( array( 'message' => esc_html__( 'Booked is not success', 'simply-buy' ) ) );
		}

		/**
		 * Bulk-block every occurrence of the given weekday for the current year + 3 years.
		 * Tracks blocked dates in the `sbuy_bulk_blocked_dates` option so they can be cleanly removed.
		 */
		public function bulk_block_day(): void {
			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 );
			}
			if ( ! current_user_can( 'manage_options' ) ) {
				wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 );
			}
			if ( ! sbuy_has_pro_access() ) {
				wp_send_json_error( array( 'message' => 'Pro access required' ), 403 );
			}

			$day        = isset( $_POST['day'] ) ? (int) $_POST['day'] : -1;
			$start_year = isset( $_POST['year'] ) ? (int) $_POST['year'] : (int) gmdate( 'Y' );

			if ( $day < 0 || $day > 6 ) {
				wp_send_json_error( array( 'message' => 'Invalid day' ), 400 );
			}

			global $wpdb;
			$table_name     = $wpdb->prefix . 'sbuy_booking';
			$inserted_dates = 0;
			$today_ts       = strtotime( wp_date( 'Y-m-d' ) );

			for ( $year = $start_year; $year <= $start_year + 3; $year++ ) {
				for ( $month = 1; $month <= 12; $month++ ) {
					$days_in_month = cal_days_in_month( CAL_GREGORIAN, $month, $year );
					for ( $d = 1; $d <= $days_in_month; $d++ ) {
						$ts = strtotime( sprintf( '%04d-%02d-%02d', $year, $month, $d ) );
						if ( $ts < $today_ts ) {
							continue;
						}
						if ( (int) gmdate( 'w', $ts ) !== $day ) {
							continue;
						}
						if ( sbuy_has_available_slot( $ts ) <= 0 ) {
							continue; // already full
						}

						// Fill up morning (indices 0-2) and afternoon (indices 3-5) — 6 slots total.
						$existing = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}sbuy_booking WHERE date = %s", (string) $ts ) );
						$missing  = max( 0, 6 - $existing );
						for ( $i = 0; $i < $missing; $i++ ) {
							$slot = ( $existing + $i ) < 3 ? 'morning' : 'afternoon';
							$wpdb->insert( $table_name, array( 'date' => (string) $ts, 'slot' => $slot, 'is_bulk' => 1 ) );
						}

						$inserted_dates++;
					}
				}
			}

			sbuy_add_log( 'bulk_block_day', 'Bulk-blocked weekday ' . $day . ' (' . $inserted_dates . ' dates)' );

			wp_send_json_success( array(
				'message' => sprintf(
					/* translators: %d: number of dates blocked */
					esc_html__( '%d dates blocked successfully.', 'simply-buy' ),
					$inserted_dates
				),
			) );
		}

		/**
		 * Remove all bulk-blocked bookings (rows tracked via the `sbuy_bulk_blocked_dates` option).
		 * Customer bookings are NOT touched — only dates we explicitly recorded as bulk-blocks.
		 */
		public function bulk_unblock_all(): void {
			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 );
			}
			if ( ! current_user_can( 'manage_options' ) ) {
				wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 );
			}
			if ( ! sbuy_has_pro_access() ) {
				wp_send_json_error( array( 'message' => 'Pro access required' ), 403 );
			}

			global $wpdb;
			$removed = (int) $wpdb->query( "DELETE FROM {$wpdb->prefix}sbuy_booking WHERE is_bulk = 1" );

			sbuy_add_log( 'bulk_unblock_all', 'Removed ' . $removed . ' bulk-blocked bookings' );

			wp_send_json_success( array(
				'message' => sprintf(
					/* translators: %d: number of bookings removed */
					esc_html__( '%d blocks removed.', 'simply-buy' ),
					$removed
				),
			) );
		}

		/**
		 * Preview the bulk price adjustment on 3 random products.
		 */
		public function bulk_price_preview(): void {
			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 );
			}
			if ( ! current_user_can( 'manage_options' ) ) {
				wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 );
			}
			if ( ! sbuy_has_pro_access() ) {
				wp_send_json_error( array( 'message' => 'Pro access required' ), 403 );
			}

			$args = $this->parse_bulk_price_args();
			if ( is_wp_error( $args ) ) {
				wp_send_json_error( array( 'message' => $args->get_error_message() ), 400 );
			}

			$all_ids = get_posts( array(
				'post_type'      => 'sbuy_product',
				'post_status'    => 'publish',
				'posts_per_page' => -1,
				'fields'         => 'ids',
			) );
			$total   = count( $all_ids );
			$samples = array();

			if ( $total > 0 ) {
				$pick = array_rand( $all_ids, min( 3, $total ) );
				foreach ( (array) $pick as $idx ) {
					$post_id = $all_ids[ $idx ];
					$meta    = json_decode( (string) get_post_meta( $post_id, '_simplybuy', true ), true );
					$old     = isset( $meta['price'] ) ? (float) str_replace( ',', '.', (string) $meta['price'] ) : 0.0;
					$new     = sbuy_adjust_price( $old, $args['direction'], $args['value'], $args['mode'] );
					$samples[] = array(
						'title' => get_the_title( $post_id ),
						'old'   => number_format( $old, 2, ',', '' ),
						'new'   => number_format( $new, 2, ',', '' ),
					);
				}
			}

			wp_send_json_success( array(
				'total'     => $total,
				'direction' => $args['direction'],
				'mode'      => $args['mode'],
				'value'     => $args['value'],
				'samples'   => $samples,
			) );
		}

		/**
		 * Validate + normalize the POST args used by both bulk_price_preview and bulk_price_apply.
		 * Returns array{direction:string,mode:string,value:float} or WP_Error on bad input.
		 */
		private function parse_bulk_price_args() {
			$direction = isset( $_POST['direction'] ) && 'decrease' === $_POST['direction'] ? 'decrease' : 'increase';
			$mode      = isset( $_POST['mode'] ) && 'fixed' === $_POST['mode'] ? 'fixed' : 'percent';

			if ( 'fixed' === $mode ) {
				$value = isset( $_POST['amount'] ) ? (float) str_replace( ',', '.', (string) $_POST['amount'] ) : 0.0;
				if ( $value <= 0 ) {
					return new WP_Error( 'sbuy_bad_amount', esc_html__( 'Please enter a positive amount.', 'simply-buy' ) );
				}
			} else {
				$value = isset( $_POST['percent'] ) ? (float) str_replace( ',', '.', (string) $_POST['percent'] ) : 0.0;
				if ( $value <= 0 || $value > 100 ) {
					return new WP_Error( 'sbuy_bad_percent', esc_html__( 'Please enter a percentage between 0.1 and 100.', 'simply-buy' ) );
				}
			}

			return array( 'direction' => $direction, 'mode' => $mode, 'value' => $value );
		}

		/**
		 * Apply the bulk price adjustment to every sbuy_product.
		 */
		public function bulk_price_apply(): void {
			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 );
			}
			if ( ! current_user_can( 'manage_options' ) ) {
				wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 );
			}
			if ( ! sbuy_has_pro_access() ) {
				wp_send_json_error( array( 'message' => 'Pro access required' ), 403 );
			}

			$args = $this->parse_bulk_price_args();
			if ( is_wp_error( $args ) ) {
				wp_send_json_error( array( 'message' => $args->get_error_message() ), 400 );
			}

			$all_ids = get_posts( array(
				'post_type'      => 'sbuy_product',
				'post_status'    => 'publish',
				'posts_per_page' => -1,
				'fields'         => 'ids',
			) );

			$updated = 0;
			foreach ( $all_ids as $post_id ) {
				$meta_raw = get_post_meta( $post_id, '_simplybuy', true );
				$meta     = json_decode( (string) $meta_raw, true );
				if ( ! is_array( $meta ) || ! isset( $meta['price'] ) ) {
					continue;
				}
				$old = (float) str_replace( ',', '.', (string) $meta['price'] );
				$new = sbuy_adjust_price( $old, $args['direction'], $args['value'], $args['mode'] );

				$meta['price'] = number_format( $new, 2, ',', '' );
				update_post_meta( $post_id, '_simplybuy', wp_json_encode( $meta ) );
				$updated++;
			}

			$unit = ( 'fixed' === $args['mode'] ) ? '€' : '%';
			sbuy_add_log( 'bulk_price', sprintf( 'Bulk price adjustment: %s %s%s (%s) applied to %d products', $args['direction'], $args['value'], $unit, $args['mode'], $updated ) );

			wp_send_json_success( array(
				'updated' => $updated,
				'message' => sprintf(
					/* translators: %d: number of products updated */
					esc_html__( '%d products updated.', 'simply-buy' ),
					$updated
				),
			) );
		}

		/**
		 * Send a review-request test email to the admin address using current settings.
		 */
		public function review_send_test(): void {
			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 );
			}
			if ( ! current_user_can( 'manage_options' ) ) {
				wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 );
			}
			if ( ! sbuy_has_pro_access() ) {
				wp_send_json_error( array( 'message' => 'Pro access required' ), 403 );
			}

			$admin_email = get_option( 'admin_email' );
			$user        = wp_get_current_user();
			$name        = $user && $user->display_name ? $user->display_name : esc_html__( 'there', 'simply-buy' );

			$sent = sbuy_send_review_email_now( array(
				'name'  => $name,
				'email' => $admin_email,
			) );

			if ( $sent ) {
				wp_send_json_success( array(
					'message' => sprintf(
						/* translators: %s: email address */
						esc_html__( 'Test email sent to %s', 'simply-buy' ),
						$admin_email
					),
				) );
			} else {
				wp_send_json_error( array( 'message' => esc_html__( 'Mail send failed', 'simply-buy' ) ) );
			}
		}

		/**
		 * Test the Google Calendar connection.
		 *
		 * Uses the saved service-account JSON + calendar ID to do a read-only
		 * `calendars.get` against the Google API. Returns specific, actionable
		 * error messages for each failure mode (missing settings, bad JSON,
		 * 401 Unauthorized, 404 calendar-not-found, network errors, etc).
		 *
		 * Never creates an event — it's a diagnostic, not a side-effect.
		 *
		 * @since 1.2.4
		 */
		public function test_calendar_connection(): void {
			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'simplybuy-nonce' ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Invalid nonce', 'simply-buy' ) ), 400 );
			}
			if ( ! current_user_can( 'manage_options' ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'simply-buy' ) ), 403 );
			}

			$json_raw    = (string) sbuy_get_options( 'google_service_account_json', 'sbuy_settings_google' );
			$calendar_id = trim( (string) sbuy_get_options( 'calendar_id', 'sbuy_settings_google' ) );

			if ( '' === trim( $json_raw ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Error: Service account JSON is empty.', 'simply-buy' ) ), 400 );
			}
			if ( '' === $calendar_id ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Error: Calendar ID is empty.', 'simply-buy' ) ), 400 );
			}

			$json = json_decode( $json_raw, true );
			if ( ! is_array( $json ) || empty( $json['type'] ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Error: Invalid JSON file.', 'simply-buy' ) ), 400 );
			}

			if ( ! class_exists( 'Google\\Client' ) ) {
				$autoload = SBUY_CORE_PLUGIN_PATH . 'google-api-php-client/vendor/autoload.php';
				if ( is_readable( $autoload ) ) {
					require_once $autoload;
				}
			}
			if ( ! class_exists( 'Google\\Client' ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Error: Google API client not available.', 'simply-buy' ) ), 500 );
			}

			try {
				$client = new Google\Client();
				$client->setAuthConfig( $json );
				$client->addScope( Google_Service_Calendar::CALENDAR_READONLY );
				$service = new Google_Service_Calendar( $client );

				// Read-only ping — just fetch the calendar metadata.
				$calendar = $service->calendars->get( $calendar_id );

				wp_send_json_success( array(
					'message' => esc_html__( 'Connection successful — Google Calendar is reachable.', 'simply-buy' ),
					'summary' => isset( $calendar->summary ) ? (string) $calendar->summary : '',
				) );
			} catch ( \Google\Service\Exception $e ) {
				$code = $e->getCode();
				if ( 401 === (int) $code ) {
					$msg = esc_html__( 'Error: No permission (401) — check the service account JSON and verify the calendar is shared with the service-account email.', 'simply-buy' );
				} elseif ( 403 === (int) $code ) {
					$msg = esc_html__( 'Error: Access forbidden (403) — the service account is not authorized for this calendar.', 'simply-buy' );
				} elseif ( 404 === (int) $code ) {
					$msg = esc_html__( 'Error: Calendar ID not found (404).', 'simply-buy' );
				} else {
					$msg = sprintf(
						/* translators: 1: HTTP status code, 2: API error message */
						esc_html__( 'Error: Google API (%1$s): %2$s', 'simply-buy' ),
						$code,
						$e->getMessage()
					);
				}
				wp_send_json_error( array( 'message' => $msg ) );
			} catch ( \Throwable $e ) {
				wp_send_json_error( array(
					'message' => sprintf(
						/* translators: %s: exception message */
						esc_html__( 'Error: %s', 'simply-buy' ),
						$e->getMessage()
					),
				) );
			}
		}
	}
}
