<?php
/**
 * REST API Calendar controller (v2).
 *
 * Provides an optimized endpoint for fetching appointment data for calendar display.
 * Uses direct SQL queries with JOINs to avoid N+1 queries and heavy object hydration.
 *
 * Endpoints:
 * - `GET /wc-appointments/v2/calendar` - Get appointments optimized for calendar display
 *
 * @package WooCommerce\Appointments\Rest\Controller
 * @since 5.0.0
 */

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * REST API Calendar controller class.
 *
 * @since 5.0.0
 */
class WC_Appointments_REST_V2_Calendar_Controller extends WP_REST_Controller {

	use WC_Appointments_Rest_Permission_Check;

	/**
	 * Endpoint namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'wc-appointments/v2';

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

	/**
	 * Register routes.
	 */
	public function register_routes(): void {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			[
				[
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => [ $this, 'get_items' ],
					'permission_callback' => [ $this, 'get_items_permissions_check' ],
					'args'                => $this->get_collection_params(),
				],
				'schema' => [ $this, 'get_public_item_schema' ],
			]
		);
	}

	/**
	 * Check if a given request has access to read calendar items.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error
	 */
	public function get_items_permissions_check( $request ) {
		if ( ! current_user_can( 'manage_woocommerce' ) && ! current_user_can( 'edit_others_appointments' ) ) {
			return new WP_Error(
				'woocommerce_rest_cannot_view',
				__( 'Sorry, you cannot list calendar appointments.', 'woocommerce-appointments' ),
				[ 'status' => rest_authorization_required_code() ]
			);
		}

		return true;
	}

	/**
	 * Get calendar appointments.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_items( $request ) {
		$date_from = $request->get_param( 'date_from' );
		$date_to   = $request->get_param( 'date_to' );

		// Parse timestamps (support '@' prefix for Unix timestamps)
		$start_ts = $this->parse_timestamp( $date_from );
		$end_ts   = $this->parse_timestamp( $date_to );

		if ( ! $start_ts || ! $end_ts ) {
			return new WP_Error(
				'woocommerce_rest_invalid_date_range',
				__( 'date_from and date_to are required and must be valid timestamps.', 'woocommerce-appointments' ),
				[ 'status' => 400 ]
			);
		}

		if ( $end_ts <= $start_ts ) {
			return new WP_Error(
				'woocommerce_rest_invalid_date_range',
				__( 'date_to must be greater than date_from.', 'woocommerce-appointments' ),
				[ 'status' => 400 ]
			);
		}

		// Parse include fields
		$include        = $request->get_param( 'include' );
		$include_fields = $include ? array_map( 'trim', explode( ',', $include ) ) : [];

		// Parse status filter
		$status        = $request->get_param( 'status' );
		$status_filter = $status ? array_map( 'trim', explode( ',', $status ) ) : [];

		// Normalize status values (add wc- prefix if needed for DB query)
		$status_filter = array_map( function ( $s ) {
			// Status values in DB have wc- prefix for custom statuses
			if ( ! str_starts_with( $s, 'wc-' ) && ! in_array( $s, [ 'publish', 'pending', 'draft', 'trash' ], true ) ) {
				return 'wc-' . $s;
			}
			return $s;
		}, $status_filter );

		// Staff filter - enforce current user's staff ID if they cannot manage others' appointments.
		// This mirrors the filtering applied in the Appointments List view.
		$staff_id = absint( $request->get_param( 'staff_id' ) );
		if ( ! current_user_can( 'manage_others_appointments' ) ) {
			$staff_id = get_current_user_id();
		}

		// Get appointments using optimized data store method
		$appointments = WC_Appointment_Data_Store::get_calendar_appointments( [
			'start_ts'   => $start_ts,
			'end_ts'     => $end_ts,
			'product_id' => absint( $request->get_param( 'product_id' ) ),
			'staff_id'   => $staff_id,
			'status'     => $status_filter,
			'include'    => $include_fields,
		] );

		// Check if order details should be included (for modal display)
		$include_order_details = $request->get_param( 'include_order_details' );
		if ( $include_order_details ) {
			// Collect unique order IDs
			$order_ids = array_unique( array_filter( array_column( $appointments, 'order_id' ) ) );

			if ( ! empty( $order_ids ) ) {
				// Fetch order details in batch
				$orders_data = WC_Appointment_Data_Store::get_calendar_order_details( $order_ids );

				// Merge order details into appointments
				foreach ( $appointments as &$appt ) {
					$order_id = $appt['order_id'] ?? 0;
					if ( $order_id && isset( $orders_data[ $order_id ] ) ) {
						$appt['order_info'] = $orders_data[ $order_id ];
						if ( empty( $appt['customer_email'] ) && ! empty( $appt['order_info']['billing']['email'] ) ) {
							$appt['customer_email'] = $appt['order_info']['billing']['email'];
						}
						if ( empty( $appt['customer_phone'] ) && ! empty( $appt['order_info']['billing']['phone'] ) ) {
							$appt['customer_phone'] = $appt['order_info']['billing']['phone'];
						}
						
						// Update guest customer names from order billing info
						if ( ( empty( $appt['customer_id'] ) || $appt['customer_name'] === __( 'Guest', 'woocommerce-appointments' ) ) && isset( $appt['order_info']['billing'] ) ) {
							$billing = $appt['order_info']['billing'];
							$billing_first = trim( $billing['first_name'] ?? '' );
							$billing_last = trim( $billing['last_name'] ?? '' );
							$guest_name = trim( $billing_first . ' ' . $billing_last );
							
							if ( $guest_name ) {
								// Add (Guest) suffix for guest customers with names
								/* translators: %s: Guest name */
								$guest_name_with_suffix = sprintf( _x( '%s (Guest)', 'Guest string with name from appointment order in brackets', 'woocommerce-appointments' ), $guest_name );
								$appt['customer_name'] = $guest_name_with_suffix;
								$appt['customer_first_name'] = $billing_first;
								$appt['customer_last_name'] = $billing_last;
								$appt['customer_full_name'] = $guest_name_with_suffix;
							}
						}
					} else {
						// Ensure key exists (as null) so frontend knows we attempted to load it
						// and doesn't trigger a fallback API call
						$appt['order_info'] = null;
					}
				}
				unset( $appt );
			}
		}

		// Apply _fields parameter to filter response fields
		$fields = $request->get_param( '_fields' );
		if ( $fields ) {
			$allowed_fields = array_map( 'trim', explode( ',', $fields ) );
			$appointments   = array_map( function ( $appt ) use ( $allowed_fields ) {
				return array_intersect_key( $appt, array_flip( $allowed_fields ) );
			}, $appointments );
		}

		$response = rest_ensure_response( array_values( $appointments ) );

		// Add headers for pagination info
		$response->header( 'X-WP-Total', count( $appointments ) );

		return $response;
	}

	/**
	 * Parse a timestamp value from request parameter.
	 *
	 * Supports:
	 * - Unix timestamp (numeric)
	 * - '@' prefixed Unix timestamp (e.g., '@1705000000')
	 * - strtotime-compatible date strings
	 *
	 * @param mixed $value The value to parse.
	 * @return int Unix timestamp or 0 on failure.
	 */
	private function parse_timestamp( $value ): int {
		if ( empty( $value ) ) {
			return 0;
		}

		// Remove @ prefix if present
		$value = ltrim( (string) $value, '@' );

		// If numeric, treat as Unix timestamp
		if ( is_numeric( $value ) ) {
			return absint( $value );
		}

		// Try strtotime
		$ts = strtotime( $value );
		return false !== $ts ? $ts : 0;
	}

	/**
	 * Get the query params for collections.
	 *
	 * @return array
	 */
	public function get_collection_params(): array {
		return [
			'date_from'  => [
				'description'       => 'Start of date range. Accepts Unix timestamp, @timestamp, or strtotime-compatible string.',
				'type'              => 'string',
				'required'          => true,
				'validate_callback' => 'rest_validate_request_arg',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'date_to'    => [
				'description'       => 'End of date range. Accepts Unix timestamp, @timestamp, or strtotime-compatible string.',
				'type'              => 'string',
				'required'          => true,
				'validate_callback' => 'rest_validate_request_arg',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'product_id' => [
				'description'       => 'Filter by product ID.',
				'type'              => 'integer',
				'default'           => 0,
				'sanitize_callback' => 'absint',
			],
			'staff_id'   => [
				'description'       => 'Filter by staff ID.',
				'type'              => 'integer',
				'default'           => 0,
				'sanitize_callback' => 'absint',
			],
			'status'     => [
				'description'       => 'Comma-separated list of statuses to filter by.',
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'include'    => [
				'description'       => 'Comma-separated list of fields to include in the response.',
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'_fields'    => [
				'description'       => 'Limit response to specific fields (standard REST API parameter).',
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'include_order_details' => [
				'description'       => 'Include order details (currency, total, billing) for each appointment. Useful for modal display.',
				'type'              => 'boolean',
				'default'           => true,
				'sanitize_callback' => 'rest_sanitize_boolean',
			],
		];
	}

	/**
	 * Get the schema for a single calendar appointment item.
	 *
	 * @return array
	 */
	public function get_item_schema(): array {
		$schema = [
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'calendar-appointment',
			'type'       => 'object',
			'properties' => [
				'id'                  => [
					'description' => 'Unique identifier for the appointment.',
					'type'        => 'integer',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'start'               => [
					'description' => 'Appointment start time as Unix timestamp.',
					'type'        => 'integer',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'end'                 => [
					'description' => 'Appointment end time as Unix timestamp.',
					'type'        => 'integer',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'status'              => [
					'description' => 'Appointment status.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'customer_status'     => [
					'description' => 'Customer attendance status.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'product_id'          => [
					'description' => 'Associated product ID.',
					'type'        => 'integer',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'product_title'       => [
					'description' => 'Associated product title.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'staff_id'            => [
					'description' => 'Primary staff member ID.',
					'type'        => 'integer',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'staff_ids'           => [
					'description' => 'Array of assigned staff member IDs.',
					'type'        => 'array',
					'items'       => [ 'type' => 'integer' ],
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'staff_name'          => [
					'description' => 'Staff member display name.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'staff_avatar'        => [
					'description' => 'Staff member avatar URL.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'customer_id'         => [
					'description' => 'Customer user ID.',
					'type'        => 'integer',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'customer_name'       => [
					'description' => 'Customer display name.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'customer_first_name' => [
					'description' => 'Customer first name.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'customer_last_name'  => [
					'description' => 'Customer last name.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'customer_full_name'  => [
					'description' => 'Customer full name.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'customer_avatar'     => [
					'description' => 'Customer avatar URL.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'order_id'            => [
					'description' => 'Associated order ID.',
					'type'        => 'integer',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'order_item_id'       => [
					'description' => 'Associated order item ID.',
					'type'        => 'integer',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'cost'                => [
					'description' => 'Appointment cost.',
					'type'        => 'number',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'all_day'             => [
					'description' => 'Whether this is an all-day appointment.',
					'type'        => 'boolean',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'qty'                 => [
					'description' => 'Quantity/capacity used.',
					'type'        => 'integer',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'cal_color'           => [
					'description' => 'Calendar display color from product settings.',
					'type'        => 'string',
					'context'     => [ 'view' ],
					'readonly'    => true,
				],
				'order_info'          => [
					'description' => 'Order details including currency, total, and billing info.',
					'type'        => 'object',
					'context'     => [ 'view' ],
					'readonly'    => true,
					'properties'  => [
						'id'             => [ 'type' => 'integer' ],
						'currency'       => [ 'type' => 'string' ],
						'total'          => [ 'type' => 'string' ],
						'discount_total' => [ 'type' => 'string' ],
						'billing'        => [
							'type'       => 'object',
							'properties' => [
								'first_name' => [ 'type' => 'string' ],
								'last_name'  => [ 'type' => 'string' ],
								'email'      => [ 'type' => 'string' ],
								'phone'      => [ 'type' => 'string' ],
							],
						],
						'line_items'     => [
							'type'  => 'array',
							'items' => [
								'type'       => 'object',
								'properties' => [
									'id'         => [ 'type' => 'integer' ],
									'name'       => [ 'type' => 'string' ],
									'sku'        => [ 'type' => 'string' ],
									'product_id' => [ 'type' => 'integer' ],
									'quantity'   => [ 'type' => 'integer' ],
									'subtotal'   => [ 'type' => 'string' ],
									'total'      => [ 'type' => 'string' ],
									'meta_data'  => [
										'type'  => 'array',
										'items' => [
											'type'       => 'object',
											'properties' => [
												'key'           => [ 'type' => 'string' ],
												'value'         => [ 'type' => 'string' ],
												'display_key'   => [ 'type' => 'string' ],
												'display_value' => [ 'type' => 'string' ],
											],
										],
									],
								],
							],
						],
					],
				],
			],
		];

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