<?php
namespace EM_Pro\API;

class Service {

	public static function list_coupons( $params = array() ) {
		if ( !static::coupons_available() ) {
			return Utils::error( 'emp_api_coupons_unavailable', __( 'Coupons are not available.', 'em-pro' ), 404 );
		}
		if ( !Utils::can_manage_bookings() ) {
			return Utils::error( 'emp_api_coupon_forbidden', __( 'You do not have permission to view coupons.', 'em-pro' ), 403 );
		}
		$params = Utils::normalize_input( $params );
		$collection = Utils::collection_args( $params );
		$args = array(
			'limit' => $collection['per_page'],
			'offset' => $collection['offset'],
		);
		foreach ( array( 'code', 'event', 'scope', 'blog', 'ids' ) as $key ) {
			if ( array_key_exists( $key, $params ) ) {
				$args[ $key ] = $params[ $key ];
			}
		}
		foreach ( array( 'eventwide', 'sitewide' ) as $key ) {
			if ( array_key_exists( $key, $params ) ) {
				$args[ $key ] = Utils::is_truthy( $params[ $key ] );
			}
		}
		if ( Utils::can_manage_others_bookings() ) {
			if ( !empty( $params['owner'] ) ) {
				$args['owner'] = absint( $params['owner'] );
			}
		} else {
			$args['owner'] = get_current_user_id();
		}
		$coupons = \EM_Coupons::get( $args );
		$items = array();
		foreach ( $coupons as $EM_Coupon ) {
			if ( static::can_manage_coupon( $EM_Coupon ) ) {
				$items[] = static::prepare_coupon( $EM_Coupon );
			}
		}
		$total = \EM_Coupons::count( array_merge( $args, array( 'limit' => 0, 'offset' => 0 ) ) );
		return array(
			'items' => $items,
			'pagination' => Utils::pagination( $total, $collection['page'], $collection['per_page'] ),
		);
	}

	public static function get_coupon( $id ) {
		$EM_Coupon = static::get_coupon_object( $id );
		if ( is_wp_error( $EM_Coupon ) ) return $EM_Coupon;
		if ( !static::can_manage_coupon( $EM_Coupon ) ) {
			return Utils::error( 'emp_api_coupon_forbidden', __( 'You do not have permission to view this coupon.', 'em-pro' ), 403 );
		}
		return static::prepare_coupon( $EM_Coupon );
	}

	public static function create_coupon( $data ) {
		return static::save_coupon( $data );
	}

	public static function update_coupon( $id, $data ) {
		return static::save_coupon( $data, $id );
	}

	protected static function save_coupon( $data, $id = 0 ) {
		if ( !static::ensure_coupon_admin() ) {
			return Utils::error( 'emp_api_coupons_unavailable', __( 'Coupons are not available.', 'em-pro' ), 404 );
		}
		$data = Utils::normalize_input( $data );
		$EM_Coupon = $id ? new \EM_Coupon_Admin( absint( $id ) ) : new \EM_Coupon_Admin();
		if ( $id && empty( $EM_Coupon->coupon_id ) ) {
			return Utils::error( 'emp_api_coupon_not_found', __( 'Coupon not found.', 'em-pro' ), 404 );
		}
		if ( !static::can_manage_coupon( $EM_Coupon ) ) {
			return Utils::object_error( 'emp_api_coupon_forbidden', $EM_Coupon, __( 'You do not have permission to save this coupon.', 'em-pro' ), 403 );
		}
		// For partial updates, pre-load current coupon state so get_post() doesn't wipe untouched fields.
		// with_request_data() lives on core's Utils (Pro's Utils does not extend it).
		$base = $id ? $EM_Coupon->to_request_data() : array();
		$request_data = array_merge( $base, $data );
		$valid = \EM\API\Utils::with_request_data( $request_data, function() use ( $EM_Coupon ) {
			return $EM_Coupon->get_post( true );
		} );
		if ( !$valid ) {
			return Utils::object_error( 'emp_api_coupon_invalid', $EM_Coupon, __( 'Coupon data is invalid.', 'em-pro' ), 400 );
		}
		if ( !$EM_Coupon->save() ) {
			return Utils::object_error( 'emp_api_coupon_save_failed', $EM_Coupon, __( 'Coupon could not be saved.', 'em-pro' ), 500 );
		}
		return static::prepare_coupon( $EM_Coupon );
	}

	public static function delete_coupon( $id ) {
		if ( !static::ensure_coupon_admin() ) {
			return Utils::error( 'emp_api_coupons_unavailable', __( 'Coupons are not available.', 'em-pro' ), 404 );
		}
		$EM_Coupon = new \EM_Coupon_Admin( absint( $id ) );
		if ( empty( $EM_Coupon->coupon_id ) ) {
			return Utils::error( 'emp_api_coupon_not_found', __( 'Coupon not found.', 'em-pro' ), 404 );
		}
		if ( !static::can_manage_coupon( $EM_Coupon ) ) {
			return Utils::object_error( 'emp_api_coupon_forbidden', $EM_Coupon, __( 'You do not have permission to delete this coupon.', 'em-pro' ), 403 );
		}
		$previous = static::prepare_coupon( $EM_Coupon );
		if ( !$EM_Coupon->delete() ) {
			return Utils::object_error( 'emp_api_coupon_delete_failed', $EM_Coupon, __( 'Coupon could not be deleted.', 'em-pro' ), 500 );
		}
		return array( 'deleted' => true, 'previous' => $previous );
	}

	public static function validate_coupon( $id, $params = array() ) {
		$EM_Coupon = static::get_coupon_object( $id );
		if ( is_wp_error( $EM_Coupon ) ) return $EM_Coupon;
		if ( !static::can_manage_coupon( $EM_Coupon ) ) {
			return Utils::error( 'emp_api_coupon_forbidden', __( 'You do not have permission to validate this coupon.', 'em-pro' ), 403 );
		}
		$params = Utils::normalize_input( $params );
		$event_match = null;
		if ( !empty( $params['event_id'] ) ) {
			$EM_Event = em_get_event( $params['event_id'] );
			if ( !$EM_Event || !$EM_Event->get_id() ) {
				return Utils::error( 'emp_api_event_not_found', __( 'Event not found.', 'em-pro' ), 404 );
			}
			$event_coupon = \EM_Coupons::event_get_coupon( $EM_Coupon->coupon_code, $EM_Event );
			$event_match = $event_coupon && absint( $event_coupon->coupon_id ) === absint( $EM_Coupon->coupon_id );
		}
		$total = isset( $params['total'] ) && is_numeric( $params['total'] ) ? (float) $params['total'] : null;
		$valid = $EM_Coupon->is_valid() && $event_match !== false;
		return apply_filters( 'emp_api_validate_coupon', array(
			'coupon' => static::prepare_coupon( $EM_Coupon ),
			'valid' => (bool) $valid,
			'event_match' => $event_match,
			'quote' => $total === null ? null : static::quote_coupon_discount( $EM_Coupon, $total ),
		), $EM_Coupon, $params );
	}

	public static function list_event_coupons( $event_id, $params = array() ) {
		if ( !static::coupons_available() ) {
			return Utils::error( 'emp_api_coupons_unavailable', __( 'Coupons are not available.', 'em-pro' ), 404 );
		}
		$EM_Event = static::get_event_object( $event_id );
		if ( is_wp_error( $EM_Event ) ) return $EM_Event;
		if ( !$EM_Event->can_manage( 'manage_bookings', 'manage_others_bookings' ) ) {
			return Utils::error( 'emp_api_event_coupon_forbidden', __( 'You do not have permission to view coupons for this event.', 'em-pro' ), 403 );
		}
		$items = array();
		foreach ( \EM_Coupons::event_get_coupons( $EM_Event ) as $EM_Coupon ) {
			if ( static::can_manage_coupon( $EM_Coupon ) ) {
				$items[] = static::prepare_coupon( $EM_Coupon );
			}
		}
		return array(
			'event_id' => $EM_Event->get_event_uid(),
			'items' => $items,
			'pagination' => Utils::pagination( count( $items ), 1, max( 1, count( $items ) ) ),
		);
	}

	public static function attach_event_coupon( $event_id, $coupon_id ) {
		$EM_Event = static::get_event_object( $event_id );
		if ( is_wp_error( $EM_Event ) ) return $EM_Event;
		$EM_Coupon = static::get_coupon_object( $coupon_id );
		if ( is_wp_error( $EM_Coupon ) ) return $EM_Coupon;
		if ( !$EM_Event->can_manage( 'manage_bookings', 'manage_others_bookings' ) || !static::can_manage_coupon( $EM_Coupon ) ) {
			return Utils::error( 'emp_api_event_coupon_forbidden', __( 'You do not have permission to link this coupon to this event.', 'em-pro' ), 403 );
		}
		global $wpdb;
		$exists = $wpdb->get_var( $wpdb->prepare( 'SELECT meta_id FROM ' . EM_META_TABLE . ' WHERE object_id=%d AND meta_key=%s AND meta_value=%d LIMIT 1', $EM_Event->get_event_id(), 'event-coupon', $EM_Coupon->coupon_id ) );
		if ( !$exists ) {
			$inserted = $wpdb->insert( EM_META_TABLE, array(
				'object_id' => $EM_Event->get_event_id(),
				'meta_key' => 'event-coupon',
				'meta_value' => absint( $EM_Coupon->coupon_id ),
			), array( '%d', '%s', '%d' ) );
			if ( !$inserted ) {
				return Utils::error( 'emp_api_event_coupon_link_failed', __( 'Coupon could not be linked to this event.', 'em-pro' ), 500 );
			}
		}
		unset( $EM_Event->coupons, $EM_Event->coupon_ids );
		return array(
			'event_id' => $EM_Event->get_event_uid(),
			'linked' => true,
			'coupon' => static::prepare_coupon( $EM_Coupon ),
		);
	}

	public static function detach_event_coupon( $event_id, $coupon_id ) {
		$EM_Event = static::get_event_object( $event_id );
		if ( is_wp_error( $EM_Event ) ) return $EM_Event;
		$EM_Coupon = static::get_coupon_object( $coupon_id );
		if ( is_wp_error( $EM_Coupon ) ) return $EM_Coupon;
		if ( !$EM_Event->can_manage( 'manage_bookings', 'manage_others_bookings' ) || !static::can_manage_coupon( $EM_Coupon ) ) {
			return Utils::error( 'emp_api_event_coupon_forbidden', __( 'You do not have permission to unlink this coupon from this event.', 'em-pro' ), 403 );
		}
		global $wpdb;
		$previous = static::prepare_coupon( $EM_Coupon );
		$deleted = $wpdb->delete( EM_META_TABLE, array(
			'object_id' => $EM_Event->get_event_id(),
			'meta_key' => 'event-coupon',
			'meta_value' => absint( $EM_Coupon->coupon_id ),
		), array( '%d', '%s', '%d' ) );
		if ( $deleted === false ) {
			return Utils::error( 'emp_api_event_coupon_unlink_failed', __( 'Coupon could not be unlinked from this event.', 'em-pro' ), 500 );
		}
		unset( $EM_Event->coupons, $EM_Event->coupon_ids );
		return array( 'deleted' => true, 'event_id' => $EM_Event->get_event_uid(), 'previous' => $previous );
	}

	public static function list_transactions( $params = array() ) {
		if ( !defined( 'EM_TRANSACTIONS_TABLE' ) ) {
			return Utils::error( 'emp_api_transactions_unavailable', __( 'Transactions are not available.', 'em-pro' ), 404 );
		}
		if ( !Utils::can_manage_others_bookings() && empty( $params['booking_id'] ) ) {
			return Utils::error( 'emp_api_transaction_forbidden', __( 'You do not have permission to view transactions.', 'em-pro' ), 403 );
		}
		$params = Utils::normalize_input( $params );
		$collection = Utils::collection_args( $params );
		$query = static::transaction_query_parts( $params );
		if ( is_wp_error( $query ) ) return $query;
		global $wpdb;
		$orderby = static::transaction_orderby( $params );
		$sql = 'SELECT * FROM ' . EM_TRANSACTIONS_TABLE . ' tx ' . $query['where'] . " ORDER BY {$orderby['orderby']} {$orderby['order']} LIMIT %d OFFSET %d";
		$values = array_merge( $query['values'], array( $collection['per_page'], $collection['offset'] ) );
		$transactions = $wpdb->get_results( $wpdb->prepare( $sql, $values ) );
		$count_sql = 'SELECT COUNT(transaction_id) FROM ' . EM_TRANSACTIONS_TABLE . ' tx ' . $query['where'];
		$total = $query['values'] ? $wpdb->get_var( $wpdb->prepare( $count_sql, $query['values'] ) ) : $wpdb->get_var( $count_sql );
		return array(
			'items' => array_map( array( static::class, 'prepare_transaction' ), $transactions ),
			'pagination' => Utils::pagination( $total, $collection['page'], $collection['per_page'] ),
		);
	}

	public static function get_transaction( $id ) {
		$transaction = static::get_transaction_row( $id );
		if ( is_wp_error( $transaction ) ) return $transaction;
		if ( !static::can_manage_transaction( $transaction ) ) {
			return Utils::error( 'emp_api_transaction_forbidden', __( 'You do not have permission to view this transaction.', 'em-pro' ), 403 );
		}
		return static::prepare_transaction( $transaction );
	}

	public static function delete_transaction( $id ) {
		$transaction = static::get_transaction_row( $id );
		if ( is_wp_error( $transaction ) ) return $transaction;
		if ( !static::can_manage_transaction( $transaction ) ) {
			return Utils::error( 'emp_api_transaction_forbidden', __( 'You do not have permission to delete this transaction.', 'em-pro' ), 403 );
		}
		global $wpdb;
		$previous = static::prepare_transaction( $transaction );
		$result = $wpdb->delete( EM_TRANSACTIONS_TABLE, array( 'transaction_id' => absint( $id ) ), array( '%d' ) );
		if ( $result === false ) {
			return Utils::error( 'emp_api_transaction_delete_failed', __( 'Transaction could not be deleted.', 'em-pro' ), 500 );
		}
		return array( 'deleted' => true, 'previous' => $previous );
	}

	/**
	 * Record a manual / at-the-gate payment (cash, card taken offline, etc.) against a booking,
	 * mirroring the offline gateway's admin "add payment" flow, and approve once fully paid.
	 */
	public static function record_manual_payment( $id, $data ) {
		if ( !defined( 'EM_TRANSACTIONS_TABLE' ) || !class_exists( '\\EM\\Payments\\Offline\\Gateway' ) ) {
			return Utils::error( 'emp_api_payments_unavailable', __( 'Manual payments are not available on this site.', 'em-pro' ), 404 );
		}
		$EM_Booking = em_get_booking( absint( $id ) );
		if ( !$EM_Booking || !$EM_Booking->booking_id ) {
			return Utils::error( 'emp_api_booking_not_found', __( 'Booking not found.', 'em-pro' ), 404 );
		}
		if ( !$EM_Booking->can_manage( 'manage_bookings', 'manage_others_bookings' ) ) {
			return Utils::error( 'emp_api_payment_forbidden', __( 'You do not have permission to record payments for this booking.', 'em-pro' ), 403 );
		}
		$data = Utils::normalize_input( $data );
		if ( !isset( $data['amount'] ) || !is_numeric( $data['amount'] ) ) {
			return Utils::error( 'emp_api_payment_amount_invalid', __( 'A numeric payment amount is required.', 'em-pro' ), 400 );
		}
		$amount = round( (float) $data['amount'], 2 );
		$type = !empty( $data['type'] ) ? sanitize_text_field( $data['type'] ) : 'cash';
		$note = !empty( $data['note'] ) ? sanitize_text_field( $data['note'] ) : __( 'Manual payment recorded via the admin app.', 'em-pro' );
		$approve = !array_key_exists( 'approve', $data ) || Utils::is_truthy( $data['approve'] );
		$currency = get_option( 'dbem_bookings_currency' );

		\EM\Payments\Offline\Gateway::record_transaction( $EM_Booking, $amount, $currency, current_time( 'mysql', true ), '', 'Completed', $note, $type, ucfirst( $type ) );

		$price = (float) $EM_Booking->get_price();
		$total_paid = (float) $EM_Booking->get_total_paid();
		$fully_paid = $total_paid >= $price;
		$approved = false;
		if ( $approve && $fully_paid && (int) $EM_Booking->booking_status !== 1 ) {
			$approved = (bool) $EM_Booking->approve();
		}
		do_action( 'em_payment_processed', $EM_Booking, '\\EM\\Payments\\Offline\\Gateway' );

		return array(
			'recorded'       => true,
			'amount'         => $amount,
			'currency'       => $currency,
			'type'           => $type,
			'price'          => $price,
			'total_paid'     => (float) $EM_Booking->get_total_paid(),
			'fully_paid'     => (bool) $fully_paid,
			'approved'       => $approved,
			'booking_status' => absint( $EM_Booking->booking_status ),
		);
	}

	public static function get_attendance( $id ) {
		$EM_Ticket_Booking = static::get_ticket_booking( $id );
		if ( is_wp_error( $EM_Ticket_Booking ) ) return $EM_Ticket_Booking;
		if ( !$EM_Ticket_Booking->can_manage() ) {
			return Utils::error( 'emp_api_attendance_forbidden', __( 'You do not have permission to view this attendance record.', 'em-pro' ), 403 );
		}
		return static::prepare_attendance( $EM_Ticket_Booking );
	}

	public static function set_attendance( $id, $action ) {
		if ( !class_exists( '\EM_Pro\Attendance\Attendance' ) ) {
			return Utils::error( 'emp_api_attendance_unavailable', __( 'Attendance is not available.', 'em-pro' ), 404 );
		}
		$EM_Ticket_Booking = static::get_ticket_booking( $id );
		if ( is_wp_error( $EM_Ticket_Booking ) ) return $EM_Ticket_Booking;
		if ( !$EM_Ticket_Booking->can_manage() ) {
			return Utils::error( 'emp_api_attendance_forbidden', __( 'You do not have permission to update this attendance record.', 'em-pro' ), 403 );
		}
		$action = sanitize_key( $action );
		if ( !in_array( $action, array( 'checkin', 'checkout' ), true ) ) {
			return Utils::error( 'emp_api_attendance_action_invalid', __( 'Attendance action is invalid.', 'em-pro' ), 400 );
		}
		$result = \EM_Pro\Attendance\Attendance::handle_action( $EM_Ticket_Booking, $action );
		$result['attendance'] = static::prepare_attendance( $EM_Ticket_Booking );
		return $result;
	}

	public static function get_rsvp( $id ) {
		$EM_Booking = static::get_booking( $id );
		if ( is_wp_error( $EM_Booking ) ) return $EM_Booking;
		if ( !static::can_read_booking( $EM_Booking ) ) {
			return Utils::error( 'emp_api_rsvp_forbidden', __( 'You do not have permission to view this RSVP status.', 'em-pro' ), 403 );
		}
		return static::prepare_rsvp( $EM_Booking );
	}

	public static function set_rsvp( $id, $status ) {
		$EM_Booking = static::get_booking( $id );
		if ( is_wp_error( $EM_Booking ) ) return $EM_Booking;
		if ( !static::can_update_rsvp( $EM_Booking ) ) {
			return Utils::error( 'emp_api_rsvp_forbidden', __( 'You do not have permission to update this RSVP status.', 'em-pro' ), 403 );
		}
		$status = $status === null || $status === '' ? null : absint( $status );
		if ( $status !== null && !array_key_exists( $status, \EM_Booking::get_rsvp_statuses() ) ) {
			return Utils::error( 'emp_api_rsvp_status_invalid', __( 'RSVP status is invalid.', 'em-pro' ), 400 );
		}
		if ( !$EM_Booking->set_rsvp_status( $status, array( 'api' => true ) ) ) {
			return Utils::object_error( 'emp_api_rsvp_update_failed', $EM_Booking, __( 'RSVP status could not be updated.', 'em-pro' ), 400 );
		}
		return static::prepare_rsvp( $EM_Booking );
	}

	protected static function coupons_available() {
		return class_exists( '\EM_Coupon' ) && class_exists( '\EM_Coupons' );
	}

	protected static function ensure_coupon_admin() {
		if ( !static::coupons_available() ) return false;
		if ( !class_exists( '\EM_Coupon_Admin' ) ) {
			$legacy_gateways = (
				class_exists( '\EM_Options' ) && \EM_Options::site_get( 'legacy-gateways', false )
			) || (
				function_exists( 'em_constant' ) && em_constant( 'EMP_GATEWAY_LEGACY' )
			);
			$admin_files = $legacy_gateways ? array(
				EMP_DIR . '/add-ons/gateways-legacy/coupons/coupon-admin.php',
				EMP_DIR . '/add-ons/coupons/coupon-admin.php',
			) : array(
				EMP_DIR . '/add-ons/coupons/coupon-admin.php',
				EMP_DIR . '/add-ons/gateways-legacy/coupons/coupon-admin.php',
			);
			foreach ( $admin_files as $admin_file ) {
				if ( file_exists( $admin_file ) ) {
					include_once $admin_file;
				}
				if ( class_exists( '\EM_Coupon_Admin' ) ) {
					break;
				}
			}
		}
		return class_exists( '\EM_Coupon_Admin' );
	}

	protected static function get_coupon_object( $id ) {
		if ( !static::coupons_available() ) {
			return Utils::error( 'emp_api_coupons_unavailable', __( 'Coupons are not available.', 'em-pro' ), 404 );
		}
		$EM_Coupon = new \EM_Coupon( absint( $id ) );
		if ( empty( $EM_Coupon->coupon_id ) ) {
			return Utils::error( 'emp_api_coupon_not_found', __( 'Coupon not found.', 'em-pro' ), 404 );
		}
		return $EM_Coupon;
	}

	protected static function get_event_object( $id ) {
		$EM_Event = em_get_event( $id );
		if ( !$EM_Event || !$EM_Event->get_id() ) {
			return Utils::error( 'emp_api_event_not_found', __( 'Event not found.', 'em-pro' ), 404 );
		}
		return $EM_Event;
	}

	protected static function can_manage_coupon( $EM_Coupon ) {
		return is_object( $EM_Coupon ) && method_exists( $EM_Coupon, 'can_manage' ) && $EM_Coupon->can_manage( 'manage_bookings', 'manage_others_bookings' );
	}

	public static function prepare_coupon( $EM_Coupon ) {
		$api = array(
			'id' => absint( $EM_Coupon->coupon_id ),
			'code' => $EM_Coupon->coupon_code,
			'name' => $EM_Coupon->coupon_name,
			'description' => $EM_Coupon->coupon_description,
			'start' => static::prepare_date( $EM_Coupon->coupon_start ),
			'end' => static::prepare_date( $EM_Coupon->coupon_end ),
			'max' => $EM_Coupon->coupon_max === null ? null : absint( $EM_Coupon->coupon_max ),
			'type' => $EM_Coupon->coupon_type,
			'tax' => $EM_Coupon->coupon_tax,
			'discount' => (float) $EM_Coupon->coupon_discount,
			'eventwide' => !empty( $EM_Coupon->coupon_eventwide ),
			'sitewide' => !empty( $EM_Coupon->coupon_sitewide ),
			'private' => !empty( $EM_Coupon->coupon_private ),
			'count' => absint( $EM_Coupon->get_count() ),
			'valid' => (bool) $EM_Coupon->is_valid(),
			'owner_id' => absint( $EM_Coupon->coupon_owner ),
			'blog_id' => absint( $EM_Coupon->blog_id ),
			'discount_text' => $EM_Coupon->get_discount_text(),
			'permissions' => array(
				'edit' => static::can_manage_coupon( $EM_Coupon ),
				'delete' => static::can_manage_coupon( $EM_Coupon ),
			),
		);
		return apply_filters( 'emp_api_prepare_coupon', $api, $EM_Coupon );
	}

	protected static function quote_coupon_discount( $EM_Coupon, $total ) {
		$total = max( 0, (float) $total );
		$discount = (float) $EM_Coupon->coupon_discount;
		$amount = $EM_Coupon->coupon_type === '%' ? ( $total * $discount / 100 ) : $discount;
		$amount = min( $total, max( 0, $amount ) );
		return array(
			'total' => $total,
			'discount_amount' => $amount,
			'discounted_total' => max( 0, $total - $amount ),
			'tax' => $EM_Coupon->coupon_tax,
		);
	}

	protected static function transaction_query_parts( $params ) {
		global $wpdb;
		$conditions = array();
		$values = array();
		if ( !empty( $params['booking_id'] ) ) {
			$EM_Booking = em_get_booking( absint( $params['booking_id'] ) );
			if ( !$EM_Booking || !$EM_Booking->booking_id || !$EM_Booking->can_manage( 'manage_bookings', 'manage_others_bookings' ) ) {
				return Utils::error( 'emp_api_transaction_forbidden', __( 'You do not have permission to view transactions for this booking.', 'em-pro' ), 403 );
			}
			$conditions[] = 'tx.booking_id = %d';
			$values[] = absint( $params['booking_id'] );
		}
		if ( !empty( $params['gateway'] ) ) {
			$conditions[] = 'tx.transaction_gateway = %s';
			$values[] = sanitize_key( $params['gateway'] );
		}
		if ( !empty( $params['status'] ) ) {
			$conditions[] = 'tx.transaction_status = %s';
			$values[] = sanitize_text_field( $params['status'] );
		}
		if ( !empty( $params['search'] ) ) {
			$conditions[] = '(tx.transaction_id = %d OR tx.transaction_gateway_id = %s)';
			$values[] = absint( $params['search'] );
			$values[] = sanitize_text_field( $params['search'] );
		}
		return array(
			'where' => $conditions ? 'WHERE ' . implode( ' AND ', $conditions ) : '',
			'values' => $values,
		);
	}

	protected static function transaction_orderby( $params ) {
		$allowed = array( 'transaction_id', 'transaction_timestamp', 'transaction_total_amount', 'transaction_gateway', 'transaction_status' );
		$orderby = !empty( $params['orderby'] ) && in_array( $params['orderby'], $allowed, true ) ? $params['orderby'] : 'transaction_timestamp';
		$order = !empty( $params['order'] ) && strtoupper( $params['order'] ) === 'ASC' ? 'ASC' : 'DESC';
		return array( 'orderby' => $orderby, 'order' => $order );
	}

	protected static function get_transaction_row( $id ) {
		if ( !defined( 'EM_TRANSACTIONS_TABLE' ) ) {
			return Utils::error( 'emp_api_transactions_unavailable', __( 'Transactions are not available.', 'em-pro' ), 404 );
		}
		global $wpdb;
		$transaction = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM ' . EM_TRANSACTIONS_TABLE . ' WHERE transaction_id=%d', absint( $id ) ) );
		if ( !$transaction ) {
			return Utils::error( 'emp_api_transaction_not_found', __( 'Transaction not found.', 'em-pro' ), 404 );
		}
		return $transaction;
	}

	protected static function can_manage_transaction( $transaction ) {
		if ( Utils::can_manage_others_bookings() ) return true;
		if ( !empty( $transaction->booking_id ) ) {
			$EM_Booking = em_get_booking( absint( $transaction->booking_id ) );
			return $EM_Booking && $EM_Booking->booking_id && $EM_Booking->can_manage( 'manage_bookings', 'manage_others_bookings' );
		}
		return false;
	}

	public static function prepare_transaction( $transaction ) {
		$api = array(
			'id' => absint( $transaction->transaction_id ),
			'booking_id' => absint( $transaction->booking_id ),
			'gateway_id' => $transaction->transaction_gateway_id,
			'gateway' => $transaction->transaction_gateway,
			'payment_type' => $transaction->transaction_payment_type,
			'payment_type_detail' => $transaction->transaction_payment_type_detail,
			'timestamp' => static::prepare_datetime( $transaction->transaction_timestamp ),
			'total_amount' => $transaction->transaction_total_amount === null ? null : (float) $transaction->transaction_total_amount,
			'currency' => $transaction->transaction_currency,
			'status' => $transaction->transaction_status,
			'due_date' => static::prepare_date( $transaction->transaction_duedate ),
			'note' => $transaction->transaction_note,
			'expires' => static::prepare_datetime( $transaction->transaction_expires ),
			'permissions' => array(
				'delete' => static::can_manage_transaction( $transaction ),
			),
		);
		return apply_filters( 'emp_api_prepare_transaction', $api, $transaction );
	}

	protected static function get_ticket_booking( $id ) {
		if ( !class_exists( '\EM_Ticket_Booking' ) ) {
			return Utils::error( 'emp_api_attendance_unavailable', __( 'Ticket bookings are not available.', 'em-pro' ), 404 );
		}
		$EM_Ticket_Booking = new \EM_Ticket_Booking( $id );
		if ( empty( $EM_Ticket_Booking->ticket_booking_id ) ) {
			return Utils::error( 'emp_api_ticket_booking_not_found', __( 'Ticket booking not found.', 'em-pro' ), 404 );
		}
		return $EM_Ticket_Booking;
	}

	public static function prepare_attendance( $EM_Ticket_Booking ) {
		$status = class_exists( '\EM_Pro\Attendance\Attendance' ) ? \EM_Pro\Attendance\Attendance::get_status( $EM_Ticket_Booking ) : null;
		$api = array(
			'id' => absint( $EM_Ticket_Booking->ticket_booking_id ),
			'uuid' => $EM_Ticket_Booking->ticket_uuid,
			'booking_id' => absint( $EM_Ticket_Booking->booking_id ),
			'ticket_id' => absint( $EM_Ticket_Booking->ticket_id ),
			'status' => $status === null ? null : absint( $status ),
			'status_text' => class_exists( '\EM_Pro\Attendance\Attendance' ) ? \EM_Pro\Attendance\Attendance::get_status_text( $EM_Ticket_Booking ) : null,
			'timestamp' => class_exists( '\EM_Pro\Attendance\Attendance' ) ? static::prepare_datetime( \EM_Pro\Attendance\Attendance::get_status_timestamp( $EM_Ticket_Booking ) ) : null,
			'history' => class_exists( '\EM_Pro\Attendance\Attendance' ) ? static::prepare_attendance_history( \EM_Pro\Attendance\Attendance::get_history( $EM_Ticket_Booking ) ) : array(),
		);
		return apply_filters( 'emp_api_prepare_attendance', $api, $EM_Ticket_Booking );
	}

	protected static function get_booking( $id ) {
		$EM_Booking = em_get_booking( $id );
		if ( !$EM_Booking || !$EM_Booking->booking_id ) {
			return Utils::error( 'emp_api_booking_not_found', __( 'Booking not found.', 'em-pro' ), 404 );
		}
		return $EM_Booking;
	}

	protected static function can_read_booking( $EM_Booking ) {
		if ( $EM_Booking->can_manage( 'manage_bookings', 'manage_others_bookings' ) ) return true;
		return is_user_logged_in() && absint( $EM_Booking->person_id ) === get_current_user_id();
	}

	protected static function can_update_rsvp( $EM_Booking ) {
		if ( $EM_Booking->can_manage( 'manage_bookings', 'manage_others_bookings' ) ) return true;
		return is_user_logged_in() && absint( $EM_Booking->person_id ) === get_current_user_id() && method_exists( $EM_Booking, 'can_change_rsvp' ) && $EM_Booking->can_change_rsvp();
	}

	public static function prepare_rsvp( $EM_Booking ) {
		$statuses = array();
		foreach ( \EM_Booking::get_rsvp_statuses() as $status => $status_data ) {
			$statuses[] = array(
				'status' => $status === '' ? null : absint( $status ),
				'label' => $status_data->label,
				'action' => $status_data->action,
				'label_action' => $status_data->label_action,
			);
		}
		$status = $EM_Booking->get_rsvp_status();
		$api = array(
			'id' => absint( $EM_Booking->booking_id ),
			'uuid' => $EM_Booking->booking_uuid,
			'status' => $status === null || $status === '' ? null : absint( $status ),
			'status_text' => $EM_Booking->get_rsvp_status( true ),
			'statuses' => $statuses,
			'permissions' => array(
				'edit' => static::can_update_rsvp( $EM_Booking ),
			),
		);
		return apply_filters( 'emp_api_prepare_rsvp', $api, $EM_Booking );
	}

	protected static function prepare_date( $value ) {
		return empty( $value ) || $value === '0000-00-00' ? null : $value;
	}

	protected static function prepare_datetime( $value ) {
		if ( empty( $value ) || $value === '0000-00-00 00:00:00' ) {
			return null;
		}
		$timestamp = strtotime( $value );
		return $timestamp ? gmdate( 'c', $timestamp ) : $value;
	}

	protected static function prepare_attendance_history( $history ) {
		if ( !is_array( $history ) ) return array();
		foreach ( $history as $key => $item ) {
			if ( isset( $item['status'] ) ) {
				$history[ $key ]['status'] = absint( $item['status'] );
			}
			if ( empty( $item['timestamp'] ) && !empty( $item['datetime'] ) ) {
				$timestamp = strtotime( $item['datetime'] );
				$history[ $key ]['timestamp'] = $timestamp ? gmdate( 'c', $timestamp ) : null;
			}
		}
		return $history;
	}
}
