<?php
/**
 * WooCommerce Global Payments HPP
 *
 * This source file is subject to the GNU General Public License v3.0
 * that is bundled with this package in the file license.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl-3.0.html
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@skyverge.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade WooCommerce Global Payments HPP to newer
 * versions in the future. If you wish to customize WooCommerce Global Payments HPP for your
 * needs please refer to https://docs.woocommerce.com/document/woocommerce-global-payments/ for more information.
 *
 * @author      SkyVerge
 * @copyright   Copyright (c) 2012-2024, SkyVerge, Inc.
 * @license     http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
 */

namespace SkyVerge\WooCommerce\Realex_HPP;

defined( 'ABSPATH' ) or exit;

use SkyVerge\WooCommerce\PluginFramework\v5_12_0 as Framework;
use WC_Gateway_Realex_Redirect;
use WC_Realex_Redirect;

/**
 * The Realex Redirect plugin lifecycle handler.
 *
 * @since 2.1.2
 *
 * @method \WC_Realex_Redirect get_plugin()
 */
class Lifecycle extends Framework\Plugin\Lifecycle {


	/**
	 * Constructs the class.
	 *
	 * @param \WC_Realex_Redirect $plugin plugin instance
	 */
	public function __construct( \WC_Realex_Redirect $plugin ) {

		$this->upgrade_versions = [
			'2.0.0',
			'3.0.0',
		];

		parent::__construct( $plugin );
	}


	/**
	 * Adds the action & filter hooks.
	 *
	 * @since 3.0.0
	 */
	protected function add_hooks() {

		parent::add_hooks();

		if ( is_admin() && ! wp_doing_ajax() ) {
			// uses a higher priority to run after the method that sets the migration flag on Global Payments Direct
			add_action( 'admin_init', [ $this, 'maybe_migrate_from_direct' ], 50 );
		}
	}


	/**
	 * Performs any install tasks.
	 *
	 * @see Framework\SV_WC_Plugin::install()
	 *
	 * @since 2.1.2
	 */
	protected function install() {

		// check for a pre 1.1.1 version
		if ( $legacy_settings = get_option( 'woocommerce_realex_redirect_settings' ) ) {

			$this->migrate_from_legacy( is_array( $legacy_settings ) ? $legacy_settings : [] );

			// upgrade path
			$this->upgrade( '1.0.0' );

		// check if the merchant is migrating from Global Payments Direct
		} elseif ( 'yes' === get_option( 'wc_realex_redirect_migrate_from_direct' ) ) {

			$this->migrate_from_direct();
		}
	}


	/**
	 * Performs any required upgrade tasks.
	 *
	 * @see Framework\SV_WC_Plugin::upgrade()
	 *
	 * @since 2.2.0
	 */
	protected function upgrade_to_2_0_0() {

		$legacy_settings   = get_option( 'woocommerce_realex_redirect_settings', [] );
		$settings_upgraded = get_option( 'woocommerce_realex_redirect_settings_upgraded', false );

		if ( ! empty( $legacy_settings ) && ! $settings_upgraded ) {

			// back up the settings, just in case someone wants to downgrade
			update_option( 'woocommerce_realex_redirect_settings_legacy', $legacy_settings );

			$this->get_plugin()->log( 'Upgrading settings' );

			$legacy_settings = wp_parse_args( $legacy_settings, [
				'testmode'     => 'yes',
				'debug_mode'   => 'off',
				'settlement'   => 'yes',
				'cardtypes'    => [ 'VISA', 'MC', 'AMEX', 'LASER', 'SWITCH', 'DINERS', 'cartebleue', 'maestro', ],
				'merchantid'   => '',
				'sharedsecret' => '',
				'accounttest'  => '',
				'accountlive'  => '',
				'enable_avs'   => 'yes',
			] );

			$upgraded_settings = [
				'enabled'          => $legacy_settings['enabled'],
				'title'            => $legacy_settings['title'],
				'description'      => $legacy_settings['description'],
				'transaction_type' => 'no' === $legacy_settings['settlement'] ? \WC_Gateway_Realex_Redirect::TRANSACTION_TYPE_AUTHORIZATION : \WC_Gateway_Realex_Redirect::TRANSACTION_TYPE_CHARGE,
				'card_types'       => $legacy_settings['cardtypes'],
				'debug_mode'       => $legacy_settings['debug_mode'],
				'environment'      => 'yes' === $legacy_settings['testmode'] ? \WC_Gateway_Realex_Redirect::ENVIRONMENT_TEST : \WC_Gateway_Realex_Redirect::ENVIRONMENT_PRODUCTION, // TODO: don't check after testing
				'merchant_id'      => $legacy_settings['merchantid'],
				'shared_secret'    => WC_Realex_Redirect::encrypt_credential( $legacy_settings['sharedsecret'] ),
				'subaccount'       => $legacy_settings['accountlive'],
				'test_subaccount'  => $legacy_settings['accounttest'],
				'form_type'        => 'redirect',
				'enable_avs'       => $legacy_settings['enable_avs'],
			];

			if ( update_option( 'woocommerce_realex_redirect_settings', $upgraded_settings ) ) {

				update_option( 'woocommerce_realex_redirect_settings_upgraded', true );

				$this->get_plugin()->log( 'Settings successfully upgraded' );

			} else {

				$this->get_plugin()->log( 'Error upgrading settings' );
			}
		}
	}


	/**
	 * Performs any required upgrade tasks for v3.0.0.
	 *
	 * @since 3.0.0
	 */
	protected function upgrade_to_3_0_0() {

		if ( 'yes' === get_option( 'wc_realex_redirect_migrate_from_direct' ) ) {
			$this->migrate_from_direct();
		}
	}


	/**
	 * Migrates from the Global Payments direct gateway plugin.
	 *
	 * @since 3.0.0
	 */
	protected function migrate_from_direct() {

		$this->migrate_settings_from_direct();
		$this->migrate_payment_tokens_from_direct();
		$this->migrate_framework_payment_tokens_from_direct();
		$this->migrate_user_meta_from_direct();
		$this->migrate_order_meta_from_direct();

		update_option( 'wc_realex_redirect_migrated_from_direct', 'yes' );

		$this->deactivate_global_payments_direct_plugin();

		// remove migration flag after all migration steps are complete
		delete_option( 'wc_realex_redirect_migrate_from_direct' );
	}


	/**
	 * Deactivates Global Payments Direct plugin.
	 *
	 * @internal
	 *
	 * @since 3.0.0
	 */
	public function deactivate_global_payments_direct_plugin() {

		if ( ! function_exists( 'is_plugin_active' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$plugin_file = 'woocommerce-gateway-realex/woocommerce-gateway-realex.php';

		if ( is_plugin_active( $plugin_file ) ) {
			deactivate_plugins( $plugin_file );
		}
	}


	/**
	 * Updates the settings based on the legacy settings.
	 *
	 * We need to adjust the settings array if we are upgrading from the pre-versioned version.
	 *
	 * form_submission_method => 'yes': In version 1.1.1 of the plugin we added the option to redirect
	 * from the checkout page to the hosted payment page, and made it the default behavior. Unfortunately
	 * all the existing customers will have whitelisted the pay page url /checkout/pay/ with Realex so
	 * we can't go willy-nilly changing this on them so we'll default them to keeping their current
	 * behavior
	 *
	 * @since 3.0.0
	 *
	 * @param array $legacy_settings legacy settings
	 */
	protected function migrate_from_legacy( array $legacy_settings ) {

		if ( ! isset( $legacy_settings['form_submission_method'] ) ) {
			$legacy_settings['form_submission_method'] = 'yes';
		}

		// log -> debug_mode
		if ( ! isset( $legacy_settings['log'] ) || 'no' === $legacy_settings['log'] ) {
			$legacy_settings['debug_mode'] = 'off';
		} elseif ( isset( $legacy_settings['log'] ) && 'yes' === $legacy_settings['log'] ) {
			$legacy_settings['debug_mode'] = 'log';
		}
		unset( $legacy_settings['log'] );

		// set the updated options array
		update_option( 'woocommerce_realex_redirect_settings', $legacy_settings );
	}


	/**
	 * Updates the settings based on the Global Payments direct settings.
	 *
	 * @since 3.0.0
	 */
	private function migrate_settings_from_direct() {

		$default_settings = get_option( 'woocommerce_realex_redirect_settings', [] );
		$old_settings     = get_option( 'woocommerce_realex_credit_card_settings', [] );
		$new_settings     = [];

		// backup existing settings
		if ( $default_settings ) {
			update_option( 'woocommerce_realex_redirect_settings_before_direct_migration', $default_settings );
		}

		// set the default form type in case the merchant never saves the HPP settings before trying on a transaction
		if ( empty( $default_settings['form_type'] ) ) {
			$default_settings['form_type'] = WC_Gateway_Realex_Redirect::FORM_TYPE_IFRAME;
		}

		// copy the values from direct settings that share the same name
		foreach ( $this->get_direct_setting_names() as $name ) {
			if ( ! empty( $old_settings[ $name ] ) ) {
				$new_settings[ $name ] = $old_settings[ $name ];
			}
		}

		// encrypted settings
		foreach ( [ 'shared_secret', 'rebate_password' ] as $name ) {
			if ( ! empty( $new_settings[ $name ] ) ) {
				$new_settings[ $name ] = WC_Realex_Redirect::encrypt_credential( $new_settings[ $name ] );
			}
		}

		update_option( 'woocommerce_realex_redirect_settings', array_merge( $default_settings, $new_settings ) );
	}


	/**
	 * Gets a list of Global Payments Direct setting names that should be migrated.
	 *
	 * @since 3.0.0
	 *
	 * @return array
	 */
	private function get_direct_setting_names(): array {

		return [
			'enabled',
			'title',
			'description',
			'enable_csc',
			'transaction_type',
			'charge_virtual_orders',
			'enable_partial_capture',
			'enable_paid_capture',
			'card_types',
			'tokenization',
			'enable_customer_decline_messages',
			'debug_mode',
			'environment',
			'merchant_id',
			'shared_secret',
			'subaccount',
			'test_subaccount',
			'rebate_password',
			'enable_avs',
		];
	}


	/**
	 * Migrates user metadata generated using the Global Payments direct gateway.
	 *
	 * @since 3.0.0
	 */
	private function migrate_user_meta_from_direct() {

		global $wpdb;

		$wpdb->query( "
			UPDATE {$wpdb->usermeta}
			SET meta_key = REPLACE(meta_key, 'wc_realex_customer_id', 'wc_realex_redirect_customer_id')
			WHERE meta_key IN ('wc_realex_customer_id_test', 'wc_realex_customer_id')
		" );
	}


	/**
	 * Migrates WC payment tokens created using the Global Payments direct gateway.
	 *
	 * @since 3.0.0
	 */
	private function migrate_payment_tokens_from_direct() {

		global $wpdb;

		$wpdb->query( "
			UPDATE {$wpdb->prefix}woocommerce_payment_tokens
			SET gateway_id = 'realex_redirect'
			WHERE gateway_id = 'realex_credit_card'
		" );
	}


	/**
	 * Migrates order metadata generated using the Global Payments direct gateway.
	 *
	 * @since 3.0.0
	 */
	private function migrate_order_meta_from_direct() {

		// gets all post IDs that will be updated
		$order_ids = $this->get_order_ids_to_be_migrated_from_direct();

		// updates the postmeta table, setting the _payment_method column to realex_redirect where it is set to realex_credit_card
		$this->update_payment_method_to_realex_redirect( $order_ids );

		// adds a post meta with key _wc_realex_redirect_payment_type and value credit-card for each updated order
		$this->add_order_meta_data_for_updated_payment_types( $order_ids );

		// replaces realex_credit_card keys in post/order meta with realex_redirect
		$this->replace_meta_keys_to_realex_redirect();
	}


	/**
	 * Gets all the order IDs that will be migrated from direct.
	 *
	 * TODO: Remove this method once Lifecycle::migrate_order_meta_from_direct() is removed {AC 2021-02-04}
	 *
	 * @since 3.0.0
	 *
	 * @return int[]
	 */
	private function get_order_ids_to_be_migrated_from_direct(): array {

		$args = [
			'payment_method' => 'realex_credit_card',
			'return' => 'ids',
		];

		return wc_get_orders( $args );
	}


	/**
	 * Updates the orders, setting the payment method column to realex_redirect where it is set to realex_credit_card.
	 *
	 * TODO: Remove this method once Lifecycle::migrate_order_meta_from_direct() is removed {AC 2021-02-04}
	 *
	 * @since 3.0.0
	 *
	 * @param int[] $order_ids the order IDs to be updated
	 */
	private function update_payment_method_to_realex_redirect( array $order_ids ) {

		if ( ! empty( $order_ids ) ) {

			foreach( $order_ids as $order_id ) {

				$order = wc_get_order( $order_id );

				if ( $order->get_payment_method() === 'realex_credit_card' ) {

					$order->set_payment_method( 'realex_redirect' );
					$order->save();
				}
			}
		}
	}


	/**
	 * Adds a order meta with entry for each updated order.
	 *
	 * TODO: Remove this method once Lifecycle::migrate_order_meta_from_direct() is removed {AC 2021-02-04}
	 *
	 * @since 3.0.0
	 *
	 * @param int[] $order_ids the order IDs to be updated
	 */
	private function add_order_meta_data_for_updated_payment_types( array $order_ids ) {

		foreach ( $order_ids as $order_id ){

			$order = wc_get_order( $order_id );
			$order->update_meta_data( '_wc_realex_redirect_payment_type', 'credit-card' );
			$order->save_meta_data();
		}
	}


	/**
	 * Replaces post/order meta keys beginning with _wc_realex_credit_card with _wc_realex_redirect_.
	 *
	 * With one exception: _wc_realex_credit_card_processing_3d_secure_response is not covered by this method.
	 *
	 * TODO: Remove this method once Lifecycle::migrate_order_meta_from_direct() is removed {AC 2021-02-04}
	 *
	 * @since 3.0.0
	 */
	private function replace_meta_keys_to_realex_redirect() {
		global $wpdb;

		if ( Framework\SV_WC_Plugin_Compatibility::is_hpos_enabled() ) {

			$orders = wc_get_orders(
				[
					'meta_query' => [
						[
							'key' => '_wc_realex_credit_card_',
							'compare' => 'LIKE',
						],
					],
				]
			);

			foreach( $orders as $order ) {

				$order_meta = $order->get_meta_data();

				foreach( $order_meta as $meta ) {

					$data = $meta->get_data();

					if ( isset( $data['key'], $data['value'] ) && str_contains( $data['key'], '_wc_realex_credit_card_' ) && $data['key'] !== '_wc_realex_credit_card_processing_3d_secure_response' ) {

						$new_key = str_replace( '_wc_realex_credit_card_', '_wc_realex_redirect_', $data['key'] );
						$order->update_meta_data( $new_key, $data['value'] );
						$order->delete_meta( $data['key'] );
						$order->save_meta_data();
					}
				}
			}

		} else {

			$wpdb->query( "
				UPDATE {$wpdb->prefix}postmeta
				SET meta_key = REPLACE(meta_key, '_wc_realex_credit_card_', '_wc_realex_redirect_')
				WHERE meta_key <> '_wc_realex_credit_card_processing_3d_secure_response' AND meta_key LIKE '_wc_realex_credit_card_%'
			" );
		}
	}


	/**
	 * Queries users with yet to migrate framework payment tokens.
	 *
	 * @since 3.0.0
	 *
	 * @param string $token_prefix
	 *
	 * @return array
	 */
	private function query_users_with_framework_tokens_pending_migration( string $token_prefix ): array {

		global $wpdb;

		// query users with migrated tokens
		$user_ids_with_migrated_tokens = $wpdb->get_col( "SELECT DISTINCT user_id FROM {$wpdb->usermeta} WHERE meta_key = '{$token_prefix}_migrated' OR meta_key LIKE '{$token_prefix}%_migrated'" );

		// build NOT IN condition to exclude already migrated users
		$not_in_sql = '';
		if ( $user_ids_with_migrated_tokens ) {
			$not_in_sql = ' AND user_id NOT IN (' . implode( ',', $user_ids_with_migrated_tokens ) . ')';
		}

		// query users with tokens that are not migrated yet
		return $wpdb->get_col( "SELECT DISTINCT user_id FROM {$wpdb->usermeta} WHERE meta_key LIKE '{$token_prefix}%'" . $not_in_sql );
	}


	/**
	 * Migrates framework payment tokens created using the Global Payments direct gateway, if they were not migrated to WC payment tokens yet.
	 *
	 * @since 3.0.0
	 */
	private function migrate_framework_payment_tokens_from_direct() {

		$global_payments_direct_prefix = '_wc_realex_credit_card_payment_tokens';

		$user_ids_with_old_tokens = $this->query_users_with_framework_tokens_pending_migration( $global_payments_direct_prefix );

		if ( empty( $user_ids_with_old_tokens ) ) {
			return;
		}

		$global_payments_redirect_prefix = '_wc_realex_redirect_payment_tokens';

		foreach ( $user_ids_with_old_tokens as $user_id ) {

			// load direct payment tokens
			$direct_payment_tokens_production = get_user_meta( $user_id, $global_payments_direct_prefix, true );
			$direct_payment_tokens_staging    = get_user_meta( $user_id, $global_payments_direct_prefix . '_test', true );

			// copy them over to redirect
			if ( $direct_payment_tokens_production ) {

				if ( $old_production_tokens = get_user_meta( $user_id, $global_payments_redirect_prefix, true ) ) {
					update_user_meta( $user_id, "{$global_payments_redirect_prefix}_before_direct_migration", $old_production_tokens );
				}

				update_user_meta( $user_id, $global_payments_redirect_prefix, $direct_payment_tokens_production );
			}

			if ( $direct_payment_tokens_staging ) {

				if ( $old_staging_tokens = get_user_meta( $user_id, "${global_payments_redirect_prefix}_test", true ) ) {
					update_user_meta( $user_id, "{$global_payments_redirect_prefix}_test_before_direct_migration", $old_staging_tokens );
				}

				update_user_meta( $user_id, $global_payments_redirect_prefix . '_test', $direct_payment_tokens_staging );
			}

			// Delete the corresponding migration key
			delete_user_meta( $user_id, $global_payments_redirect_prefix . '_migrated' );
			delete_user_meta( $user_id, $global_payments_redirect_prefix . '_test_migrated' );
		}
	}


	/**
	 * Starts the migration from Global Payments Direct plugin if the migration option is set.
	 *
	 * @since 3.0.0
	 */
	public function maybe_migrate_from_direct() {

		if ( ! get_option( 'wc_realex_redirect_migrated_from_direct' ) && 'yes' === get_option( 'wc_realex_redirect_migrate_from_direct' ) ) {
			$this->migrate_from_direct();
		}
	}


}
