<?php
/**
 * Resurs Bank Payments for WooCommerce.
 *
 * @package flo-checkout
 */

namespace Flo_Checkout\Gateways;

use DivisionByZeroError;
use Exception;
use Flo_Checkout\Gateways\Helpers\Gateway_Logger_Trait;
use Flo_Checkout\Gateways\Helpers\Interface_Flo_WC_Payment_Gateway;
use Flo_Checkout\Shared;
use JetBrains\PhpStorm\NoReturn;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Data\Payment_Methods;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use Resursbank\RBEcomPHP\RESURS_ENVIRONMENTS;
use Resursbank\RBEcomPHP\RESURS_FLOW_TYPES;
use Resursbank\RBEcomPHP\RESURS_URL_ENCODE_TYPES;
use Resursbank\RBEcomPHP\ResursBank;
use WC_Admin_Settings;
use WC_Appointment_Data_Store;
use WC_Cart;
use WC_Coupon;
use WC_Customer;
use WC_Data_Exception;
use WC_Order;
use WC_Payment_Gateway;
use WC_Product;
use WC_Subscriptions_Cart;
use WC_Tax;

defined( 'ABSPATH' ) || exit;
/**
 * Resurs bank Payments for Woocommerce.
 *
 * @class       Invoice_Pay
 * @extends     WC_Payment_Gateway
 * @version     1.0.0
 */
class Resurs_Invoice_Pay extends WC_Payment_Gateway implements Interface_Flo_WC_Payment_Gateway {
	use Gateway_Logger_Trait;

	private const OMNI_CHECKOUT_CREATE = 'omniCheckoutCreate';
	private const OMNI_REF             = 'omniRef';

	/**
	 * Resurs Bank object.
	 *
	 * @var ResursBank
	 */
	private ResursBank $auth;

	/**
	 * Whether the WooCommerce session is valid.
	 *
	 * @var bool
	 */
	private bool $woo_session_valid = false;

	/**
	 * Payment ID.
	 *
	 * @var string|null
	 */
	private ?string $pay_id = null;

	/**
	 * Cart ID.
	 *
	 * @var int
	 */
	private int $cart_id;

	/**
	 * Sandbox option.
	 *
	 * @var string
	 */
	private string $sandbox;

	/**
	 * Whether the gateway is in sandbox mode.
	 *
	 * @var bool
	 */
	private bool $is_sandbox;

	/**
	 * Merchant ID.
	 *
	 * @var string
	 */
	private string $merchant_id;

	/**
	 * Password.
	 *
	 * @var string
	 */
	private string $password;

	/**
	 * Get Gateway ID.
	 *
	 * @return string
	 */
	public static function get_id(): string {
		return Payment_Methods::NMC_RESURS_INVOICE;
	}

	/**
	 * Whether the gateway supports blocks.
	 *
	 * @return bool
	 */
	public static function has_block_support(): bool {
		return true;
	}

	/**
	 * Public setup.
	 *
	 * @return void
	 */
	public static function public_setup(): void {}

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->id                 = self::get_id();
		$this->method_title       = __( 'Northmill Checkout - Resurs Bank', 'flo-checkout-plugin' );
		$this->method_description = __( 'Use invoice, Swish and partial payment and validate the customers credibility with Resurs Bank. Requires SSL when in live mode.', 'flo-checkout-plugin' );
		$this->has_fields         = true;
		$this->supports           = array(
			'products',
			'tokenization',
		);

		// Load the form fields.
		$this->init_form_fields();

		// Load the settings.
		$this->init_settings();
		$this->cart_id = url_to_postid( wc_get_checkout_url() );

		// Get setting values.
		$this->enabled     = $this->get_option( 'enabled' );
		$this->sandbox     = $this->get_option( 'sandbox' );
		$this->is_sandbox  = 'yes' === $this->sandbox;
		$this->title       = $this->get_option( 'title' );
		$this->description = $this->get_option( 'description' );
		$this->merchant_id = ! $this->is_sandbox ? $this->get_option( 'live_merchant_id' ) : $this->get_option( 'sandbox_merchant_id' );
		$this->password    = ! $this->is_sandbox ? $this->get_option( 'live_password' ) : $this->get_option( 'sandbox_password' );

		$this->authentication();
		// initial session setup.
		$this->validate_session();
		$this->init_session_variables();

		add_action( 'woocommerce_cart_updated', array( self::class, 'resurs_omni_checkout_update' ) );

		// General Hooks.
		add_action( 'woocommerce_api_invoice_pay', array( $this, 'handle_resurs_callback' ) );

		add_action(
			'woocommerce_update_options_payment_gateways_' . $this->id,
			array(
				$this,
				'process_admin_options',
			)
		);
		// add the action.
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ), 1, 0 );
		add_action(
			'woocommerce_after_checkout_form',
			array(
				$this,
				'resurs_omni_checkout_form_location',
			)
		); // Iframe form loads.
	}

	/**
	 * Payment form on checkout page.
	 */
	public function payment_fields(): void {
		if ( $this->description ) {
			echo wp_kses_post( wpautop( $this->description ) );
		}
	}

	/**
	 * Check if the gateway is available for use.
	 *
	 * @return bool
	 */
	public function is_available(): bool {
		if ( class_exists( 'WC_Subscriptions_Cart' ) ) {
			if ( WC_Subscriptions_Cart::cart_contains_subscription() ) {
				return false;
			}
		}

		return parent::is_available();
	}

	/**
	 * Process the payment.
	 *
	 * @param int $order_id Order ID.
	 */
	public function process_payment( $order_id ): array {
		new WC_Order( $order_id );

		$omni_ref = WC()->session->get( self::OMNI_REF );

		update_post_meta( $order_id, 'paymentId', $omni_ref );

		$this->log(
			'Processing order details',
			array(
				'order_id'         => $order_id,
				'resurs paymentId' => $omni_ref,
			),
			Log_Level_Enum::INFO
		);

		// Return thank you redirect.
		return array(
			'result' => 'success',
		);
	}

	/**
	 * Resurs payment put method if cart updated this function trigger
	 */
	public static function resurs_omni_checkout_update(): void {
		$pay_option = get_option( 'woocommerce_invoice_pays_settings' );

		if ( isset( $pay_option['enabled'] ) && 'no' !== $pay_option['enabled'] ) {
			if (
				is_checkout() &&
				( WC()->session->get( self::OMNI_CHECKOUT_CREATE ) === 'yes' ) &&
				WC()->cart->get_cart_contents_count() >= 1 &&
				! empty( WC()->session->get( self::OMNI_REF ) )
			) {
				$current_omni_ref = WC()->session->get( self::OMNI_REF );
				$pay_option       = get_option( 'woocommerce_invoice_pays_settings' );

				$user = 'no' === $pay_option['sandbox'] ? $pay_option['live_merchant_id'] : $pay_option['sandbox_merchant_id'];
				$pw   = 'no' === $pay_option['sandbox'] ? $pay_option['live_password'] : $pay_option['sandbox_password'];

				if ( $user && $pw ) {
					if ( 'yes' === $pay_option['sandbox'] ) {
						$auth = new ResursBank( $user, $pw, RESURS_ENVIRONMENTS::ENVIRONMENT_TEST );
					} else {
						$auth = new ResursBank( $user, $pw, RESURS_ENVIRONMENTS::ENVIRONMENT_PRODUCTION );
					}
				}
				$payment_spec = self::get_payment_spec( WC()->cart );
				$auth->updateCheckoutOrderLines( $current_omni_ref, $payment_spec['specLines'] );
			}
		}
	}

	/**
	 * Admin field validation
	 *
	 * @return void
	 *
	 * @phpcs:disable WordPress.Security.NonceVerification -- Nonce verification is not needed here.
	 */
	public function process_admin_options(): void {
		parent::process_admin_options();
		if ( ! empty( $_POST['woocommerce_invoice_pays_sandbox'] ) && ( empty( $_POST['woocommerce_invoice_pays_sandbox_merchant_id'] ) || empty( $_POST['woocommerce_invoice_pays_sandbox_password'] ) ) ) {
			WC_Admin_Settings::add_error( 'Error: Please fill sandbox fields' );
			return;
		}

		if ( empty( $_POST['woocommerce_invoice_pays_sandbox'] ) && ( empty( $_POST['woocommerce_invoice_pays_live_merchant_id'] ) || empty( $_POST['woocommerce_invoice_pays_live_password'] ) ) ) {
			WC_Admin_Settings::add_error( 'Error: Please fill live fields' );
		}
	}
	// phpcs:enable

	/**
	 * Initialise Gateway Settings Form Fields.
	 */
	public function init_form_fields(): void {

		$label       = __( 'Enable Logging', 'flo-checkout-plugin' );
		$description = __( 'Enable the logging of errors.', 'flo-checkout-plugin' );

		if ( defined( 'WC_LOG_DIR' ) ) {
			$invoice_log_url = add_query_arg( 'tab', 'logs', add_query_arg( 'page', 'wc-status', admin_url( 'admin.php' ) ) );
			$invoice_log_key = 'invoice_pays-' . sanitize_file_name( wp_hash( 'invoice_pays' ) ) . '-log';
			$invoice_log_url = add_query_arg( 'log_file', $invoice_log_key, $invoice_log_url );

			$label .= ' | ';
			$label .= sprintf(
				// translators: %1$s: opening a tag, %2$s: closing a tag.
				__( '%1$sView Log%2$s', 'flo-checkout-plugin' ),
				'<a href="' . esc_url( $invoice_log_url ) . '">',
				'</a>'
			);
		}

		$this->form_fields = array(
			'enabled'             => array(
				'title'       => __( 'Enable/Disable', 'flo-checkout-plugin' ),
				'label'       => __( 'Enable payment using Resurs Bank payment gateway', 'flo-checkout-plugin' ),
				'type'        => 'checkbox',
				'description' => '',
				'default'     => 'no',
			),
			'sandbox'             => array(
				'title'       => __( 'Sandbox', 'flo-checkout-plugin' ),
				'label'       => __( 'Enable Sandbox Mode', 'flo-checkout-plugin' ),
				'type'        => 'checkbox',
				'description' => __( 'Enabling test mode(real payments will not be taken).', 'flo-checkout-plugin' ),
				'default'     => 'yes',
			),
			'title'               => array(
				'title'       => __( 'Title', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This controls the title which the user sees during checkout within the credit card payment type.', 'flo-checkout-plugin' ),
				'default'     => __( 'Pay with Resurs Bank', 'flo-checkout-plugin' ),
			),
			'description'         => array(
				'title'       => __( 'Description', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This controls the description which the user sees during checkout within the credit card payment type.', 'flo-checkout-plugin' ),
				'default'     => __( 'Pay with Resurs Bank', 'flo-checkout-plugin' ),
			),
			'sandbox_merchant_id' => array(
				'title'       => __( 'Sandbox - Merchant ID', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This is the sandbox account merchant ID', 'flo-checkout-plugin' ),
				'default'     => '',
			),
			'sandbox_password'    => array(
				'title'       => __( 'Sandbox password', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This is the sandbox password. Follow these instructions create accounts https://tinyurl.com/waoxrnq.', 'flo-checkout-plugin' ),
				'default'     => '',
			),
			'live_merchant_id'    => array(
				'title'       => __( 'Live - Merchant ID', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This is the live account merchant ID .', 'flo-checkout-plugin' ),
				'default'     => '',
			),
			'live_password'       => array(
				'title'       => __( 'Live password', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This is the live password. Follow these instructions create accounts https://tinyurl.com/waoxrnq.', 'flo-checkout-plugin' ),
				'default'     => '',
			),
			'log_enabled'         => array(
				'title'       => __( 'Debug Log', 'flo-checkout-plugin' ),
				'label'       => $label,
				'description' => $description,
				'type'        => 'checkbox',
				'default'     => 'no',
			),
		);
	}

	/**
	 * Enqueue scripts and styles.
	 *
	 * @return void
	 */
	public function payment_scripts(): void {
		if ( 'no' === $this->enabled ) {
			return;
		}

		if ( empty( $this->merchant_id ) || empty( $this->password ) ) {
			return;
		}

		wp_enqueue_script(
			'resursomni',
			Shared::instance()->assets->get_package_assets_url() . 'js/resurscheckout.js',
			array(),
			filemtime( Shared::instance()->assets->get_package_assets_path() . 'js/resurscheckout.js' ),
			''
		);

		$ajax_object       = array( 'ajax_url' => admin_url( 'admin-ajax.php' ) );
		$translation_array = array(
			'payError' => __( 'There is error on payment, please try again later', 'flo-checkout-plugin' ),
		);

		wp_enqueue_script(
			'northmill-flo-invoice-js',
			Shared::instance()->assets->get_package_assets_url() . 'js/flo_invoice_pay.js',
			array( 'jquery' ),
			filemtime( Shared::instance()->package_info->paths->get_path() . 'assets/js/flo_invoice_pay.js' ),
			true
		);
		wp_enqueue_script(
			'northmill-flo-invoice-js-customs',
			Shared::instance()->assets->get_package_assets_url() . 'js/flo_invoice_pay_customs.js',
			array( 'jquery' ),
			filemtime( Shared::instance()->package_info->paths->get_path() . 'assets/js/flo_invoice_pay_customs.js' ),
			true
		);
		wp_enqueue_style(
			'northmill-flo-pay-css',
			Shared::instance()->assets->get_package_assets_url() . 'css/flo_invoice_pay.css',
			array(),
			filemtime( Shared::instance()->package_info->paths->get_path() . 'assets/css/flo_invoice_pay.css' )
		);

		wp_localize_script( 'northmill-flo-invoice-js', 'ajax_object', $ajax_object );
		wp_localize_script( 'northmill-flo-invoice-js-customs', 'ajax_object', $ajax_object );
		wp_localize_script( 'northmill-flo-invoice-js', 'resurs_omni_vars', $this->get_variable_localization() );
		wp_localize_script( 'northmill-flo-invoice-js', 'resurs_fallback', array( 'url' => $this->get_fallback_url() ) );
		wp_localize_script( 'northmill-flo-invoice-js', 'resurs_translations', $translation_array );
	}

	/**
	 * Get localization variables for the payment form.
	 *
	 * @return array
	 */
	private function get_variable_localization(): array {
		$omni_book_url   = get_site_url() . '/';
		$omni_book_nonce = add_query_arg( 'wc-ajax', 'checkout', $omni_book_url );

		if ( $this->is_sandbox ) {
			$omni_url = $this->auth->getCheckoutUrl( RESURS_ENVIRONMENTS::ENVIRONMENT_TEST );
		} else {
			$omni_url = $this->auth->getCheckoutUrl( RESURS_ENVIRONMENTS::ENVIRONMENT_PRODUCTION );
		}

		$omni_ref_age = null;
		if ( $this->woo_session_valid ) {
			$omni_ref         = WC()->session->get( self::OMNI_REF );
			$omni_ref_created = WC()->session->get( 'omniRefCreated' );
			$omni_ref_age     = intval( WC()->session->get( 'omniRefAge' ) );
		}

		$gateways = WC()->payment_gateways()->get_available_payment_gateways();

		return array(
			'RESURSCHECKOUT_IFRAME_URL' => $omni_url,
			'ACCEPT_CHECKOUT_PREFIXES'  => '',
			'RESURSCHECKOUT'            => get_site_url() . '/',
			'OmniPreBookUrl'            => $omni_book_nonce,
			'OmniRef'                   => ! empty( $omni_ref ) ? $omni_ref : null,
			'OmniRefCreated'            => ! empty( $omni_ref_created ) ? $omni_ref_created : null,
			'OmniRefAge'                => $omni_ref_age,
			'isResursTest'              => $this->sandbox,
			'gatewayCount'              => ( is_array( $gateways ) ? count( $gateways ) : 0 ),
			'postidreference'           => '1',
		);
	}

	/**
	 * Localize the fallback URL for the payment form.
	 *
	 * @return string
	 */
	private function get_fallback_url(): string {
		$omni_fallback_url = get_site_url() . '/';

		$omni_ref = WC()->session->get( self::OMNI_REF );

		$omni_fallback_url = add_query_arg( 'wc-api', 'Flo_Checkout\Gateways\Resurs_Invoice_Pay', $omni_fallback_url );
		$omni_fallback_url = add_query_arg( 'event-type', 'fallPayment', $omni_fallback_url );
		$omni_fallback_url = add_query_arg( 'payment_id', $omni_ref, $omni_fallback_url );

		return add_query_arg( 'omnicheckout_callback_mode', wp_create_nonce( 'omnicheckout_callback_nonce' ), $omni_fallback_url );
	}

	/**
	 * Validate the session.
	 *
	 * @return void
	 */
	private function validate_session(): void {
		if ( isset( WC()->session ) ) {
			$this->woo_session_valid = true;
		}
	}

	/**
	 * Initialize session variables.
	 *
	 * @return void
	 */
	private function init_session_variables(): void {
		if ( $this->woo_session_valid ) {

			$omni_ref_created     = WC()->session->get( 'omniRefCreated' );
			$current_omni_ref_age = time() - $omni_ref_created;

			$current_omni_ref = WC()->session->get( self::OMNI_REF );
			if ( empty( $current_omni_ref ) ) {
				/*
				* Empty references, create
				*/

				WC()->session->set( self::OMNI_REF, $this->pay_id );
				WC()->session->set( 'omniRefCreated', time() );
				WC()->session->set( 'omniRefAge', $current_omni_ref_age );
			}
		}
	}

	/**
	 * Load the Resurs form
	 */
	public function resurs_omni_checkout_form_location(): void {
		if ( 'no' === $this->enabled ) {
			return;
		}

		// no reason to enqueue JavaScript if API keys are not set.
		if ( empty( $this->merchant_id ) || empty( $this->password ) ) {
			return;
		}

		// Prepare the frame.
		try {
			$frame_display = '<div id="resurs-checkout-container" style="display:none;">' . $this->resurs_omni_checkout_create_frame() . '</div>';
		} catch ( Exception $e ) {
			$frame_content = __(
				'We are unable to load Resurs Checkout for the moment. Please try again later.',
				'flo-checkout-plugin'
			);
			$frame_display = '<div class="col2-set label-warning" style="border:1px solid red; text-align: center;" id="resurs-checkout-container">' . $frame_content . "<!-- \n" . $e->getMessage() . ' --></div>';
			$this->reset_session();
		}
		echo $frame_display; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * Set environment base select.
	 *
	 * @return void
	 */
	private function set_environment_base_select(): void {
		if ( $this->is_sandbox ) {
			$this->auth->setEnvironment( RESURS_ENVIRONMENTS::ENVIRONMENT_TEST );
		} else {
			$this->auth->setEnvironment( RESURS_ENVIRONMENTS::ENVIRONMENT_PRODUCTION );
		}
	}

	/**
	 * Handle authentication.
	 *
	 * @return void
	 * @throws Exception If the credentials are incorrect.
	 */
	private function authentication(): void {
		if ( $this->merchant_id && $this->password && 'no' !== $this->enabled ) {
			$env = $this->is_sandbox ? RESURS_ENVIRONMENTS::ENVIRONMENT_TEST : RESURS_ENVIRONMENTS::ENVIRONMENT_PRODUCTION;

			$this->auth = new ResursBank( $this->merchant_id, $this->password, $env );
			$this->auth->setWsdlCache( true );

			if ( $this->is_credentials_valid() ) {
				$this->set_environment_base_select();
				$this->create_payment_id();
				$this->update_register_callbacks();
			} else {
				WC_Admin_Settings::add_error( __( 'Credentials are incorrect, please verify.', 'flo-checkout-plugin' ) );
			}
		}
	}

	/**
	 * Check whether credentials are valid.
	 *
	 * @return bool
	 */
	private function is_credentials_valid(): bool {
		try {
			return $this->auth->validateCredentials();
		} catch ( Exception ) {
			return false;
		}
	}

	/**
	 * Create payment ID.
	 *
	 * @return void
	 */
	private function create_payment_id(): void {
		$this->pay_id = $this->auth->getPreferredPaymentId();
	}

	/**
	 * Create resurs iframe.
	 *
	 * @return string
	 * @throws Exception When the payment cannot be created.
	 */
	private function resurs_omni_checkout_create_frame(): string {
		$this->auth->setPreferredPaymentFlowService( RESURS_FLOW_TYPES::RESURS_CHECKOUT );
		// Create backend order for Iframe from cart details Which will create payment id based on that.
		$book_data_omni = self::create_resurs_omni_order();

		$this->log(
			'createResursOmniOrder object',
			array(
				'data' => $book_data_omni,
			),
			Log_Level_Enum::INFO
		);

		$omni_ref = WC()->session->get( self::OMNI_REF );

		try {
			$customer_id = self::get_resurs_woo_customer_id();

			if ( ! is_null( $customer_id ) ) {
				$this->auth->setMetaData( 'CustomerId', $customer_id );
			}

			// If success payment created it will generate Iframe and else if will false.
			$order_reference_results = $this->auth->createPayment( $omni_ref, $book_data_omni );

			// This is similar output as this https://test.resurs.com/docs/display/ecom/Resurs+Checkout#ResursCheckout-/payments/{orderReference}-POST.
			$result_iframe  = is_string( $order_reference_results ) ? $order_reference_results : '';
			$result_iframe .= '<noscript><b>' .
							__(
								'Resurs Checkout will not work properly without Javascript functions enabled',
								'flo-checkout-plugin'
							)
							. '</b></noscript>';
			WC()->session->set( self::OMNI_CHECKOUT_CREATE, 'yes' );
		} catch ( Exception $e ) {
			$error_unable  = __(
				'We are unable to load Resurs Checkout for the moment. Please try again later.',
				'flo-checkout-plugin'
			);
			$result_iframe = '<div class="col2-set label-warning" style="border:1px solid red; text-align: center;" id="resurs-checkout-container">' . $error_unable . "<!-- \n" . $e->getMessage() . ' --></div>';
			$this->reset_session();

			$this->log(
				'Payment error',
				array(
					'error' => $e->getMessage(),
				),
				Log_Level_Enum::ERROR
			);
		}

		return $result_iframe;
	}

	/**
	 * Reset session.
	 *
	 * @return void
	 */
	private function reset_session(): void {
		WC()->session->set( self::OMNI_REF, null );
		WC()->session->set( 'omniId', null );
		WC()->session->set( 'omniRefCreated', null );
		WC()->session->set( 'omniRefAge', null );
		WC()->session->set( self::OMNI_CHECKOUT_CREATE, null );
	}

	/**
	 * Get data from customer object.
	 *
	 * @param array  $billing_object Billing object.
	 * @param string $key            Key.
	 *
	 * @return mixed|string
	 */
	private function get_data_from_customer_object( array $billing_object, string $key ): mixed {
		$return = '';
		if ( isset( $billing_object[ $key ] ) ) {
			$return = $billing_object[ $key ];
		}

		return $return;
	}

	/**
	 * Create backend order for Iframe from cart details
	 * Which will create payment id based on that
	 *
	 * @throws Exception If customer cannot be read/found and $data is set.
	 */
	private function create_resurs_omni_order(): array {
		global $woocommerce;
		$spec_lines = self::get_payment_spec( $woocommerce->cart, true );

		$get_urls = $this->create_return_urls();

		$book_data_omni = array(
			'orderLines' => $spec_lines,
		);

		// If user is logged in, fetch data and send to RCO.
		// Note: Cookies may override this feature.
		if ( is_user_logged_in() ) {
			$this->auth->setFlag( 'KEEP_RCO_DELIVERY', true );
			$logged_in_customer = new WC_Customer( get_current_user_id() );
			$logged_in_billing  = $logged_in_customer->get_billing();
			$this->auth->setCustomer(
				'',
				'',
				$this->get_data_from_customer_object( $logged_in_billing, 'phone' ),
				$this->get_data_from_customer_object( $logged_in_billing, 'email' ),
				'NATURAL',
				''
			);
			if ( ! empty( $logged_in_billing['first_name'] ) && ! empty( $logged_in_billing['last_name'] ) && ! empty( $logged_in_billing['address_1'] ) && ! empty( $logged_in_billing['country'] ) ) {
				$this->auth->setDeliveryAddress(
					$this->get_data_from_customer_object(
						$logged_in_billing,
						'first_name'
					) . ' ' . $this->get_data_from_customer_object(
						$logged_in_billing,
						'last_name'
					),
					$this->get_data_from_customer_object( $logged_in_billing, 'first_name' ),
					$this->get_data_from_customer_object( $logged_in_billing, 'last_name' ),
					$this->get_data_from_customer_object( $logged_in_billing, 'address_1' ),
					$this->get_data_from_customer_object( $logged_in_billing, 'address_2' ),
					$this->get_data_from_customer_object( $logged_in_billing, 'city' ),
					$this->get_data_from_customer_object( $logged_in_billing, 'postcode' ),
					$this->get_data_from_customer_object( $logged_in_billing, 'country' )
				);
			}
		}

		$this->auth->setSigning(
			$get_urls['successUrl'],
			$get_urls['backUrl'],
			false,
			$get_urls['backUrl'],
			RESURS_URL_ENCODE_TYPES::NONE
		);

		if ( isset( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] ) {
			$shop_url = 'https';
		} else {
			$shop_url = 'http';
		}

		// Here append the common URL characters.
		$shop_url .= '://';

		// Append the host(domain name, ip) to the URL.
		$shop_url .= $_SERVER['HTTP_HOST'];

		$book_data_omni['shopUrl'] = $shop_url;

		return $book_data_omni;
	}

	/**
	 * Get current user ID.
	 *
	 * @return int|null
	 */
	private static function get_resurs_woo_customer_id(): ?int {
		$return = null;

		if ( function_exists( 'wp_get_current_user' ) ) {
			$current_user = wp_get_current_user();
		} else {
			// phpcs:ignore WordPress.WP.DeprecatedFunctions
			$current_user = get_currentuserinfo();
		}

		if ( isset( $current_user->ID ) ) {
			$return = $current_user->ID;
		}

		return $return;
	}

	/**
	 * Get payment specification.
	 *
	 * @param WC_Cart $cart            Cart object.
	 * @param bool    $spec_lines_only Whether to return only the spec lines.
	 *
	 * @return array
	 */
	private static function get_payment_spec( WC_Cart $cart, bool $spec_lines_only = false ): array {
		global $woocommerce;

		$shipping_type = '';

		$session_shipping_method = WC()->session->get( 'chosen_shipping_methods' );
		if ( $session_shipping_method ) {
			$shipping_method = WC()->shipping->get_shipping_methods();

			$session_methods = substr(
				$session_shipping_method[0],
				0,
				strrpos( $session_shipping_method[0], ':' )
			);

			$shipping_type = $shipping_method[ $session_methods ]->id;
		}

		$spec_lines = self::get_spec_lines( $cart->get_cart() );

		$shipping       = (float) $cart->shipping_total;
		$shipping_tax   = (float) $cart->shipping_tax_total;
		$shipping_total = $shipping + $shipping_tax;

		try {
			$shipping_tax_pct = round( $shipping_tax / $shipping, 2 ) * 100;
		} catch ( DivisionByZeroError ) {
			$shipping_tax_pct = 0;
		}

		$spec_lines[] = array(
			'id'                   => 'frakt',
			'artNo'                => '00_frakt',
			'description'          => $shipping_type,
			'quantity'             => '1',
			'unitMeasure'          => '',
			'unitAmountWithoutVat' => $shipping,
			'vatPct'               => $shipping_tax_pct,
			'totalVatAmount'       => $shipping_tax,
			'totalAmount'          => $shipping_total,
			'type'                 => 'SHIPPING_FEE',
		);

		$fees = $cart->get_fees();
		if ( is_array( $fees ) ) {
			foreach ( $fees as $fee ) {
				if ( $fee->tax > 0 ) {
					$rate = ( $fee->tax / $fee->amount ) * 100;
				} else {
					$rate = 0;
				}
				if ( ! empty( $fee->id ) && ( $fee->amount > 0 || $fee->amount < 0 ) ) {
					$spec_lines[] = array(
						'id'                   => $fee->id,
						'artNo'                => $fee->id,
						'description'          => $fee->name,
						'quantity'             => 1,
						'unitMeasure'          => '',
						'unitAmountWithoutVat' => $fee->amount,
						'vatPct'               => ! is_nan( $rate ) ? $rate : 0,
						'totalVatAmount'       => $fee->tax,
						'totalAmount'          => $fee->amount + $fee->tax,
						'type'                 => 'ORDER_LINE',
					);
				}
			}
		}
		if ( wc_coupons_enabled() ) {
			$coupons = $cart->get_coupons();
			if ( is_array( $coupons ) && count( $coupons ) > 0 ) {
				$coupon_values     = $cart->coupon_discount_amounts;
				$coupon_tax_values = $cart->coupon_discount_tax_amounts;

				/**
				 * Loop over the coupons and add them to the spec lines.
				 *
				 * @var  $code
				 * @var  $coupon WC_Coupon
				 */
				foreach ( $coupons as $code => $coupon ) {
					$post               = get_post( $coupon->get_id() );
					$coupon_id          = $coupon->get_id();
					$coupon_code        = $coupon->get_code();
					$coupon_description = $post->post_excerpt;
					if ( empty( $coupon_description ) ) {
						$coupon_description = $coupon_code . '_' . __(
							'coupon',
							'flo-checkout-plugin'
						);
					}
					$spec_lines[] = array(
						'id'                   => $coupon_id,
						'artNo'                => $coupon_code,
						'description'          => $coupon_description,
						'quantity'             => 1,
						'unitMeasure'          => '',
						'unitAmountWithoutVat' => ( 0 - (float) $coupon_values[ $code ] ) + ( 0 - (float) $coupon_tax_values[ $code ] ),
						'vatPct'               => 0,
						'totalVatAmount'       => 0,
						'totalAmount'          => ( 0 - (float) $coupon_values[ $code ] ) + ( 0 - (float) $coupon_tax_values[ $code ] ),
						'type'                 => 'DISCOUNT',
					);
				}
			}
		}
		$our_payment_spec_calc = self::calculate_spec_line_amount( $spec_lines );
		if ( ! $spec_lines_only ) {
			$payment_spec = array(
				'specLines'      => $spec_lines,
				'totalAmount'    => $our_payment_spec_calc['totalAmount'],
				'totalVatAmount' => $our_payment_spec_calc['totalVatAmount'],
			);
		} else {
			return $spec_lines;
		}

		return $payment_spec;
	}

	/**
	 * Calculate the total amount and VAT amount for the spec lines.
	 *
	 * @param array $spec_line The spec lines.
	 *
	 * @return array
	 */
	private static function calculate_spec_line_amount( array $spec_line = array() ): array {
		$set_payment_spec = array(
			'totalAmount'    => 0,
			'totalVatAmount' => 0,
		);

		if ( is_array( $spec_line ) && count( $spec_line ) ) {
			foreach ( $spec_line as $row ) {
				$set_payment_spec['totalAmount']    += $row['totalAmount'];
				$set_payment_spec['totalVatAmount'] += $row['totalVatAmount'];
			}
		}

		return $set_payment_spec;
	}

	/**
	 * Get spec lines.
	 *
	 * @param array $cart Cart data.
	 *
	 * @return array
	 */
	private static function get_spec_lines( array $cart ): array {
		$spec_lines = array();
		foreach ( $cart as $item ) {
			/**
			 * Cart item product.
			 *
			 * @var WC_Product $data
			 */
			$data = $item['data'];

			$_tax        = new WC_Tax();
			$tax_class   = $data->get_tax_class();
			$rates_array = $_tax->get_rates( $tax_class );
			$rates       = ! empty( $rates_array ) ? array_shift( $rates_array ) : null;
			if ( isset( $rates['rate'] ) ) {
				$vat_pct = (float) $rates['rate'];
			} else {
				$vat_pct = 0;
			}

			$price_ex_tax     = ( ! ( self::is_wc_version() ) ? $data->get_price_excluding_tax() : wc_get_price_excluding_tax( $data ) );
			$total_vat_amount = ( $price_ex_tax * ( $vat_pct / 100 ) );
			$book_art_id      = $data->get_id();
			$post_title       = $data->get_title();

			$art_description = empty( $post_title ) ?
				__( 'Article description missing', 'flo-checkout-plugin' ) :
				$post_title;

			$spec_lines[] = array(
				'id'                   => $book_art_id,
				'artNo'                => $book_art_id,
				'description'          => $art_description,
				'quantity'             => $item['quantity'],
				'unitMeasure'          => '',
				'unitAmountWithoutVat' => $price_ex_tax,
				'vatPct'               => $vat_pct,
				'totalVatAmount'       => ( $price_ex_tax * ( $vat_pct / 100 ) ),
				'totalAmount'          => ( ( $price_ex_tax * $item['quantity'] ) + ( $total_vat_amount * $item['quantity'] ) ),
				'type'                 => 'ORDER_LINE',
			);
		}

		return $spec_lines;
	}

	/**
	 * Create return URLs.
	 *
	 * @return array
	 */
	private function create_return_urls(): array {
		try {
			return array(
				'successUrl' => $this->get_resurs_omni_url( true ),
				'backUrl'    => $this->get_resurs_omni_url(),
			);
		} catch ( Exception ) {
			return array();
		}
	}

	/**
	 * Get Resurs Omni URL.
	 *
	 * @param bool $is_success Whether the URL is for a successful payment.
	 *
	 * @return string
	 */
	private function get_resurs_omni_url( bool $is_success = false ): string {
		$omni_success_url = get_site_url() . '/';

		$omni_ref = WC()->session->get( self::OMNI_REF );

		$omni_success_url = add_query_arg( 'wc-api', 'Flo_Checkout\Gateways\Resurs_Invoice_Pay', $omni_success_url );
		$omni_success_url = add_query_arg( 'event-type', 'iframe_checkout', $omni_success_url );
		$omni_success_url = add_query_arg( 'payment_id', $omni_ref, $omni_success_url );
		$omni_success_url = add_query_arg( 'set-no-session', '1', $omni_success_url );
		$omni_success_url = add_query_arg( 'success', $is_success ? 1 : 0, $omni_success_url );

		return add_query_arg( 'omnicheckout_callback_mode', wp_create_nonce( 'omnicheckout_callback_nonce' ), $omni_success_url );
	}

	/**
	 * Check WooCommerce version.
	 *
	 * @param string $version WooCommerce version.
	 *
	 * @return bool
	 */
	private static function is_wc_version( string $version = '3.0' ): bool {
		if ( class_exists( 'WooCommerce' ) ) {
			global $woocommerce;
			if ( version_compare( $woocommerce->version, $version, '>=' ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Handle the callback.
	 *
	 * @return void
	 */
	public function handle_resurs_callback(): void {

		$url_arr          = wp_parse_url( $_SERVER['REQUEST_URI'] );
		$url_arr['query'] = str_replace( 'amp;', '', $url_arr['query'] );
		parse_str( $url_arr['query'], $request );
		if ( ! is_array( $request ) ) {
			$request = array();
		}
		if ( ! count( $request ) && isset( $_GET['event-type'] ) ) {
			$request = $_GET;
		}
		$event_type = $request['event-type'];

		if ( 'fallPayment' === $event_type ) {
			$this->reset_sessions();
		}

		if ( 'iframe_checkout' === $event_type ) {
			$this->iframe_checkout();
		}

		$current_validation = $this->validate_callback( $request );

		if ( $current_validation ) {

			if ( 'iframe_checkout' !== $event_type ) {
				$this->callback_events( $_REQUEST );
			}
		}
	}

	/**
	 * Check result of Iframe checkout, book the payment and complete the order
	 */
	private function iframe_checkout(): void {
		$url_arr          = wp_parse_url( $_SERVER['REQUEST_URI'] );
		$url_arr['query'] = str_replace( 'amp;', '', $url_arr['query'] );
		parse_str( $url_arr['query'], $request );

		$order_id = $this->wc_get_order_id_by_payment_id( $request['payment_id'] );

		try {
			$resurs_payment_obj = $this->auth->getPayment( $request['payment_id'] );
		} catch ( Exception ) {
			$resurs_payment_obj = null;
		}

		$order = new WC_Order( $order_id );

		if ( ! $this->get_hasCall_by_order_id( (int) $order_id ) ) {
			$this->log(
				'Order response',
				array(
					'order_id'        => $order_id,
					'status'          => $resurs_payment_obj->status ?? '',
					'object'          => $resurs_payment_obj,
					'request_success' => $request['success'] ?? '',
				),
				Log_Level_Enum::INFO
			);

			if (
				1 == $request['success'] &&
				'IS_DEBITED' === $resurs_payment_obj->status
			) {
				$this->maybe_update_appointment_status( $order_id, 'paid' );
				$order->payment_complete();
				WC()->cart->empty_cart();
				$thank_you_url = $order->get_checkout_order_received_url();
				wp_safe_redirect( $thank_you_url );

			} elseif (
				1 == $request['success'] &&
				'DEBITABLE' === $resurs_payment_obj->status
			) {
				$this->maybe_update_appointment_status( $order_id, 'paid' );
				$order->payment_complete();
				WC()->cart->empty_cart();
				$thank_you_url = $order->get_checkout_order_received_url();
				wp_safe_redirect( $thank_you_url );

			} elseif (
				! $resurs_payment_obj->frozen &&
				$resurs_payment_obj->fraud &&
				1 == $request['success']
			) {
				$this->maybe_update_appointment_status( $order_id, 'pending-confirmation' );
				$order->update_status( WC_PENDING_STATUS );
				WC()->cart->empty_cart();
				$thank_you_url = $order->get_checkout_order_received_url();
				wp_safe_redirect( $thank_you_url );

			} elseif (
				$resurs_payment_obj->frozen &&
				! $resurs_payment_obj->fraud &&
				1 == $request['success']
			) {
				$this->maybe_update_appointment_status( $order_id, 'pending-confirmation' );
				$order->update_status( WC_PENDING_STATUS );
				WC()->cart->empty_cart();
				$thank_you_url = $order->get_checkout_order_received_url();
				wp_safe_redirect( $thank_you_url );

			} elseif ( 0 == $request['success'] ) {
				$this->maybe_update_appointment_status( $order_id, 'cancelled' );
				$order->update_status( WC_CANCELLED_STATUS );
				wc_add_notice( __( 'Payment failed. Please contact bank for information', 'flo-checkout-plugin' ), 'error' );
				wp_safe_redirect( wc_get_checkout_url() );

			} else {
				$this->maybe_update_appointment_status( $order_id, 'cancelled' );
				$order->update_status( WC_CANCELLED_STATUS );
				wc_add_notice( __( 'Payment failed. Please contact bank for information', 'flo-checkout-plugin' ), 'error' );
				wp_safe_redirect( wc_get_checkout_url() );
			}
		} else {
			WC()->cart->empty_cart();
			wc_add_notice( __( 'This is a already recorded payment.', 'flo-checkout-plugin' ), 'error' );
			wp_safe_redirect( wc_get_checkout_url() );
		}

		// Add hasCall meta key with payment id for which is stop runing next time.
		update_post_meta( $order_id, 'hasCall', $request['payment_id'] );

		$this->reset_session();

		$this->init_session_variables();
	}

	/**
	 * Reset sessions.
	 *
	 * @return never
	 * @throws Exception When iframe can't be created.
	 */
	#[NoReturn] private function reset_sessions(): never {

		$order_id = $this->wc_get_order_id_by_payment_id( $_GET['payment_id'] );

		$order = new WC_Order( $order_id );
		$this->maybe_update_appointment_status( $order_id, 'cancelled' );
		$order->update_status( WC_CANCELLED_STATUS );

		update_post_meta( $order_id, 'hasCall', $_GET['payment_id'] );

		$this->reset_session();

		$this->init_session_variables();

		$return_result['success']     = true;
		$return_result['errorString'] = __( 'Denied by Resurs', 'flo-checkout-plugin' );
		$return_result['errorCode']   = '200';
		$return_result['newIframe']   = $this->resurs_omni_checkout_create_frame();

		wp_send_json( $return_result, $return_result['errorCode'] );
		wp_die();
	}

	/**
	 * Maybe update appointment status.
	 *
	 * @param int    $order_id Order ID.
	 * @param string $new_status New status.
	 *
	 * @return void
	 */
	public function maybe_update_appointment_status( int $order_id, string $new_status ): void {

		if ( class_exists( 'WC_Appointment_Data_Store' ) ) {
			$appointment_id = WC_Appointment_Data_Store::get_appointment_ids_from_order_id( $order_id );

			if ( $appointment_id ) {
				/**
				 * Existence checked by class check.
				 *
				 * @noinspection PhpUndefinedFunctionInspection
				 */
				$appointment = get_wc_appointment( $appointment_id[0] );
				$appointment->set_status( $new_status );
				$appointment->save();
			}
		}
	}

	/**
	 * Validate callback.
	 *
	 * @param array $request Request data.
	 *
	 * @return void
	 * @throws WC_Data_Exception If payment ID could not be set.
	 */
	public function callback_events( array $request ): void {

		$event_type    = $request['event-type'];
		$return_result = array();

		$order_id = $this->wc_get_order_id_by_payment_id( $request['paymentId'] );

		$resurs_payment_obj = $this->auth->getPayment( $request['paymentId'] );

		$this->log(
			'Callback event',
			array(
				'order_id'   => $order_id,
				'event_type' => $event_type,
				'payment_id' => $request['paymentId'],
				'payment'    => $resurs_payment_obj,
			),
			Log_Level_Enum::INFO
		);

		$order = new WC_Order( $order_id );

		if ( is_array( $resurs_payment_obj->status ) ) { // This is instance update case.
			if ( 'IS_DEBITED' !== $resurs_payment_obj->status[1] ) {
				$return_result = $this->get_result_array( $event_type, $order, $order_id, $return_result );
			} else {
				if ( $resurs_payment_obj->id ) {
					$order->set_transaction_id( $resurs_payment_obj->id );
				}
				$order->payment_complete();
				$this->maybe_update_appointment_status( $order_id, 'paid' );
				$return_result['success']     = true;
				$return_result['successCode'] = '204';

				$this->log( 'Order status', array( 'status' => WC_COMPLETED_STATUS ), Log_Level_Enum::INFO );
			}
		} elseif ( 'DEBITABLE' !== $resurs_payment_obj->status ) {
			$return_result = $this->get_result_array( $event_type, $order, $order_id, $return_result );
		} else {
			if ( $resurs_payment_obj->id ) {
				$order->set_transaction_id( $resurs_payment_obj->id );
			}
			$order->payment_complete();
			$this->maybe_update_appointment_status( $order_id, 'paid' );
			$return_result['success']     = true;
			$return_result['successCode'] = '204';

			$this->log( 'Order status', array( 'status' => WC_COMPLETED_STATUS ), Log_Level_Enum::INFO );
		}

		wp_send_json( $return_result, $return_result['successCode'] );
	}

	/**
	 * Get result array.
	 *
	 * @param string      $event_type Event type.
	 * @param WC_Order    $order       Order object.
	 * @param string|null $order_id    Order ID.
	 * @param array       $return_result Return result to update.
	 *
	 * @return array
	 */
	private function get_result_array( string $event_type, WC_Order $order, ?string $order_id, array $return_result ): array {
		switch ( $event_type ) {
			case 'UNFREEZE':
				$order->update_status( WC_PENDING_STATUS );
				$this->maybe_update_appointment_status( $order_id, 'pending-confirmation' );
				$return_result['success']     = true;
				$return_result['successCode'] = '204';

				$this->log( 'Order status', array( 'status' => WC_PENDING_STATUS ), Log_Level_Enum::INFO );

				break;
			case 'ANNULMENT':
				$order->update_status( WC_CANCELLED_STATUS );
				$this->maybe_update_appointment_status( $order_id, 'cancelled' );
				$return_result['success']     = true;
				$return_result['successCode'] = '204';

				$this->log( 'Order status', array( 'status' => WC_CANCELLED_STATUS ), Log_Level_Enum::INFO );

				break;
		}

		return $return_result;
	}

	/**
	 * Get order ID by payment ID.
	 *
	 * @param string $payment_id Payment ID.
	 *
	 * @return string|null
	 */
	private function wc_get_order_id_by_payment_id( string $payment_id = '' ): ?string {
		global $wpdb;

		return $wpdb->get_var(
			$wpdb->prepare(
				"SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = 'paymentId' and meta_value = %s",
				$payment_id
			)
		);
	}

	/**
	 * Get hasCall by order ID.
	 *
	 * @param int $order_id Order ID.
	 *
	 * @return string|null
	 */
	private function get_hasCall_by_order_id( int $order_id ): ?string {
		global $wpdb;

		return $wpdb->get_var(
			$wpdb->prepare(
				"SELECT meta_value FROM {$wpdb->prefix}postmeta WHERE meta_key = 'hasCall' and post_id = %s",
				$order_id
			)
		);
	}

	/**
	 * Get callback types.
	 *
	 * @return array[]
	 */
	private function get_callback_types(): array {
		return array(
			'UNFREEZE'  => array(
				'uri_components'    => array(
					'paymentId' => 'paymentId',
				),
				'digest_parameters' => array(
					'paymentId' => 'paymentId',
				),
			),
			'ANNULMENT' => array(
				'uri_components'    => array(
					'paymentId' => 'paymentId',
				),
				'digest_parameters' => array(
					'paymentId' => 'paymentId',
				),
			),
			'BOOKED'    => array(
				'uri_components'    => array(
					'paymentId' => 'paymentId',
				),
				'digest_parameters' => array(
					'paymentId' => 'paymentId',
				),
			),
			'UPDATE'    => array(
				'uri_components'    => array(
					'paymentId' => 'paymentId',
				),
				'digest_parameters' => array(
					'paymentId' => 'paymentId',
				),
			),
		);
	}

	/**
	 * Update register callbacks.
	 *
	 * @return void
	 * @throws Exception On callback registration error.
	 */
	public function update_register_callbacks(): void {
		foreach ( $this->get_callback_types() as $callback => $options ) {
			$this->register_custom_callbacks( $callback, $options );
		}
	}

	/**
	 * Register custom callbacks.
	 *
	 * @param string $type    Type.
	 * @param array  $options Options.
	 *
	 * @throws Exception On callback registration error.
	 */
	public function register_custom_callbacks( string $type, array $options ): string {
		try {
			$home_url      = get_site_url() . '/';
			$uri_template  = $home_url;
			$uri_template  = add_query_arg( 'wc-api', 'Flo_Checkout\Gateways\Resurs_Invoice_Pay', $uri_template );
			$uri_template .= '&event-type=' . $type;
			foreach ( $options['uri_components'] as $key => $value ) {
				$uri_template .= '&' . $key . '={' . $value . '}';
			}
			$uri_template .= '&digest={digest}';

			$callback_type = $this->auth->getCallbackTypeByString( $type );
			$salt          = get_option( 'salt_key' );
			if ( ! $salt ) {
				$salt = uniqid( wp_rand(), true );
				add_option( 'salt_key', $salt );
			}
			$this->auth->setCallbackDigestSalt( $salt );
			$this->auth->setRegisterCallback( $callback_type, $uri_template );
		} catch ( Exception $e ) {
			throw new Exception( esc_html( $e ) );
		}

		return $uri_template;
	}

	/**
	 * Validate the callback was related this site event
	 *
	 * @param array $request Request data.
	 */
	private function validate_callback( array $request ): string {
		$success = null;

		$payment_id = $request['paymentId'] ?? null;
		$digest     = $request['digest'] ?? null;
		$result     = $request['result'] ?? null;

		$test_digest_array = array( $payment_id );

		foreach ( $test_digest_array as $test_id ) {
			if (
				$this->auth->getValidatedCallbackDigest(
					$test_id,
					get_option( 'salt_key' ),
					$digest,
					$result
				)
			) {
				$success = $test_id;
				break;
			}
		}

		return (string) $success;
	}
}
