<?php
/**
 * Plugin upgrade class.
 *
 * @package flo-sync
 */

namespace NMFS;

use DateTime;
use NMFS\Admin\Settings_Fields;
use NMFS\Sync\Syncers;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Envs;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Factory;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Hub_Account_Setup;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\WP_Repository;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Sync\Ajax\Ajax_Syncers;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Admin\Notices\Models\Notice_Model;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Admin\Notices\Notice_Type_Enum;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Admin\Settings\Settings_Fields_Module;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Cron_Module;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Sync\Bookkeeping\Article_Account_Codes\Article_Account_Codes;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Sync\Bookkeeping_Sync_Module;

defined( 'ABSPATH' ) || exit;

/**
 * Plugin upgrade class.
 */
class Plugin_Upgrade {

	/**
	 * Setup shared hooks.
	 *
	 * @param string $service_name_underscored The service name in underscored format.
	 */
	public static function setup_shared_hooks( string $service_name_underscored ): void {
		add_filter(
			'northmill_online_shared_upgrades_' . $service_name_underscored,
			array( self::class, 'get_upgrades' )
		);
		add_filter(
			'northmill_online_shared_package_version_option_name_' . $service_name_underscored,
			array( self::class, 'get_package_version_option_name' ),
			10,
			0
		);
	}

	/**
	 * General setup.
	 */
	public static function setup(): void {
		$shared = Shared::instance();

		$settings_menu_slug = $shared->admin->pages->get_page( $shared->package_info->get_service_name(), 'sync' )->menu_slug;
		$shared->admin->notices->register_notice(
			'4.0-upgrade-sync-after',
			Notice_Type_Enum::WARNING,
			function () use ( $settings_menu_slug ) {
				return '<p>' . sprintf(
					// translators: %1$s: opening anchor tag, %2$s: closing anchor tag.
					__(
						'To finish upgrading Northmill Sync you need to do a full manual sync of the following items at the %1$ssync page%2$s. Check the "Get all data" checkbox, select the following items in the list to the right and then press Manual sync.',
						'flo-sync'
					),
					'<a href="' . admin_url( 'admin.php?page=' . $settings_menu_slug ) . '">',
					'</a>'
				) . '</p>';
			},
			false,
			function ( Notice_Model $notice_model ) use ( $shared ) {
				$using_multi_account_sync   = Hub_Account_Setup::is_multiple_stocks_enabled();
				$using_article_availability = $shared->admin->settings->is_setting_enabled( Settings_Fields::SYNC_ARTICLE_AVAILABILITIES );

				if ( $using_article_availability || $using_multi_account_sync ) {
					$availability_sync_required = false;
					$stock_sync_required        = false;

					$update_datetime = Shared::instance()->upgrader->get_time_of_update( '4.0.0' );

					if ( $using_article_availability ) {
						$availability_sync_required = true;

						$last_availability_full_sync = Ajax_Syncers::get_syncer( Syncers::SYNCER_KEY_ARTICLE_AVAILABILITIES )?->syncer->get_last_synced_wp_option( 'ajax_all' );
						if ( $last_availability_full_sync ) {
							$last_availability_date = new DateTime( $last_availability_full_sync );

							if ( $last_availability_date > $update_datetime ) {
								$availability_sync_required = false;
							}
						}
					}

					if ( $using_multi_account_sync ) {
						$stock_sync_required = true;

						$last_article_full_sync = Ajax_Syncers::get_syncer( Syncers::SYNCER_KEY_ARTICLE )?->syncer->get_last_synced_wp_option( 'ajax_all' );
						$last_stock_full_sync   = Ajax_Syncers::get_syncer( Syncers::SYNCER_KEY_ARTICLE_STOCK )?->syncer->get_last_synced_wp_option( 'ajax_all' );

						if ( $last_article_full_sync && $last_stock_full_sync ) {
							$last_article_date = new DateTime( $last_article_full_sync );
							$last_stock_date   = new DateTime( $last_stock_full_sync );

							if (
								$last_article_date > $update_datetime &&
								$last_stock_date > $update_datetime
							) {
								$stock_sync_required = false;
							}
						}
					}

					if ( $availability_sync_required || $stock_sync_required ) {
						$message  = $notice_model->get_message();
						$message .= '<ul>';
						if ( $availability_sync_required ) {
							$message .= '<li>' . __( 'Article availabilities', 'flo-sync' ) . '</li>';
						}
						if ( $stock_sync_required ) {
							$message .= '<li>' . __( 'Articles', 'flo-sync' ) . '</li>';
							$message .= '<li>' . __( 'Stock', 'flo-sync' ) . '</li>';
						}
						$message .= '</ul>';

						$notice_model->set_message( $message );
						return true;
					}
				}

				$shared->admin->notices->get_notice( '4.0-upgrade-sync-after' )?->set_closed();

				return false;
			},
			array(
				'paragraph_wrap' => false,
			)
		);

		$tools_menu_slug = $shared->admin->pages->get_page( $shared->package_info->get_service_name(), 'tools' )->menu_slug;
		$shared->admin->notices->register_notice(
			'4.0-upgrade-clean-up',
			Notice_Type_Enum::INFO,
			function () use ( $tools_menu_slug ) {
				return sprintf(
					// translators: %1$s: opening anchor tag, %2$s: closing anchor tag.
					__(
						'Northmill Sync has been upgraded to version 4.0.0. After confirming the update was successful you can clean up old data at the %1$sTools page%2$s.',
						'flo-sync'
					),
					'<a href="' . admin_url( 'admin.php?page=' . $tools_menu_slug . '&tab=remove_sync_3_data' ) . '">',
					'</a>'
				);
			},
			true,
			function () use ( $shared ) {
				$notice = $shared->admin->notices->get_notice( '4.0-upgrade-sync-after' );
				return ! $notice->should_show();
			}
		);

		$shared->admin->notices->register_notice(
			'4.1-upgrade-bookkeeping-sync',
			Notice_Type_Enum::INFO,
			function () use ( $settings_menu_slug ) {
				return sprintf(
					// translators: %1$s: opening anchor tag, %2$s: closing anchor tag.
					__(
						'To finish upgrading Northmill Sync you need to do a full manual sync of Article Account Codes and Articles at the %1$ssync page%2$s. Check the "Get all data" checkbox, select Article Account Codes and Articles in the list to the right and then press Manual sync.',
						'flo-sync'
					),
					'<a href="' . admin_url( 'admin.php?page=' . $settings_menu_slug ) . '">',
					'</a>'
				);
			},
			true,
			function () use ( $shared ) {
				$update_datetime = Shared::instance()->upgrader->get_time_of_update( '4.1.0' );

				$last_article_full_sync = Ajax_Syncers::get_syncer( Syncers::SYNCER_KEY_ARTICLE )?->syncer->get_last_synced_wp_option( 'ajax_all' );
				$last_aac_full_sync     = Ajax_Syncers::get_syncer( Bookkeeping_Sync_Module::SYNCER_KEY_AAC )?->syncer->get_last_synced_wp_option( 'ajax_all' );

				if ( $last_aac_full_sync && $last_article_full_sync ) {
					$last_availability_date = new DateTime( $last_aac_full_sync );
					$last_article_full_sync = new DateTime( $last_article_full_sync );

					if (
						$last_availability_date > $update_datetime &&
						$last_article_full_sync > $update_datetime
					) {
						return false;
					}
				}
				return true;
			}
		);
	}

	/**
	 * Get all upgrades.
	 * Hooked to Shared filter 'northmill_online_shared_upgrades'.
	 *
	 * @param array $upgrades Current upgrades to filter.
	 *
	 * @return array
	 */
	public static function get_upgrades( array $upgrades ): array {
		$shared = Shared::instance();

		/**
		 * Upgrade to 4.0.0
		 * - Remove old cron sync events
		 * - Set up new cron sync event based on old settings
		 * - Convert old settings to new settings
		 * - Convert old hub accounts to new hub accounts
		 * - Run sync if auto sync was on
		 */
		$upgrades['4.0.0'] = function ( bool $is_new_install ) use ( $shared ) {
			if ( $is_new_install ) {
				$shared->cron->check_event_statuses();
				$shared->admin->notices->get_notice( '4.0-upgrade-sync-after' )?->set_closed();
				$shared->admin->notices->get_notice( '4.0-upgrade-clean-up' )?->set_closed();

				return;
			}

			$old_settings = get_option( 'northmill_flo_settings', array() );

			$shared->logger->log(
				'upgrader',
				'Upgrading to 4.0.0',
				array(
					'old_settings' => $old_settings,
				)
			);

			$auto_sync_on = self::convert_old_setting( $old_settings, Settings_Fields_Module::AUTO_SYNC );

			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_NEW_ORDERS );
			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_ONLY_STOCK );
			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_PRODUCT_STATUS );
			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_ARTICLE_AVAILABILITIES );
			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_PRICE );
			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_ARTICLE_DISPLAY_ORDER );
			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_PRODUCT_DESCRIPTION );
			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_PRODUCT_PICTURES );
			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_PRODUCT_TITLE );
			self::convert_old_setting( $old_settings, Settings_Fields_Module::SYNC_PRODUCT_TAX_CLASS );
			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_CAMPAIGNS );
			self::convert_old_setting( $old_settings, Settings_Fields::SYNC_PRODUCT_CATEGORIES );
			self::convert_old_setting( $old_settings, Settings_Fields_Module::CREATE_RECEIPTS );
			self::convert_old_setting( $old_settings, Settings_Fields_Module::DEBUG_LOG );
			self::convert_old_setting( $old_settings, Settings_Fields::ORDER_STOCK_ADJUSTMENT );
			self::convert_old_setting( $old_settings, Settings_Fields_Module::HUB_ORDER_PREFIX );
			self::convert_old_setting( $old_settings, Envs::SETTING_NAME );

			$schedule = Cron_Module::SCHEDULE_FIVE_MINUTES;

			if ( ! empty( $old_settings['mf_sync_articles_interval'] ) ) {
				$current_schedule = $old_settings['mf_sync_articles_interval'];
				switch ( $current_schedule ) {
					case 'everyminute':
						$schedule = Cron_Module::SCHEDULE_ONE_MINUTE;
						break;
					case 'everyfiveminutes':
						break;
					case 'everyfifteenminutes':
						$schedule = Cron_Module::SCHEDULE_FIFTEEN_MINUTES;
						break;
					default:
						$schedules = wp_get_schedules();

						if ( isset( $schedules[ $current_schedule ] ) ) {
							$current_interval = $schedules[ $current_schedule ]['interval'];

							$closest_schedule    = null;
							$smallest_difference = PHP_INT_MAX;
							$nm_schedules        = Cron_Module::cron_schedules( array() );

							foreach ( $nm_schedules as $name => $schedule ) {
								$difference = abs( $schedule['interval'] - $current_interval );
								if ( $difference < $smallest_difference ) {
									$smallest_difference = $difference;
									$closest_schedule    = $name;
								}
							}

							$schedule = $closest_schedule ?? Cron_Module::SCHEDULE_FIVE_MINUTES;
						}

						break;
				}
			}
			$shared->admin->settings->set_setting( Settings_Fields_Module::SYNC_INTERVAL, $schedule );

			$likely_env = self::convert_old_hub_accounts();

			if ( null !== $likely_env ) {
				$shared->admin->settings->set_setting( Envs::SETTING_NAME, $likely_env );
			}

			if ( $auto_sync_on ) {
				$was_ran = $shared->cron->get_cron_event( 'sync' )?->run_now();

				if ( ! $was_ran ) {
					$shared->logger->log(
						'upgrader',
						'Failed to run sync after upgrade',
						array(),
						Log_Level_Enum::ERROR
					);
				}
			}

			if ( wp_next_scheduled( 'nmf_cron_sync' ) ) {
				wp_clear_scheduled_hook( 'nmf_cron_sync' );
			}

			if ( wp_next_scheduled( 'nmf_cron_sync_checker' ) ) {
				wp_clear_scheduled_hook( 'nmf_cron_sync_checker' );
			}

			$shared->cron->check_event_statuses();
		};

		/**
		 * Upgrade to 4.1.0
		 * - Delete old Article Account Codes and Tax Rates.
		 * - Delete order ticket texts, since they were added (not updated) for each sync.
		 */
		$upgrades['4.1.0'] = function ( bool $is_new_install ) use ( $shared ) {
			if ( ! $is_new_install ) {
				global $wpdb;
				delete_option( 'mf_tax_rates_ids' );
				delete_option( 'mf_tax_rates_cls' );
				delete_option( 'mf_wc_tax_rules' );
				delete_option( 'northmill_article_account_codes' );

				Article_Account_Codes::remove_prefixed_tax_rates( 'mf-' );
				Article_Account_Codes::remove_prefixed_tax_classes( 'mf-' );

				$wpdb->query(
					"DELETE pm
                 FROM $wpdb->postmeta pm
                 INNER JOIN $wpdb->posts p ON pm.post_id = p.ID
                 WHERE p.post_type = 'product'
                 AND ( pm.meta_key = 'nmf_order_ticket_dialogue' OR pm.meta_key = 'nmf_order_ticket_text' )",
				);
			}
		};

		/**
		 * Upgrade to 4.2.0
		 * - Change settings structure to not be nested.
		 */
		$upgrades['4.2.0'] = function ( bool $is_new_install ) use ( $shared ) {
			if ( ! $is_new_install ) {
				$options = get_option( 'northmill-sync-settings' );

				if ( isset( $options['northmill-sync-settings'] ) ) {
					Shared::instance()->admin->settings->set_settings( $options['northmill-sync-settings'] );
				}
			}
		};

		return $upgrades;
	}

	/**
	 * Convert old settings field.
	 *
	 * @param array  $old_settings       Old settings.
	 * @param string $new_setting_name   New setting name.
	 *
	 * @return mixed new setting value.
	 */
	private static function convert_old_setting( array $old_settings, string $new_setting_name ): mixed {
		$settings_module = Shared::instance()->admin->settings;

		$default = Shared::instance()->admin->settings_fields->get_field_default( $new_setting_name );

		$name_translations = array(
			Settings_Fields_Module::AUTO_SYNC              => 'mf_sync_status',
			Settings_Fields::SYNC_NEW_ORDERS               => 'sync_new_orders_to_apps',
			Settings_Fields::SYNC_ONLY_STOCK               => 'sync_only_product_stock_amount',
			Settings_Fields::SYNC_PRODUCT_STATUS           => 'sync_publish_products_status',
			Settings_Fields::SYNC_ARTICLE_AVAILABILITIES   => 'sync_article_availability',
			Settings_Fields::SYNC_ARTICLE_DISPLAY_ORDER    => 'sync_product_display_order_status',
			Settings_Fields::SYNC_PRICE                    => 'is_price_active',
			Settings_Fields::SYNC_PRODUCT_DESCRIPTION      => 'sync_article_description',
			Settings_Fields::SYNC_PRODUCT_PICTURES         => 'is_pic_active',
			Settings_Fields::SYNC_PRODUCT_TITLE            => 'is_product_title_active',
			Settings_Fields_Module::SYNC_PRODUCT_TAX_CLASS => 'is_tax_class_active',
			Settings_Fields::SYNC_CAMPAIGNS                => 'is_campaigns_active',
			Settings_Fields::SYNC_PRODUCT_CATEGORIES       => 'is_product_categories_active',
			Settings_Fields::ORDER_STOCK_ADJUSTMENT        => 'sync_order_stock_adjustment',
			Settings_Fields_Module::CREATE_RECEIPTS        => 'is_receipt_active',
			Settings_Fields_Module::DEBUG_LOG              => 'debug_log_enabled',
			Settings_Fields_Module::HUB_ORDER_PREFIX       => 'hub_order_number_prefix',
			Envs::SETTING_NAME                             => 'mf_sync_env',
		);

		if (
			! isset( $name_translations[ $new_setting_name ] ) ||
			! isset( $old_settings[ $name_translations[ $new_setting_name ] ] )
		) {
			$settings_module->set_setting( $new_setting_name, $default );
			return $default;
		}

		$setting_value = $old_settings[ $name_translations[ $new_setting_name ] ];

		switch ( $new_setting_name ) {
			case Settings_Fields_Module::AUTO_SYNC:
			case Settings_Fields::SYNC_NEW_ORDERS:
			case Settings_Fields::SYNC_ONLY_STOCK:
			case Settings_Fields::SYNC_PRODUCT_STATUS:
			case Settings_Fields::SYNC_ARTICLE_AVAILABILITIES:
			case Settings_Fields::SYNC_ARTICLE_DISPLAY_ORDER:
			case Settings_Fields::SYNC_PRICE:
			case Settings_Fields::SYNC_PRODUCT_DESCRIPTION:
			case Settings_Fields::SYNC_PRODUCT_PICTURES:
			case Settings_Fields::SYNC_PRODUCT_TITLE:
			case Settings_Fields_Module::SYNC_PRODUCT_TAX_CLASS:
			case Settings_Fields::SYNC_CAMPAIGNS:
			case Settings_Fields::SYNC_PRODUCT_CATEGORIES:
			case Settings_Fields_Module::CREATE_RECEIPTS:
			case Settings_Fields_Module::DEBUG_LOG:
				$setting_value = 'enabled' === $setting_value;
				break;
			case Settings_Fields::ORDER_STOCK_ADJUSTMENT:
				$setting_value = Settings_Fields::ORDER_STOCK_ADJUSTMENT_ORDER_OPEN === $setting_value ?
					Settings_Fields::ORDER_STOCK_ADJUSTMENT_ORDER_OPEN :
					Settings_Fields::ORDER_STOCK_ADJUSTMENT_ORDER_PAID;
				break;
			case Settings_Fields_Module::HUB_ORDER_PREFIX:
				// Keep it unchanged.
				break;
			case Envs::SETTING_NAME:
				$setting_value = 'test' === $setting_value ? 'test' : 'prod';
				break;
		}

		$settings_module->set_setting( $new_setting_name, $setting_value );

		return $setting_value;
	}

	/**
	 * Convert old hub accounts.
	 */
	private static function convert_old_hub_accounts(): ?string {
		$get_post_args = array(
			'post_type'      => 'mf_bo_accounts',
			'posts_per_page' => -1,
			'post_status'    => 'publish',
			'orderby'        => 'id',
			'order'          => 'ASC',
		);
		$posts         = get_posts( $get_post_args );

		$accounts = array(
			'test' => 0,
			'prod' => 0,
		);

		foreach ( $posts as $account ) {
			$backoffice_account_id = $account->ID;
			$account_meta          = get_post_meta( $backoffice_account_id );

			Shared::instance()->logger->log(
				'upgrader',
				'Converting Backoffice account',
				array(
					'backoffice_account_id' => $account->ID,
					'account_meta'          => $account_meta,
				)
			);

			$account_data = array(
				'enabled'                    => (bool) $account_meta['nmf_bo_account_enabled'][0],
				'display_name'               => $account_meta['nmf_bo_account_display_name'][0],
				'url'                        => $account_meta['nmf_bo_account_api_url'][0],
				'user'                       => $account_meta['nmf_bo_account_api_user'][0],
				'pass'                       => $account_meta['nmf_bo_account_api_pass'][0],
				'type'                       => $account_meta['nmf_bo_account_api_type'][0],
				'env'                        => $account_meta['nmf_bo_account_api_env'][0],
				'allowed_shipping_methods'   => maybe_unserialize( $account_meta['nmf_bo_account_allowed_shipping_methods'][0] ),
				'org_num'                    => $account_meta['nmf_bo_account_org_num'][0],
				'org_name'                   => $account_meta['nmf_bo_account_org_name'][0],
				'partial_refund_article_sku' => $account_meta['nmf_bo_account_partial_refund_article_sku'][0],
			);

			if ( str_contains( $account_data['url'], '//test.api.moreflo.com' ) ) {
				$account_data['env'] = 'test';
			} elseif ( str_contains( $account_data['url'], '//api.moreflo.com' ) ) {
				$account_data['env'] = 'prod';
			}

			if ( ! in_array( $account_data['env'], array( 'test', 'prod' ), true ) ) {
				$account_data['env'] = 'prod';
			}
			++$accounts[ $account_data['env'] ];

			$hub_account_model = Factory::create( $account_data );
			$created           = WP_Repository::create_from_model( $hub_account_model );

			if ( false === $created ) {
				Shared::instance()->logger->log(
					'upgrader',
					'Failed to convert Backoffice account',
					array(
						'backoffice_account_id' => $account->ID,
					),
					Log_Level_Enum::ERROR
				);
			}
		}

		return self::get_environment( $accounts );
	}

	/**
	 * Get probable environment.
	 *
	 * @param array $accounts Converted account count.
	 *
	 * @return string|null
	 */
	private static function get_environment( array $accounts ): ?string {

		if ( $accounts['test'] > 0 && 0 === $accounts['prod']
		) {
			return 'test'; // Only test accounts exist.
		}

		if ( $accounts['prod'] > 0 && 0 === $accounts['test'] ) {
			return 'prod'; // Only prod accounts exist.
		}

		return null; // Ambiguous; either no accounts or both types exist.
	}

	/**
	 * Get package version option name.
	 *
	 * @return string
	 */
	public static function get_package_version_option_name(): string {
		return 'nmf_version';
	}
}
