<?php
/**
 * Fast Events export and import controller.
 *
 * @since 1.3
 */

declare( strict_types=1 );
namespace FeData\FastEvents\RestAPI;

defined( 'ABSPATH' ) || exit;

use FeData\FastEvents\Helpers\AuthHelper;
use FeData\FastEvents\Helpers\SeatsHelper;
use FeData\FastEvents\Utilities\CheckHtml;
use FeData\FastEvents\Utilities\JsonUtil;
use FeData\FastEvents\Utilities\UniqIds;
use stdClass;
use WP_Error;
use WP_Http;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use ZipArchive;

/**
 * Fast Events export and import class.
 */
class ExportImportController {

	private const NAMESPACE    = 'fast-events/v1';
	private const EVENTS_ROUTE = '/admin/events/';

	/**
	 * Fast Events export and import constructor.
	 *
	 * @param AuthHelper $auth Auth helper.
	 *
	 * @since 1.3
	 */
	public function __construct( private readonly AuthHelper $auth ) {}


	/**
	 * Register the routes for the objects of the controller.
	 *
	 * @since 1.3
	 */
	public function register_routes(): void {

		// Export selected events, media and pages.
		register_rest_route(
			self::NAMESPACE,
			self::EVENTS_ROUTE . 'export',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'export_events' ),
				'permission_callback' => function () {
					return $this->auth->authorize( $this->auth::EVENT_EXPORT );
				},
				'args'                => array(
					'event_ids' => array(
						'required'    => true,
						'type'        => 'array',
						'uniqueItems' => true,
						'items'       => array(
							'type' => 'integer',
						),
					),
					'media_ids' => array(
						'required'    => true,
						'type'        => 'array',
						'uniqueItems' => true,
						'items'       => array(
							'type' => 'integer',
						),
					),
					'pages_ids' => array(
						'required'    => true,
						'type'        => 'array',
						'uniqueItems' => true,
						'items'       => array(
							'type' => 'integer',
						),
					),
				),
			)
		);

		// Import events, media and pages.
		register_rest_route(
			self::NAMESPACE,
			self::EVENTS_ROUTE . 'import',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'import_events' ),
				'permission_callback' => function () {
					return $this->auth->authorize( $this->auth::EVENT_IMPORT );
				},
				'args'                => array(
					'overwrite_media' => array(
						'required' => true,
						'type'     => 'boolean',
					),
					'overwrite_pages' => array(
						'required' => true,
						'type'     => 'boolean',
					),
				),
			)
		);
	}


	/**
	 * Export selected events, media posts, and pages
	 *
	 * @since 1.0
	 * @since 1.4   Event authorization restriction
	 * @since 2.3.0 Use fast_events_json_encode.
	 *
	 * @param WP_REST_Request $request Request instance.
	 *
	 * @return WP_Error
	 */
	public function export_events( WP_REST_Request $request ): WP_Error {
		global $wpdb;

		if ( ! class_exists( 'ZipArchive' ) ) {
			return new WP_Error( 'failed', __( 'Unable to generate download file. ZipArchive not available.', 'fast-events' ), array( 'status' => 404 ) );
		}

		// Check if events are available.
		if ( ! isset( $request['event_ids'] ) ) {
			return new WP_Error( 'not_found', __( 'No events selected', 'fast-events' ), array( 'status' => 404 ) );
		}
		$event_ids = array_map( 'absint', $request['event_ids'] );

		// Check the destination event(s) authorization.
		foreach ( $event_ids as $event_id ) {
			$resp = $this->auth->check_event_authorization( $event_id );
			if ( is_wp_error( $resp ) ) {
				return $resp;
			}
		}

		// Look up the events.
		$json         = new stdClass();
		$json->home   = home_url();
		$json->events = array();
		$json->seats  = array();
		if ( ! empty( $event_ids ) ) {
			$json->events = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT * FROM {$wpdb->prefix}fe_events WHERE id IN (" . implode( ',', $event_ids ) . ')' // phpcs:ignore
				)
			);
			if ( empty( $json->events ) ) {
				return new WP_Error( 'not_found', __( 'Event(s) not found', 'fast-events' ), array( 'status' => 404 ) );
			}

			// Look up the seats.
			$json->seats = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT * FROM {$wpdb->prefix}fe_event_seats WHERE event_id IN (" . implode( ',', $event_ids ) . ')' // phpcs:ignore
				)
			);
			if ( empty( $json->seats ) ) {
				return new WP_Error( 'not_found', __( 'Seats not found', 'fast-events' ), array( 'status' => 404 ) );
			}
		}

		// Create the zip file.
		$file = get_temp_dir() . UniqIds::uniq_field() . '.zip';
		$zip  = new ZipArchive();
		if ( $zip->open( $file, ZipArchive::CREATE ) !== true ) {
			return new WP_Error( 'cant_create', __( 'Can\'t create zip-file', 'fast-events' ), array( 'status' => 404 ) );
		}

		// Add media.
		$json->media = array();
		if ( ! empty( $request['media_ids'] ) ) {
			$json->media = $wpdb->get_results(
				"select id, post_title, post_mime_type from $wpdb->posts where post_type = 'attachment' AND id IN (" .
				implode( ',', array_map( 'absint', $request['media_ids'] ) ) . ')' // phpcs:ignore
			);
			foreach ( $json->media as $key => $value ) {
				$media_file = get_attached_file( $value->id );
				if ( false === $media_file ) {
					return new WP_Error( 'not_found', __( 'Media file not found, id ', 'fast-events' ) . $value->id, array( 'status' => 404 ) );
				}
				$json->media[ $key ]->url = wp_get_attachment_url( $value->id );
				if ( false === $json->media[ $key ]->url ) {
					return new WP_Error( 'not_found', __( 'Media URL not found: ', 'fast-events' ) . $media_file, array( 'status' => 404 ) );
				}
				if ( $zip->addFile( $media_file, wp_basename( $media_file ) ) !== true ) {
					return new WP_Error( 'cant_add', __( 'Can\'t add media to zip-file: ', 'fast-events' ) . ( $media_file ), array( 'status' => 404 ) );
				}
			}
		}

		// Add pages.
		$json->pages = array();
		if ( ! empty( $request['pages_ids'] ) ) {
			$json->pages = $wpdb->get_results(
				"select id, post_name, post_title, post_content from $wpdb->posts where post_type = 'page' AND id IN (" .
				implode( ',', array_map( 'absint', $request['pages_ids'] ) ) . ')' // phpcs:ignore
			);
			foreach ( $json->pages as $key => $value ) {
				$json->pages[ $key ]->url = get_page_link( $value->id );
			}
		}

		// Add JSON to the file.
		$zip->addFromString( 'events.json', JsonUtil::fast_events_json_encode( $json ) );
		$zip->close();

		header( 'Content-Type: application/octet-stream' );
		header( 'Content-Transfer-Encoding: Binary' );
		header( 'Content-disposition: attachment; filename="fe-events.zip"' );
		readfile( $file ); // phpcs:ignore
		wp_delete_file( $file );
		die();
	}


	/**
	 * Import events, media posts, and pages
	 *
	 * @since 1.0
	 *
	 * @param WP_REST_Request $request Request instance.
	 *
	 * @return bool|WP_Error|WP_REST_Response|stdClass
	 */
	public function import_events( WP_REST_Request $request ): bool|WP_Error|WP_REST_Response|stdClass {
		global $wp_filesystem;
		global $wpdb;

		require_once ABSPATH . 'wp-admin/includes/image.php';
		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
		require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';

		WP_Filesystem();
		$tmp = get_temp_dir();

		if ( isset( $_FILES['file']['tmp_name'] ) && empty( $_FILES['file']['error'] ) ) { // phpcs:ignore

			// Read the file and unzip it.
			$fname = sanitize_text_field( wp_unslash( $_FILES['file']['tmp_name'] ) ); // phpcs:ignore
			if ( unzip_file( $fname, $tmp ) !== true ) {
				return new WP_Error( 'cant_unzip', __( 'Can\'t unzip file', 'fast-events' ), array( 'status' => 404 ) );
			}

			// Remove temp file.
			wp_delete_file( $fname );

			// Read JSON file.
			$json = json_decode( $wp_filesystem->get_contents( $tmp . 'events.json' ) );
			// Check for errors.
			if ( null === $json && JSON_ERROR_NONE !== json_last_error() ) {
				$error_data = array(
					'status'             => 400,
					'json_error_code'    => json_last_error(),
					'json_error_message' => json_last_error_msg(),
				);
				return new WP_Error( 'invalid_json', 'events.json: ' . __( 'Invalid JSON string', 'fast-events' ), $error_data );
			}

			// Validate against JSON schema's.
			$result = $this->fast_events_validate_schema( $json );
			if ( is_wp_error( $result ) ) {
				return $result;
			}

			// Set overwrite flags.
			$json->media_overwrite = $request['overwrite_media'];
			$json->page_overwrite  = $request['overwrite_pages'];

			// Insert media, if any available.
			if ( ! empty( $json->media ) ) {
				$wp_upload_dir = wp_upload_dir();
				foreach ( $json->media as $key => $value ) {
					$basename = wp_basename( $value->url );
					$meta     = $wpdb->get_row(
						$wpdb->prepare(
							"SELECT post_id, meta_value from $wpdb->postmeta where SUBSTRING(meta_value,%d,%d)=%s",
							-strlen( $basename ),
							strlen( $basename ),
							$basename
						)
					);

					// Can we overwrite the existing media?
					if ( $json->media_overwrite && ! empty( $meta ) ) {
						copy( $tmp . wp_basename( $value->url ), $wp_upload_dir['basedir'] . '/' . $meta->meta_value );

						// Save the new media id and guid.
						$json->media[ $key ]->new_id  = $meta->post_id;
						$json->media[ $key ]->new_url = wp_get_attachment_url( $meta->post_id );
					} else {
						$upload_file = $this->fast_events_get_media_filename( $wp_upload_dir['path'] . '/' . wp_basename( $value->url ) );
						copy( $tmp . wp_basename( $value->url ), $upload_file );
						$media_id = wp_insert_attachment(
							array(
								'guid'           => $wp_upload_dir['url'] . '/' . wp_basename( $upload_file ),
								'post_mime_type' => $value->post_mime_type,
								'post_title'     => $value->post_title,
								'post_content'   => '',
								'post_status'    => 'inherit',
							),
							$upload_file
						);
						if ( is_wp_error( $media_id ) ) {
							return new WP_Error( 'cant_add', __( 'Can\'t add new media: ', 'fast-events' ) . ( wp_basename( $value->guid ) ), array( 'status' => 404 ) );
						} else {

							// Save the new media id and guid.
							$json->media[ $key ]->new_id  = $media_id;
							$json->media[ $key ]->new_url = wp_get_attachment_url( $media_id );
						}
						wp_update_attachment_metadata( $media_id, wp_generate_attachment_metadata( $media_id, $upload_file ) );
					}
				}
			}

			// Insert pages, if any available.
			if ( ! empty( $json->pages ) ) {
				foreach ( $json->pages as $key => $value ) {

					// Update all media links first, if any.
					if ( ! empty( $json->media ) ) {
						foreach ( $json->media as $media ) {
							$value->post_content = str_replace( $media->url, $media->new_url, $value->post_content );
						}
					}

					// Check for existing post.
					$existing_post = get_page_by_path( $value->post_name );
					if ( $json->page_overwrite && ! empty( $existing_post ) ) {

						// Update the page.
						wp_update_post(
							array(
								'ID'           => $existing_post->ID,
								'post_content' => $value->post_content,
							)
						);

						// Save the new page id and page link.
						$json->pages[ $key ]->new_id  = $existing_post->ID;
						$json->pages[ $key ]->new_url = get_page_link( $existing_post->ID );
					} else {
						$page_id = wp_insert_post(
							array(
								'post_title'   => wp_strip_all_tags( $value->post_title ),
								'post_content' => $value->post_content,
								'post_status'  => 'publish',
								'post_type'    => 'page',
								'post_name'    => $value->post_name,
							)
						);
						if ( is_wp_error( $page_id ) ) {
							return new WP_Error( 'cant_add', __( 'Can\'t add new page: ', 'fast-events' ) . ( $value->post_title ), array( 'status' => 404 ) );
						} else {

							// Save the new page id and page link.
							$json->pages[ $key ]->new_id  = $page_id;
							$json->pages[ $key ]->new_url = get_page_link( $page_id );
						}
					}
				}
			}

			// Insert events and seats.
			$wpdb->query( 'START TRANSACTION' );

			$result = $this->fast_events_insert_events_and_seats( $json );
			if ( is_wp_error( $result ) ) {
				$wpdb->query( 'ROLLBACK' );

				return $result;
			}

			// Commit the transaction.
			$wpdb->query( 'COMMIT' );

			// Update cross-links and shortcodes in pages.
			if ( ! empty( $json->pages ) ) {
				$this->fast_events_update_links_shortcodes( $json );
			}

			return rest_ensure_response( __( 'Imported new event(s) successfully', 'fast-events' ) );

		} else {
			return new WP_Error( 'file_error', __( 'No file found for upload', 'fast-events' ), array( 'status' => 409 ) );
		}
	}


	/**
	 * Validate the input JSON against schema's
	 *
	 * @since 1.0
	 *
	 * @param stdClass $json JSON structure of events, seats, media, and pages.
	 *
	 * @return bool|WP_Error|stdClass
	 */
	private function fast_events_validate_schema( stdClass &$json ): bool|WP_Error|stdClass {

		// Validate and sanitize import json-file.
		require_once FAST_EVENTS_ABSPATH . 'schema/fast-events-import.php';
		$result = rest_validate_value_from_schema( $json, fast_events_schema_for_import(), 'events.json: ' );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		// Check the integrity of pdf_info, input_fields and scan_keys.
		foreach ( $json->events as $event_key => $event_value ) {
			$result = $this->fast_events_check_json_fields( $event_key, $json );
			if ( is_wp_error( $result ) ) {
				return $result;
			}

			// Check for malformed dates.
			if ( false === mysql2date( 'Y-m-d H:i:s', $event_value->start_date ) ||
				false === mysql2date( 'Y-m-d H:i:s', $event_value->end_date ) ||
				( 1 === absint( $event_value->event_type ) && false === mysql2date( 'Y-m-d H:i:s', $event_value->event_date ) ) ) {
				return new WP_Error( 'failed', __( 'Wrong date format', 'fast-events' ), array( 'status' => 400 ) );
			}

			// Check for malformed email address.
			if ( ( ! empty( $event_value->email_bcc ) && ! filter_var( $event_value->email_bcc, FILTER_VALIDATE_EMAIL ) ) ||
				( ! empty( $event_value->confirm_bcc ) && ! filter_var( $event_value->confirm_bcc, FILTER_VALIDATE_EMAIL ) ) ) {
				return new WP_Error( 'failed', __( 'Illegal email address', 'fast-events' ), array( 'status' => 400 ) );
			}

			// Check redirect URL.
			if ( ! empty( $event_value->confirm_redirect ) && ! wp_http_validate_url( $event_value->confirm_redirect ) ) {
				return new WP_Error( 'failed', __( 'Illegal confirm redirect URL', 'fast-events' ), array( 'status' => 400 ) );
			}
		}

		return true;
	}


	/**
	 * Process new events and seats from upload
	 *
	 * @since 1.0
	 * @since 1.4   Added saas_fee, saas_user_id, sender_name and sender_email to query
	 * @since 2.3.0 Use fast_events_json_encode.
	 *
	 * @param stdClass $json JSON structure of events, seats, media, and pages.
	 *
	 * @return WP_Error|bool
	 */
	private function fast_events_insert_events_and_seats( stdClass &$json ): bool|WP_Error {
		global $wpdb;
		global $fevt_container;

		// Boot the HTML Purifier.
		$purifier = CheckHtml::purify();

		$event_table = $wpdb->prefix . 'fe_events';

		// Process all events.
		foreach ( $json->events as $key => $value ) {

			// Substitute media URL's in emails.
			if ( ! empty( $json->media ) ) {
				$pdf = null;
				foreach ( $json->media as $item ) {
					if ( isset( $json->events[ $key ]->email_body ) && isset( $json->events[ $key ]->confirm_body ) ) {
						$json->events[ $key ]->email_body   = str_replace( $item->url, $item->new_url, $json->events[ $key ]->email_body );
						$json->events[ $key ]->confirm_body = str_replace( $item->url, $item->new_url, $json->events[ $key ]->confirm_body );
					}

					// Check ticket-template and invoice-template.
					if ( isset( $value->pdf_info ) ) {
						$pdf = json_decode( $value->pdf_info );
						if ( isset( $pdf->ticket->id ) && isset( $pdf->invoice->id ) ) {
							if ( (int) $pdf->ticket->id === (int) $item->id ) {
								$pdf->ticket->id = $item->new_id;
							}
							if ( (int) $pdf->invoice->id === (int) $item->id ) {
								$pdf->invoice->id = $item->new_id;
							}
						}

						// Check other ticket types.
						foreach ( $pdf->ticket->types as $ticket_key => $ticket_value ) {
							if ( (int) $ticket_value === (int) $item->id ) {
								$pdf->ticket->types->{ $ticket_key } = $item->new_id;
							}
						}

						// Save the new PDF info.
						$json->events[ $key ]->pdf_info = JsonUtil::fast_events_json_encode( $pdf );
					}
				}
			}

			// Substitute page URL's and other fields in emails.
			if ( ! empty( $json->pages ) ) {
				foreach ( $json->pages as $item ) {
					if ( isset( $json->events[ $key ]->email_body ) && isset( $json->events[ $key ]->confirm_body ) ) {
						$json->events[ $key ]->email_body   = str_replace( $item->url, $item->new_url, $json->events[ $key ]->email_body );
						$json->events[ $key ]->confirm_body = str_replace( $item->url, $item->new_url, $json->events[ $key ]->confirm_body );
					}

					// Change terms.
					if ( isset( $json->events[ $key ]->terms ) ) {
						if ( ! empty( $json->events[ $key ]->terms ) ) {
							$json->events[ $key ]->terms = str_replace( $item->url, $item->new_url, $json->events[ $key ]->terms );
						}
					}

					// Change the redirect field.
					if ( isset( $json->events[ $key ]->redirect ) ) {
						if ( ! empty( $json->events[ $key ]->redirect ) ) {
							$json->events[ $key ]->redirect = str_replace( $item->url, $item->new_url, $json->events[ $key ]->redirect );
						}
					}

					// Change confirm redirect field.
					if ( isset( $json->events[ $key ]->confirm_redirect ) ) {
						if ( ! empty( $json->events[ $key ]->confirm_redirect ) ) {
							$json->events[ $key ]->confirm_redirect = str_replace( $item->url, $item->new_url, $json->events[ $key ]->confirm_redirect );
						}
					}
				}
			}

			// Insert event in DB.
			$seats_needed = (int) $value->seats_needed;
			$event_type   = (int) $value->event_type;
			$values       = array(
				'event_name'                  => sanitize_text_field( $value->event_name ),
				'event_date'                  => ( 0 === $event_type ) ? null : ( '' === $value->event_date ? gmdate( 'Y-m-d H:i' ) : sanitize_text_field( $value->event_date ) ),
				'event_date_format'           => sanitize_text_field( $value->event_date_format ?? 'Y-m-d H:i' ),
				'event_type'                  => $event_type,
				'start_date'                  => sanitize_text_field( $value->start_date ),
				'end_date'                    => sanitize_text_field( $value->end_date ),
				'stock'                       => (int) $value->stock,
				'stock_link'                  => (int) $value->stock_link,
				'saas_fee'                    => ! empty( $value->saas_fee ) ? number_format( (float) $value->saas_fee, 2, '.', '' ) : null,
				'saas_user_id'                => (int) $value->saas_user_id,
				'group_type'                  => (int) $value->group_type,
				'event_group'                 => sanitize_text_field( $value->event_group ),
				'emails_needed'               => (int) $value->emails_needed,
				'tickets_needed'              => (int) $value->tickets_needed,
				'invoice_needed'              => (int) $value->invoice_needed,
				'unique_users_needed'         => (int) $value->unique_users_needed,
				'user_groups_needed'          => (int) $value->user_groups_needed,
				'recaptcha_needed'            => (int) $value->recaptcha_needed,
				'confirmation_emails_needed'  => (int) $value->confirmation_emails_needed,
				'confirmation_timeout'        => (int) $value->confirmation_timeout,
				'add_dashboard_orders_needed' => (int) $value->add_dashboard_orders_needed,
				'test_payments_needed'        => (int) $value->test_payments_needed,
				'seats_needed'                => $seats_needed,
				'webhooks_needed'             => (int) $value->webhooks_needed,
				'reload_on_exit_needed'       => (int) $value->reload_on_exit_needed,
				'tracking_needed'             => (int) $value->tracking_needed,
				'redirect'                    => sanitize_text_field( $value->redirect ),

				'sender_name'                 => sanitize_text_field( $value->sender_name ),
				'sender_email'                => sanitize_email( $value->sender_email ),
				'email_subject'               => sanitize_text_field( $value->email_subject ),
				'email_bcc'                   => sanitize_email( $value->email_bcc ),
				'email_body'                  => $purifier->purify( $value->email_body ),

				'confirm_redirect'            => sanitize_text_field( $value->confirm_redirect ),
				'confirm_subject'             => sanitize_text_field( $value->confirm_subject ),
				'confirm_bcc'                 => sanitize_email( $value->confirm_bcc ),
				'confirm_body'                => $purifier->purify( $value->confirm_body ),

				'terms'                       => ( is_null( $value->terms ) ? '' : strip_tags( $value->terms, array( 'a', 'b', 'i', 'u' ) ) ),
				'pdf_info'                    => $value->pdf_info,
				'input_fields'                => $value->input_fields,
				'scan_keys'                   => $value->scan_keys,
				'tracking_info'               => $value->tracking_info,
			);

			// In sert the event and save new id.
			$result = $wpdb->insert( $event_table, $values );
			if ( false === $result ) {
				return new WP_Error( 'failed', __( 'Insert event failed', 'fast-events' ) . ': ' . $wpdb->last_error, array( 'status' => 400 ) );
			}
			$json->events[ $key ]->new_id = $wpdb->insert_id;

			// Insert corresponding seats.
			$result = $this->fast_events_insert_seats_from_upload( $json, (int) $value->id, $json->events[ $key ]->new_id, $seats_needed );
			if ( is_wp_error( $result ) ) {
				return $result;
			}
		}

		// Now that all events have been created, we need to check for the id's of linked_events, event_ids in the user_group
		// and linked_events in seats. And change them to the new id's if there is a match.
		foreach ( $json->events as $item ) {
			$values = array();

			// Linked stock.
			if ( 0 !== absint( $item->stock_link ) ) {
				$key = array_search( (string) $item->stock_link, array_column( $json->events, 'id' ), true );
				if ( false !== $key ) {
					$values += array( 'stock_link' => $json->events[ $key ]->new_id );
				}
			}

			// Linked user group.
			$inp = json_decode( $item->input_fields );
			if ( 0 !== absint( $inp->user_group->eventid || ! empty( $inp->unique_users->master_eventids ) ) ) {

				// Link to another user_group?
				if ( 0 !== absint( $inp->user_group->eventid ) ) {
					$key = array_search( (string) $inp->user_group->eventid, array_column( $json->events, 'id' ), true );
					if ( false !== $key ) {
						$inp->user_group->eventid = (string) $json->events[ $key ]->new_id;
					}
				}

				// Unique events in user groups?
				if ( ! empty( $inp->unique_users->master_eventids ) ) {
					$arr     = explode( ',', $inp->unique_users->master_eventids );
					$new_arr = array();
					foreach ( $arr as $event_id ) {
						$key = array_search( $event_id, array_column( $json->events, 'id' ), true );
						if ( false !== $key ) {
							$new_arr[] = (string) $json->events[ $key ]->new_id;
						} else {
							$new_arr[] = $event_id;
						}
					}
					$inp->unique_users->master_eventids = implode( ',', $new_arr );
				}
				$values += array( 'input_fields' => JsonUtil::fast_events_json_encode( $inp ) );
			}

			// Update event if needed.
			if ( ! empty( $values ) ) {
				$result = $wpdb->update( $event_table, $values, array( 'id' => $item->new_id ) );
				if ( false === $result ) {
					return new WP_Error( 'failed', __( 'Update event failed', 'fast-events' ) . ': ' . $wpdb->last_error, array( 'status' => 400 ) );
				}
			}

			// Update seats if there is a linked event.
			$key = array_search( (string) $item->id, array_column( $json->seats, 'event_id' ), true );
			if ( 0 !== absint( $json->seats[ $key ]->linked_event ) ) {
				$key_seats = array_search( (string) $json->seats[ $key ]->linked_event, array_column( $json->events, 'id' ), true );
				if ( false !== $key_seats ) {
					$resp = $fevt_container->get( SeatsHelper::class )->update_entry_for_event(
						absint( $item->new_id ),
						absint( $json->seats[ $key ]->num_seats ?? 0 ),
						sanitize_text_field( $json->seats[ $key ]->seat_format ?? '' ),
						sanitize_text_field( $json->seats[ $key ]->seat_config ?? '' ),
						absint( $json->events[ $key_seats ]->new_id ),
					);
					if ( is_wp_error( $resp ) ) {
						return $resp;
					}
				}
			}
		}
		return true;
	}


	/**
	 * Check seats info and insert
	 *
	 * @since 1.0
	 *
	 * @param stdClass $json           JSON structure of events, seats, media, and pages.
	 * @param int      $event_id       The id of the imported event.
	 * @param int      $new_id         The id of the newly inserted event.
	 * @param int      $seats_needed   Seats needed (0=Np, 1=Yes).
	 *
	 * @return WP_Error|bool
	 */
	private function fast_events_insert_seats_from_upload( stdClass &$json, int $event_id, int $new_id, int $seats_needed ): bool|WP_Error {
		global $fevt_container;

		// Look up the right seats id.
		$key = array_search( (string) $event_id, array_column( $json->seats, 'event_id' ), true );
		if ( false === $key ) {
			return new WP_Error( 'no_seats', __( 'Corresponding seats not found', 'fast-events' ), array( 'status' => 404 ) );
		}

		// If we use seats, validate the configuration.
		if ( 1 === $seats_needed ) {
			$resp = $fevt_container->get( SeatsHelper::class )->validate_config(
				absint( $json->seats[ $key ]->num_seats ?? 0 ),
				sanitize_text_field( $json->seats[ $key ]->seat_format ?? '' ),
				sanitize_text_field( $json->seats[ $key ]->seat_config ?? '' ),
			);
			if ( is_wp_error( $resp ) ) {
				return $resp;
			}
		}

		// Insert the seats row for this event.
		$resp = $fevt_container->get( SeatsHelper::class )->create_entry_for_event(
			$new_id,
			absint( $json->seats[ $key ]->num_seats ?? 0 ),
			sanitize_text_field( $json->seats[ $key ]->seat_format ?? '' ),
			sanitize_text_field( $json->seats[ $key ]->seat_config ?? '' ),
			absint( $json->seats[ $key ]->linked_event ?? 0 ),
		);
		if ( is_wp_error( $resp ) ) {
			return $resp;
		}

		return true;
	}


	/**
	 * Update links in pages and fast_events shortcodes.
	 *
	 * @since 1.0
	 *
	 * @param stdClass $json JSON structure of events, seats, media, and pages.
	 */
	private function fast_events_update_links_shortcodes( stdClass &$json ): void {
		foreach ( $json->pages as $item ) {
			$post = get_post( $item->new_id );

			// Update other page links.
			foreach ( $json->pages as $page ) {
				$post->post_content = str_replace( $page->url, $page->new_url, $post->post_content );
			}

			// Update shortcodes.
			if ( has_shortcode( $post->post_content, 'fast_events' ) ) {
				preg_match_all( '/\[\s*fast_events\s+id=\d+\s*]/', $post->post_content, $matches, PREG_SET_ORDER );
				foreach ( $matches as $match ) {
					preg_match( '/\d+/', $match[0], $events );
					$key = array_search( $events[0], array_column( $json->events, 'id' ), true );
					if ( false !== $key ) {
						$post->post_content = str_replace( $match[0], '[fast_events id=' . $json->events[ $key ]->new_id . ']', $post->post_content );
					}
				}
			}

			// Update the page.
			wp_update_post(
				array(
					'ID'           => absint( $item->new_id ),
					'post_content' => $post->post_content,
				)
			);
		}
	}


	/**
	 * Get a unique media filename if there is already a media file with the same name.
	 *
	 * @since 1.0
	 *
	 * @param string $filename  Full path of filename.
	 *
	 * @return string
	 */
	private function fast_events_get_media_filename( string $filename ): string {
		$count  = 2;
		$search = '.';

		// Check if the file exists.
		while ( file_exists( $filename ) ) {
			$pos = strrpos( $filename, $search );
			if ( false !== $pos ) {
				$replace  = '-' . $count . '.';
				$filename = substr_replace( $filename, $replace, $pos, strlen( $search ) );
				$search   = $replace;
				++$count;
			}
		}
		return $filename;
	}


	/**
	 * Check the integrity of pdf_info, input_fields, and scan_key fields in the JSON structure
	 *
	 * @since 1.0
	 * @since 2.3.0 Use fast_events_json_encode.
	 *
	 * @param int      $json_key  Key position in the original JSON structure.
	 * @param stdClass $json      JSON structure.
	 *
	 * @return bool|WP_Error|stdClass
	 */
	private function fast_events_check_json_fields( int $json_key, stdClass &$json ): bool|WP_Error|stdClass {
		$ticket_array = array();

		// Validate input_fields.
		require_once FAST_EVENTS_ABSPATH . 'schema/fast-events-input-fields.php';
		$input_fields = $this->fast_events_check_json_string( $json->events[ $json_key ]->input_fields, 'input_fields: ' );
		if ( is_wp_error( $input_fields ) ) {
			return $input_fields;
		}
		$result = rest_validate_value_from_schema( $input_fields, fast_events_schema_input_fields(), 'input_fields: ' );
		if ( is_wp_error( $result ) ) {
			return $result;
		}
		$input_fields = rest_sanitize_value_from_schema( $input_fields, fast_events_schema_input_fields() );

		// Validate pdf_info.
		require_once FAST_EVENTS_ABSPATH . 'schema/fast-events-pdf-info.php';
		$pdf_info = $this->fast_events_check_json_string( $json->events[ $json_key ]->pdf_info, 'pdf_info: ' );
		if ( is_wp_error( $pdf_info ) ) {
			return $pdf_info;
		}
		$result = rest_validate_value_from_schema( $pdf_info, fast_events_schema_pdf_info(), 'pdf_info: ' );
		if ( is_wp_error( $result ) ) {
			return $result;
		}
		$pdf_info = rest_sanitize_value_from_schema( $pdf_info, fast_events_schema_pdf_info() );

		// Validate scan_keys.
		require_once FAST_EVENTS_ABSPATH . 'schema/fast-events-scan-keys.php';
		$scan_keys = $this->fast_events_check_json_string( $json->events[ $json_key ]->scan_keys, 'scan_keys: ' );
		if ( is_wp_error( $scan_keys ) ) {
			return $scan_keys;
		}
		$result = rest_validate_value_from_schema( $scan_keys, fast_events_schema_scan_keys(), 'scan_keys: ' );
		if ( is_wp_error( $result ) ) {
			return $result;
		}
		$scan_keys = rest_sanitize_value_from_schema( $scan_keys, fast_events_schema_scan_keys() );

		// Get all sanitized ticket types.
		foreach ( $input_fields['tickets'] as &$ticket ) {
			$ticket['name'] = sanitize_text_field( $ticket['name'] );
			$ticket['sold'] = 0;
			if ( in_array( $ticket['name'], $ticket_array, true ) ) {
				return new WP_Error( 'duplicate', __( 'Duplicate ticket: ', 'fast-events' ) . $ticket->name, array( 'status' => 400 ) );
			}
			$ticket_array[] = $ticket['name'];
		}

		// Sanitize other parts of the input_fields.
		$input_fields['submit']['name'] = sanitize_text_field( $input_fields['submit']['name'] );
		foreach ( $input_fields['fields'] as &$field ) {
			$field['name']  = sanitize_text_field( $field['name'] );
			$field['value'] = sanitize_text_field( $field['value'] );
		}

		// Check the scan_keys for valid sanitised ticket types.
		foreach ( $scan_keys as &$scan ) {
			$scan['tickets']  = sanitize_text_field( $scan['tickets'] );
			$scan['location'] = sanitize_text_field( $scan['location'] );
			if ( ! empty( $scan['tickets'] ) ) {
				$items = explode( ',', $scan['tickets'] );
				foreach ( $items as $item ) {
					if ( ! in_array( $item, $ticket_array, true ) ) {
						return new WP_Error( 'invalid', __( 'Non-existing ticket in scan_keys: ', 'fast-events' ) . $item, array( 'status' => 400 ) );
					}
				}
			}
		}

		// Check pdf_info if all ticket types are present.
		foreach ( $ticket_array as $single_ticket ) {
			if ( ! isset( $pdf_info['ticket']['types'][ $single_ticket ] ) ) {
				return new WP_Error( 'invalid', __( 'Ticket-type does not exist in pdf_info: ', 'fast-events' ) . $single_ticket, array( 'status' => 400 ) );

			}
		}

		$json->events[ $json_key ]->input_fields = JsonUtil::fast_events_json_encode( $input_fields );
		$json->events[ $json_key ]->pdf_info     = JsonUtil::fast_events_json_encode( $pdf_info );
		$json->events[ $json_key ]->scan_keys    = JsonUtil::fast_events_json_encode( $scan_keys );

		return true;
	}


	/**
	 * Check the integrity of pdf_info, input_fields, and scan_key fields in the JSON structure
	 *
	 * @since 1.0
	 *
	 * @param string $json_string  JSON string.
	 * @param string $name         Name of JSON string.
	 *
	 * @return array|WP_Error
	 */
	private function fast_events_check_json_string( string $json_string, string $name ): WP_Error|array {

		$parsed_json = json_decode( $json_string, true );

		// Check for errors.
		if ( null === $parsed_json && JSON_ERROR_NONE !== json_last_error() ) {
			$error_data = array(
				'status'             => WP_Http::BAD_REQUEST,
				'json_error_code'    => json_last_error(),
				'json_error_message' => json_last_error_msg(),
			);
			return new WP_Error( 'invalid_json', $name . __( 'Invalid JSON string', 'fast-events' ), $error_data );
		}

		return $parsed_json;
	}
}
