<?php
/**
 * Tutor-created single-booking save service.
 *
 * @package TutorHelper_Bookings
 */

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

/**
 * Validates tutor-entered booking data and delegates record creation to
 * the shared booking records service.
 *
 * Capability and nonce verification belong to the future admin controller.
 * This class contains reusable business validation only.
 */
final class TutorHelper_Tutor_Booking_Save {

	/**
	 * Locations supported by tutor-created booking records.
	 *
	 * Tutors are not restricted to locations configured on public
	 * availability or the selected Booking Type.
	 *
	 * @var array
	 */
	const ALLOWED_LOCATIONS = array(
		'online',
		'phone',
		'in_person',
		'custom',
		'other',
	);

	/**
	 * Statuses tutors may choose when creating a booking.
	 *
	 * @var array
	 */
	const ALLOWED_STATUSES = array(
		TutorHelper_Booking_Records::STATUS_CONFIRMED,
		TutorHelper_Booking_Records::STATUS_UNCONFIRMED,
	);

	/**
	 * Validate a positive WordPress user ID.
	 *
	 * @param mixed  $value      Raw ID.
	 * @param string $error_code Error code.
	 * @param string $message    Error message.
	 *
	 * @return int|WP_Error
	 */
	private static function normalise_user_id(
		$value,
		$error_code,
		$message
	) {
		$user_id = absint( $value );

		if ( ! $user_id ) {
			return new WP_Error(
				$error_code,
				$message
			);
		}

		return $user_id;
	}

	/**
	 * Validate a Booking Type ID.
	 *
	 * Existence and active status are deliberately checked by the
	 * shared records service to avoid a duplicate database query here.
	 *
	 * @param mixed $value Raw Booking Type ID.
	 *
	 * @return int|WP_Error
	 */
	private static function normalise_booking_type_id(
		$value
	) {
		$booking_type_id = absint( $value );

		if ( ! $booking_type_id ) {
			return new WP_Error(
				'tutorhelper_tutor_booking_type_missing',
				__(
					'Select a Booking Type.',
					'tutorhelper-bookings'
				)
			);
		}

		return $booking_type_id;
	}

	/**
	 * Validate an exact calendar date.
	 *
	 * @param mixed $value Raw date.
	 *
	 * @return string|WP_Error
	 */
	private static function normalise_date(
		$value
	) {
		$date = sanitize_text_field(
			(string) $value
		);

		$parsed =
			DateTimeImmutable::createFromFormat(
				'!Y-m-d',
				$date,
				new DateTimeZone( 'UTC' )
			);

		$errors =
			DateTimeImmutable::getLastErrors();

		if (
			false === $parsed
			|| (
				is_array( $errors )
				&& (
					0 < $errors['warning_count']
					|| 0 < $errors['error_count']
				)
			)
			|| $parsed->format( 'Y-m-d' )
				!== $date
		) {
			return new WP_Error(
				'tutorhelper_tutor_booking_date_invalid',
				__(
					'Enter a valid booking date.',
					'tutorhelper-bookings'
				)
			);
		}

		return $date;
	}

	/**
	 * Validate an exact 24-hour time.
	 *
	 * HTML time fields normally submit H:i, but H:i:s is accepted for
	 * reusable service calls and automated tests.
	 *
	 * @param mixed  $value      Raw time.
	 * @param string $error_code Error code.
	 * @param string $message    Error message.
	 *
	 * @return string|WP_Error Normalised H:i:s time.
	 */
	private static function normalise_time(
		$value,
		$error_code,
		$message
	) {
		$time = sanitize_text_field(
			(string) $value
		);

		$formats = array(
			'!H:i',
			'!H:i:s',
		);

		foreach ( $formats as $format ) {
			$parsed =
				DateTimeImmutable::createFromFormat(
					$format,
					$time,
					new DateTimeZone( 'UTC' )
				);

			$errors =
				DateTimeImmutable::getLastErrors();

			if (
				false !== $parsed
				&& ! (
					is_array( $errors )
					&& (
						0 < $errors['warning_count']
						|| 0 < $errors['error_count']
					)
				)
			) {
				$expected = '!H:i' === $format
					? $parsed->format( 'H:i' )
					: $parsed->format( 'H:i:s' );

				if ( $expected === $time ) {
					return $parsed->format(
						'H:i:s'
					);
				}
			}
		}

		return new WP_Error(
			$error_code,
			$message
		);
	}

	/**
	 * Validate a PHP-supported timezone.
	 *
	 * This accepts IANA timezone names and fixed offsets returned by
	 * WordPress installations that use an offset instead of a city.
	 *
	 * @param mixed $value Raw timezone.
	 *
	 * @return string|WP_Error
	 */
	private static function normalise_timezone(
		$value
	) {
		$timezone = sanitize_text_field(
			(string) $value
		);

		if ( '' === $timezone ) {
			$timezone = wp_timezone()->getName();
		}

		try {
			new DateTimeZone( $timezone );
		} catch ( Exception $exception ) {
			return new WP_Error(
				'tutorhelper_tutor_booking_timezone_invalid',
				__(
					'The booking timezone is invalid.',
					'tutorhelper-bookings'
				)
			);
		}

		return $timezone;
	}

	/**
	 * Validate a tutor-selected booking status.
	 *
	 * @param mixed $value Raw status.
	 *
	 * @return string|WP_Error
	 */
	private static function normalise_status(
		$value
	) {
		$status = sanitize_key(
			(string) $value
		);

		if ( '' === $status ) {
			$status =
				TutorHelper_Booking_Records::
					STATUS_CONFIRMED;
		}

		if (
			! in_array(
				$status,
				self::ALLOWED_STATUSES,
				true
			)
		) {
			return new WP_Error(
				'tutorhelper_tutor_booking_status_invalid',
				__(
					'Select a valid booking status.',
					'tutorhelper-bookings'
				)
			);
		}

		return $status;
	}

	/**
	 * Validate a tutor-selected location.
	 *
	 * @param mixed $value Raw location.
	 *
	 * @return string|WP_Error
	 */
	private static function normalise_location(
		$value
	) {
		$location = sanitize_key(
			(string) $value
		);

		if (
			! in_array(
				$location,
				self::ALLOWED_LOCATIONS,
				true
			)
		) {
			return new WP_Error(
				'tutorhelper_tutor_booking_location_invalid',
				__(
					'Select a valid booking location.',
					'tutorhelper-bookings'
				)
			);
		}

		return $location;
	}

	/**
	 * Build and validate local start and end date objects.
	 *
	 * The shared booking service performs the authoritative conversion
	 * to UTC. These objects are used here to reject invalid local ranges
	 * before attempting a database write.
	 *
	 * @param string $date       Booking date.
	 * @param string $start_time Start time in H:i:s.
	 * @param string $end_time   End time in H:i:s.
	 * @param string $timezone   PHP timezone.
	 *
	 * @return array|WP_Error
	 */
	private static function build_local_dates(
		$date,
		$start_time,
		$end_time,
		$timezone
	) {
		try {
			$timezone_object =
				new DateTimeZone( $timezone );

			$start =
				DateTimeImmutable::createFromFormat(
					'!Y-m-d H:i:s',
					$date . ' ' . $start_time,
					$timezone_object
				);

			$end =
				DateTimeImmutable::createFromFormat(
					'!Y-m-d H:i:s',
					$date . ' ' . $end_time,
					$timezone_object
				);
		} catch ( Exception $exception ) {
			return new WP_Error(
				'tutorhelper_tutor_booking_datetime_invalid',
				__(
					'The booking date or time is invalid.',
					'tutorhelper-bookings'
				)
			);
		}

		if (
			false === $start
			|| false === $end
		) {
			return new WP_Error(
				'tutorhelper_tutor_booking_datetime_invalid',
				__(
					'The booking date or time is invalid.',
					'tutorhelper-bookings'
				)
			);
		}

		if ( $end <= $start ) {
			return new WP_Error(
				'tutorhelper_tutor_booking_end_before_start',
				__(
					'The booking end time must be after its start time.',
					'tutorhelper-bookings'
				)
			);
		}

		return array(
			'start' => $start,
			'end'   => $end,
		);
	}

	/**
	 * Validate tutor and student accounts.
	 *
	 * Tutor permissions are checked by the future controller. This
	 * service only verifies that the requested tutor account exists and
	 * that the selected student has the TutorHelper student role.
	 *
	 * @param mixed $tutor_user_id   Raw tutor user ID.
	 * @param mixed $student_user_id Raw student user ID.
	 *
	 * @return array|WP_Error
	 */
	private static function validate_users(
		$tutor_user_id,
		$student_user_id
	) {
		$tutor_user_id =
			self::normalise_user_id(
				$tutor_user_id,
				'tutorhelper_tutor_booking_tutor_missing',
				__(
					'A tutor is required.',
					'tutorhelper-bookings'
				)
			);

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

		if (
			! get_user_by(
				'id',
				$tutor_user_id
			)
		) {
			return new WP_Error(
				'tutorhelper_tutor_booking_tutor_not_found',
				__(
					'The tutor account could not be found.',
					'tutorhelper-bookings'
				)
			);
		}

		$student_user_id =
			self::normalise_user_id(
				$student_user_id,
				'tutorhelper_tutor_booking_student_missing',
				__(
					'Select a student.',
					'tutorhelper-bookings'
				)
			);

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

		$student =
			TutorHelper_Students::get_student(
				$student_user_id
			);

		if ( ! $student instanceof WP_User ) {
			return new WP_Error(
				'tutorhelper_tutor_booking_student_not_found',
				__(
					'The selected TutorHelper student could not be found.',
					'tutorhelper-bookings'
				)
			);
		}

		return array(
			'tutor_user_id'   =>
				$tutor_user_id,

			'student_user_id' =>
				$student_user_id,
		);
	}

	/**
	 * Convert an evaluator blocking condition into a save-service error.
	 *
	 * @param array $evaluation Booking-condition evaluation.
	 *
	 * @return WP_Error|null
	 */
	private static function get_condition_error(
		$evaluation
	) {
		if (
			! is_array( $evaluation )
			|| ! empty(
				$evaluation['can_tutor_save']
			)
		) {
			return null;
		}

		$blocking_conditions = isset(
			$evaluation['blocking_conditions']
		)
		&& is_array(
			$evaluation['blocking_conditions']
		)
			? $evaluation['blocking_conditions']
			: array();

		foreach (
			$blocking_conditions as $condition
		) {
			if (
				! is_array( $condition )
				|| empty( $condition['code'] )
			) {
				continue;
			}

			$code = sanitize_key(
				$condition['code']
			);

			switch ( $code ) {
				case 'tutor_leave':
					return new WP_Error(
						'tutorhelper_tutor_booking_tutor_leave',
						__(
							'The booking cannot be created while the tutor is marked as unavailable.',
							'tutorhelper-bookings'
						),
						array(
							'condition_code' =>
								'tutor_leave',
						)
					);

				case 'booking_conflict':
					return new WP_Error(
						'tutorhelper_booking_time_conflict',
						__(
							'The tutor already has another booking during this time.',
							'tutorhelper-bookings'
						),
						array(
							'condition_code' =>
								'booking_conflict',
						)
					);
			}
		}

		return new WP_Error(
			'tutorhelper_tutor_booking_conditions_blocked',
			__(
				'The booking cannot be created because one or more booking conditions prevent it.',
				'tutorhelper-bookings'
			)
		);
	}


	/**
	 * Create one tutor-entered booking.
	 *
	 * Public availability restrictions are advisory for tutor-created
	 * bookings. Tutor leave and booking conflicts are enforced before the
	 * shared booking record is created.
	 *
	 * Expected data:
	 *
	 * - booking_type_id
	 * - student_user_id
	 * - booking_date
	 * - start_time
	 * - end_time
	 * - timezone
	 * - status
	 * - location_type
	 * - location_details
	 * - notes
	 *
	 * @param array $data          Submitted booking data.
	 * @param int   $tutor_user_id Tutor creating the booking.
	 *
	 * @return array|WP_Error
	 */
	public static function save(
		$data,
		$tutor_user_id
	) {
		$data = is_array( $data )
			? $data
			: array();

		$users = self::validate_users(
			$tutor_user_id,
			isset( $data['student_user_id'] )
				? $data['student_user_id']
				: 0
		);

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

		$booking_type_id =
			self::normalise_booking_type_id(
				isset( $data['booking_type_id'] )
					? $data['booking_type_id']
					: 0
			);

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

		$date = self::normalise_date(
			isset( $data['booking_date'] )
				? $data['booking_date']
				: ''
		);

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

		$start_time = self::normalise_time(
			isset( $data['start_time'] )
				? $data['start_time']
				: '',
			'tutorhelper_tutor_booking_start_time_invalid',
			__(
				'Enter a valid booking start time.',
				'tutorhelper-bookings'
			)
		);

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

		$end_time = self::normalise_time(
			isset( $data['end_time'] )
				? $data['end_time']
				: '',
			'tutorhelper_tutor_booking_end_time_invalid',
			__(
				'Enter a valid booking end time.',
				'tutorhelper-bookings'
			)
		);

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

		$timezone = self::normalise_timezone(
			isset( $data['timezone'] )
				? $data['timezone']
				: ''
		);

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

		$local_dates =
			self::build_local_dates(
				$date,
				$start_time,
				$end_time,
				$timezone
			);

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

		$status = self::normalise_status(
			isset( $data['status'] )
				? $data['status']
				: ''
		);

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

		$location_type =
			self::normalise_location(
				isset( $data['location_type'] )
					? $data['location_type']
					: ''
			);

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

		$condition_evaluation =
			TutorHelper_Booking_Conditions::
				evaluate(
					array(
						'tutor_user_id' =>
							$users['tutor_user_id'],

						'booking_type_id' =>
							$booking_type_id,

						'booking_start' =>
							$local_dates['start']
								->format(
									'Y-m-d H:i:s'
								),

						'booking_end' =>
							$local_dates['end']
								->format(
									'Y-m-d H:i:s'
								),

						'timezone' =>
							$timezone,

						'location_type' =>
							$location_type,

						'exclude_booking_id' =>
							0,
					)
				);

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

		$condition_error =
			self::get_condition_error(
				$condition_evaluation
			);

		if ( is_wp_error( $condition_error ) ) {
			TutorHelper_Logger::warning(
				'Tutor single booking blocked by booking conditions.',
				array(
					'tutor_user_id' =>
						$users['tutor_user_id'],

					'student_user_id' =>
						$users['student_user_id'],

					'booking_type_id' =>
						$booking_type_id,

					'error_code' =>
						$condition_error
							->get_error_code(),

					'blocking_conditions' =>
						isset(
							$condition_evaluation[
								'blocking_conditions'
							]
						)
						&& is_array(
							$condition_evaluation[
								'blocking_conditions'
							]
						)
							? array_values(
								array_map(
									static function (
										$condition
									) {
										return is_array(
											$condition
										)
										&& isset(
											$condition['code']
										)
											? sanitize_key(
												$condition['code']
											)
											: '';
									},
									$condition_evaluation[
										'blocking_conditions'
									]
								)
							)
							: array(),
				)
			);

			return $condition_error;
		}


		$record_data = array(
			'booking_type_id' =>
				$booking_type_id,

			'student_user_id' =>
				$users['student_user_id'],

			'tutor_user_id' =>
				$users['tutor_user_id'],

			'booking_start' =>
				$local_dates['start']->format(
					'Y-m-d H:i:s'
				),

			'booking_end' =>
				$local_dates['end']->format(
					'Y-m-d H:i:s'
				),

			'timezone' =>
				$timezone,

			'status' =>
				$status,

			'location_type' =>
				$location_type,

			'location_details' =>
				isset( $data['location_details'] )
					? sanitize_textarea_field(
						$data['location_details']
					)
					: '',

			'notes' =>
				isset( $data['notes'] )
					? sanitize_textarea_field(
						$data['notes']
					)
					: '',

			'source' =>
				TutorHelper_Booking_Records::
					SOURCE_TUTOR,

			'payment_method' =>
				TutorHelper_Booking_Records::
					PAYMENT_METHOD_MANUAL,
		);

		$booking_id =
			TutorHelper_Booking_Records::create(
				$record_data
			);

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

		$booking =
			TutorHelper_Booking_Records::get_by_id(
				$booking_id
			);

		if ( ! is_object( $booking ) ) {
			return new WP_Error(
				'tutorhelper_tutor_booking_record_missing',
				__(
					'The booking was created but could not be retrieved.',
					'tutorhelper-bookings'
				)
			);
		}

		TutorHelper_Logger::success(
			'Tutor single booking saved.',
			array(
				'booking_id' =>
					absint( $booking_id ),

				'tutor_user_id' =>
					$users['tutor_user_id'],

				'student_user_id' =>
					$users['student_user_id'],

				'booking_type_id' =>
					$booking_type_id,

				'status' =>
					$status,

				'location_type' =>
					$location_type,

				
			)
		);

		return array(
			'booking_id' =>
				absint( $booking_id ),

			'booking' =>
				$booking,

			'status' =>
				$booking->status,

			'source' =>
				$booking->source,

			'price_minor_units' =>
				absint(
					$booking->price_minor_units
				),

			'currency' =>
				sanitize_key(
					$booking->currency
				),

			'conditions' =>
				isset(
					$condition_evaluation[
						'conditions'
					]
				)
				&& is_array(
					$condition_evaluation[
						'conditions'
					]
				)
					? $condition_evaluation[
						'conditions'
					]
					: array(),

			'warnings' =>
				isset(
					$condition_evaluation[
						'warnings'
					]
				)
				&& is_array(
					$condition_evaluation[
						'warnings'
					]
				)
					? $condition_evaluation[
						'warnings'
					]
					: array(),
		);
	}
}
