<?php

use Tribe\Events\Event_Status\Event_Meta as Event_Status_Meta;

/**
 * Run schema updates on plugin activation or updates
 */
class Tribe__Events__Updater {
	protected $version_option = 'schema-version';
	protected $reset_version = '5.16.0'; // when a reset() is called, go to this version
	protected $current_version = 0;
	public $capabilities;

	public function __construct( $current_version ) {
		$this->current_version = $current_version;
	}


	/**
	 * We've had problems with the notoptions and
	 * alloptions caches getting out of sync with the DB,
	 * forcing an eternal update cycle
	 *
	 */
	protected function clear_option_caches() {
		wp_cache_delete( 'notoptions', 'options' );
		wp_cache_delete( 'alloptions', 'options' );
	}

	public function do_updates() {
		$this->clear_option_caches();
		$updates = $this->get_update_callbacks();
		uksort( $updates, 'version_compare' );

		try {
			foreach ( $updates as $version => $callback ) {

				if ( ! $this->is_new_install() && version_compare( $version, $this->current_version, '<=' ) && $this->is_version_in_db_less_than( $version ) ) {
					call_user_func( $callback );
				}
			}

			foreach ( $this->get_constant_update_callbacks() as $callback ) {
				call_user_func( $callback );
			}

			$this->update_version_option( $this->current_version );
		} catch ( Exception $e ) {
			// fail silently, but it should try again next time
		}
	}

	public function update_version_option( $new_version ) {
		Tribe__Settings_Manager::set_option( $this->version_option, $new_version );
	}

	/**
	 * Returns an array of callbacks with version strings as keys.
	 * Any key higher than the version recorded in the DB
	 * and lower than $this->current_version will have its
	 * callback called.
	 *
	 * This method has been deprecated in favor of a more testable public function
	 *
	 * @return array
	 * @deprecated 4.0
	 */
	protected function get_updates() {
		_deprecated_function( __METHOD__, '4.0', 'get_update_callbacks' );
		return $this->get_update_callbacks();
	}

	/**
	 * Getter for the private reset version.
	 * Mainly for tests.
	 *
	 * @since 6.0.1
	 *
	 * @return string The reset version number.
	 */
	public function get_reset_version(): string {
		return $this->reset_version;
	}

	/**
	 * Returns an array of callbacks with version strings as keys.
	 * Any key higher than the version recorded in the DB
	 * and lower than $this->current_version will have its
	 * callback called.
	 *
	 * @return array
	 */
	public function get_update_callbacks() {
		return [
			'2.0.1'  => [ $this, 'migrate_from_sp_events' ],
			'2.0.6'  => [ $this, 'migrate_from_sp_options' ],
			'3.10a4' => [ $this, 'set_enabled_views' ],
			'3.10a5' => [ $this, 'remove_30_min_eod_cutoffs' ],
			'4.2'    => [ $this, 'migrate_import_option' ],
			'4.6.23' => [ $this, 'migrate_wordpress_custom_field_option' ],
			'5.9.2'  => [ $this, 'migrate_event_status_reason_field' ],
		];
	}

	/**
	 * Returns an array of callbacks that should be called
	 * every time the version is updated
	 *
	 * @return array
	 */
	public function get_constant_update_callbacks() {
		return [
			[ $this, 'flush_rewrites' ],
			[ $this, 'set_capabilities' ],
		];
	}

	public function get_version_from_db() {
		return Tribe__Settings_Manager::get_option( $this->version_option );
	}

	/**
	 * Returns true if the version in the DB is less than the provided version
	 *
	 * @return boolean
	 */
	public function is_version_in_db_less_than( $version ) {
		$version_in_db = $this->get_version_from_db();

		return ( version_compare( $version, $version_in_db ) > 0 );
	}

	/**
	 * Returns true if this is a new install
	 *
	 * @return boolean
	 */
	public function is_new_install() {
		$version_in_db = $this->get_version_from_db();

		return empty( $version_in_db );
	}

	/**
	 * Returns true if an update is required
	 *
	 * @return boolean
	 */
	public function update_required() {
		return $this->is_version_in_db_less_than( $this->current_version );
	}

	public function migrate_from_sp_events() {
		$legacy_option = get_option( 'sp_events_calendar_options' );
		if ( empty( $legacy_option ) ) {
			return;
		}

		$new_option = get_option( Tribe__Events__Main::OPTIONNAME );
		if ( ! $new_option ) {
			update_option( Tribe__Events__Main::OPTIONNAME, $legacy_option );
		}
		delete_option( 'sp_events_calendar_options' );

		/** @var wpdb $wpdb */
		global $wpdb;
		$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type IN ( 'sp_events', 'sp_venue', 'sp_organizer' )" );
		if ( ! $count ) {
			return;
		}

		// update post type names
		$wpdb->update( $wpdb->posts, [ 'post_type' => Tribe__Events__Main::POSTTYPE ], [ 'post_type' => 'sp_events' ] );
		$wpdb->update( $wpdb->posts, [ 'post_type' => Tribe__Events__Main::VENUE_POST_TYPE ], [ 'post_type' => 'sp_venue' ] );
		$wpdb->update( $wpdb->posts, [ 'post_type' => Tribe__Events__Main::ORGANIZER_POST_TYPE ], [ 'post_type' => 'sp_organizer' ] );

		// update taxonomy names
		$wpdb->update( $wpdb->term_taxonomy, [ 'taxonomy' => Tribe__Events__Main::TAXONOMY ], [ 'taxonomy' => 'sp_events_cat' ] );
		wp_cache_flush();
	}

	public function migrate_from_sp_options() {
		$tec_options = Tribe__Settings_Manager::get_options();
		$option_names = [
			'spEventsTemplate'   => 'tribeEventsTemplate',
			'spEventsBeforeHTML' => 'tribeEventsBeforeHTML',
			'spEventsAfterHTML'  => 'tribeEventsAfterHTML',
		];
		foreach ( $option_names as $old_name => $new_name ) {
			if ( isset( $tec_options[ $old_name ] ) && empty( $tec_options[ $new_name ] ) ) {
				$tec_options[ $new_name ] = $tec_options[ $old_name ];
				unset( $tec_options[ $old_name ] );
			}
		}
		Tribe__Settings_Manager::set_options( $tec_options );
	}

	public function flush_rewrites() {
		// run after 'init' to ensure that all CPTs are registered
		add_action( 'wp_loaded', 'flush_rewrite_rules' );
	}

	/**
	 * Set the Capabilities for Events and Related Post Types.
	 *
	 * @since 5.1.1 - change method of calling set_capabilities.
	 */
	public function set_capabilities() {
		// @var Tribe__Events__Capabilities $capabilities
		$capabilities = tribe( Tribe__Events__Capabilities::class );

		// We need to set the requirement on update to allow the next page load to trigger
		// only when the current request fails before `wp_loaded`, dont run `set_initial_caps` here.
		$capabilities->set_needs_init();

		add_action( 'wp_loaded', [ $capabilities, 'set_initial_caps' ], 10, 0 );
		add_action( 'wp_loaded', [ $this, 'reload_current_user' ], 11, 0 );
	}

	/**
	 * Reset the $current_user global after capabilities have been changed
	 *
	 */
	public function reload_current_user() {
		global $current_user;
		if ( isset( $current_user ) && ( $current_user instanceof WP_User ) ) {
			$id = $current_user->ID;
			$current_user = null;
			wp_set_current_user( $id );
		}
	}

	/**
	 * Reset update flags. All updates past $this->reset_version will
	 * run again on the next page load
	 *
	 */
	public function reset() {
		$this->update_version_option( $this->reset_version );
	}

	/**
	 * Make sure the tribeEnableViews option is always set
	 *
	 */
	public function set_enabled_views() {
		$enabled_views = tribe_get_option( 'tribeEnableViews', null );
		if ( $enabled_views == null ) {
			$views = wp_list_pluck( apply_filters( 'tribe-events-bar-views', [] ), 'displaying' );
			tribe_update_option( 'tribeEnableViews', $views );
		}
	}

	/**
	 * Bump the :30 min EOD cutoff option to the next full hour
	 *
	 */
	public function remove_30_min_eod_cutoffs() {
		$eod_cutoff = tribe_end_of_day();
		if ( Tribe__Date_Utils::minutes_only( $eod_cutoff ) == '29' ) {
			$eod_cutoff = date_create( '@' . ( strtotime( $eod_cutoff ) + 1 ) );
			$eod_cutoff->modify( '+30 minutes' );
			tribe_update_option( 'multiDayCutoff', $eod_cutoff->format( 'h:i' ) );
		}
	}

	/**
	 * Migrate the previous import mapping to the new naming and cleanup
	 * the old.
	 */
	public function migrate_import_option() {
		$legacy_option = get_option( 'tribe_events_import_column_mapping' );
		$type = get_option( 'tribe_events_import_type' );
		if ( empty( $legacy_option ) || empty( $type ) ) {
			return;
		}

		update_option( 'tribe_events_import_column_mapping_' . $type, $legacy_option );
		delete_option( 'tribe_events_import_column_mapping' );
	}

	/**
	 * Update WordPress Custom Field Setting moved from Pro
	 * only update setting if show|hide
	 *
	 * @since 4.6.23
	 */
	public function migrate_wordpress_custom_field_option() {
		$show_box = tribe_get_option( 'disable_metabox_custom_fields' );
		if ( 'show' === $show_box ) {
			tribe_update_option( 'disable_metabox_custom_fields', true );
		} elseif ( 'hide' === $show_box ) {
			tribe_update_option( 'disable_metabox_custom_fields', false );
		}
	}

	/**
	 * Update Event Status reason field from extension to a central field for both.
	 *
	 * @since 5.11.0
	 */
	public function migrate_event_status_reason_field() {
		$args = [
			'posts_per_page' => 500,
			'meta_query' => [
				[
					'relation' => 'OR',
					[
						'key'     => Event_Status_Meta::$key_control_status,
						'value'   => [ 'canceled', 'postponed' ],
						'compare' => 'IN',
					],
				],
			],
		];

		$events = tribe_events()->by_args( $args )->get_ids();

		foreach ( $events as $event_id ) {
			$event = tribe_get_event( $event_id );

			$status = get_post_meta( $event_id, Event_Status_Meta::$key_control_status, true );

			// Update event status to TEC field.
			update_post_meta( $event_id, Event_Status_Meta::$key_status, $status );

			$reason = '';
			if ( 'canceled' === $status ) {
				$reason = get_post_meta( $event->ID, Event_Status_Meta::$key_status_canceled_reason, true );
			}

			if ( 'postponed' === $status ) {
				$reason = get_post_meta( $event->ID, Event_Status_Meta::$key_status_postponed_reason, true );
			}

			if ( empty( $reason ) ) {
				continue;
			}

			// Update reason to central source.
			update_post_meta( $event_id, Event_Status_Meta::$key_status_reason, $reason );
			clean_post_cache( $event_id );
		}
	}
}
