<?php

namespace POPxPMPro\Functions;

use WcElectronInvoice\Admin\Settings\OptionPage;
use WcElectronInvoice\Providers\OrderQuery;
use WcElectronInvoice\WooCommerce\Fields\GeneralFields;
use WcElectronInvoice\WooCommerce\Fields\InvoiceFields;
use WcElectronInvoice\WooCommerce\Fields\TaxFields;
use function WcElectronInvoice\Functions\filterInput;
use function WcElectronInvoice\Functions\normalizeVatForVies;
use function WcElectronInvoice\Functions\sanitize;
use function WcElectronInvoice\Functions\sanMarinoSupport;

defined( 'ABSPATH' ) || die( 'Not allowed.' );

/**
 * Utils
 */
class Utils {
	/**
	 * In-request PMPro order processing locks.
	 *
	 * Used to avoid recursive `pmpro_updated_order` / `pmpro_added_order`
	 * re-entry when POP updates PMPro order meta and PMPro triggers the same
	 * hook again during the same request.
	 *
	 * @var array<string, bool>
	 */
	protected static $pmproOrderProcessingLocks = [];

	/**
	 * Acquire a PMPro order processing lock for the current request.
	 *
	 * @param int    $orderId
	 * @param string $scope
	 *
	 * @return bool
	 */
	public static function acquirePmproOrderProcessingLock( int $orderId, string $scope = 'default' ): bool {
		if ( $orderId < 1 ) {
			return true;
		}

		$scope   = sanitize_key( $scope ) ?: 'default';
		$lockKey = "{$scope}:{$orderId}";
		if ( isset( self::$pmproOrderProcessingLocks[ $lockKey ] ) ) {
			return false;
		}

		self::$pmproOrderProcessingLocks[ $lockKey ] = true;

		return true;
	}

	/**
	 * Release a PMPro order processing lock for the current request.
	 *
	 * @param int    $orderId
	 * @param string $scope
	 *
	 * @return void
	 */
	public static function releasePmproOrderProcessingLock( int $orderId, string $scope = 'default' ): void {
		if ( $orderId < 1 ) {
			return;
		}

		$scope   = sanitize_key( $scope ) ?: 'default';
		$lockKey = "{$scope}:{$orderId}";
		unset( self::$pmproOrderProcessingLocks[ $lockKey ] );
	}

	/**
	 * pmProViesCheck
	 *
	 * @return void
	 */
	public static function pmProViesCheck() {
		$customerInvoiceType = trim( sanitize( filterInput( $_POST, 'invoice_type' ) ) ) ?: null;
		$level_id            = trim( sanitize( filterInput( $_POST, 'level' ) ) ) ?: null;
		$vatNumber           = trim( sanitize( filterInput( $_POST, 'vat_number' ) ) ) ?: null;
		$country             = trim( sanitize( filterInput( $_POST, 'country' ) ) ) ?: null;

		// No need for vies control
		if ( 'IT' === $country ) {
			wp_send_json_success( [
				'log_message' => esc_html__( 'Vies control not necessary', WC_EL_INV_TEXTDOMAIN ),
				'message'     => null,
			] );
		}

		if ( empty( $vatNumber ) || empty( $country ) ) {
			wp_send_json_error( [
				'message' => esc_html__( 'Missing data for VAT verification.', WC_EL_INV_TEXTDOMAIN ),
			] );
		}

		$vatCode = normalizeVatForVies( $vatNumber, $country );

		try {
			$valid = InvoiceFields::viesCheck( $vatCode, $country );

			if ( $valid ) {
				// Remove tax label
				remove_filter( 'pmpro_format_price', 'pop_pmproFormatPrice_CouncilDirective_2006_112_EC' );
				$cost_text = '';

				if ( $level_id ) {
					// Price excludes tax
					$level = pmpro_getLevel( $level_id );
					if ( 1 == $level->cycle_number ) {
						// translators: %1$s - price, %2$s - period.
						$cost_text = sprintf( esc_html__( '%1$s per %2$s', 'paid-memberships-pro' ),
							pmpro_formatPrice( $level->billing_amount ),
							pmpro_translate_billing_period( $level->cycle_period, $level->cycle_number ) );
					} else {
						// translators: %1$s - price, %2$d - number, %3$s - period.
						$cost_text = sprintf( esc_html__( '%1$s every %2$d %3$s', 'paid-memberships-pro' ),
							pmpro_formatPrice( $level->billing_amount ), $level->cycle_number,
							pmpro_translate_billing_period( $level->cycle_period, $level->cycle_number ) );
					}
				}
				wp_send_json_success( [
					'message' => esc_html__( 'VAT valid for VIES', WC_EL_INV_TEXTDOMAIN ),
					'price'   => "{$cost_text}",
				] );
			} else {
				$cost_text = '';
				if ( $level_id ) {

					// Price includes tax
					$level          = pmpro_getLevel( $level_id );
					$billing_amount = pmpro_formatPrice( $level->billing_amount );
					if ( 1 == $level->cycle_number ) {
						// translators: %1$s - price, %2$s - period.
						$cost_text = sprintf( esc_html__( '%1$s per %2$s', 'paid-memberships-pro' ),
							$billing_amount,
							pmpro_translate_billing_period( $level->cycle_period, $level->cycle_number ) );
					} else {
						// translators: %1$s - price, %2$d - number, %3$s - period.
						$cost_text = sprintf( esc_html__( '%1$s every %2$d %3$s', 'paid-memberships-pro' ),
							$billing_amount, $level->cycle_number,
							pmpro_translate_billing_period( $level->cycle_period, $level->cycle_number ) );
					}
				}

				wp_send_json_error( [
					'message' => esc_html__( 'The VAT number entered is not valid for VIES', WC_EL_INV_TEXTDOMAIN ),
					'price'   => "{$cost_text}",
				] );
			}
		} catch ( \Exception $e ) {
			wp_send_json_error( [
				'message' => esc_html__( 'Error during VIES verification: ', WC_EL_INV_TEXTDOMAIN ) . $e->getMessage(),
			] );
		}
	}

	/**
	 * pmProConnectedInvoiceNumber
	 *
	 * @param $obj
	 *
	 * @return string|void
	 * @throws \Exception
	 */
	public static function pmProConnectedInvoiceNumber( $obj ) {
		// PMPro use Order object - return parent invoice number
		$order = OrderQuery::instance()->getProviderOrder( $obj->id, 'pmpro' );
		if ( 'refunded' === $order->get_status() ) {
			$number = $order->get_meta( 'order_number_invoice' );

			$numerationType = $order->get_billing_choice_type() ?: 'invoice';
			$choiceType     = \WcElectronInvoice\Functions\filterInput( $_GET, 'choice_type', FILTER_UNSAFE_RAW );

			if ( $numerationType === 'invoice' && 'receipt' === $choiceType ) {
				$numerationType = 'receipt';
			} elseif ( 'invoice' === $choiceType ) {
				$numerationType = 'invoice';
			}

			$number = \WcElectronInvoice\Functions\buildFormattedInvoiceNumberValue( $order, $numerationType, (int) $number );

			$formattedNumber = $order->get_meta( "shop_order_formatted_number_{$order->get_id()}" ) ?? null;
			if ( $formattedNumber ) {
				return $formattedNumber;
			}

			return $number;
		}
	}

	/**
	 * pmProGetNetSubscriptionAmount
	 *
	 * @param float $amount
	 * @param int $order_id
	 *
	 * @return float|mixed
	 * @throws \Exception
	 */
	public static function pmProGetNetSubscriptionAmount( float $amount, int $order_id, $provider = 'pmpro' ) {
		if ( empty( $order_id ) ) {
			return $amount;
		}

		if ( 'pmpro' !== $provider ) {
			return $amount;
		}

		$order = OrderQuery::instance()->getProviderOrder( $order_id, 'pmpro' );

		$stampDuty = floatval( $order->get_meta( 'stamp_duty_fee_value' ) );
		$rivalsa   = floatval( $order->get_meta( 'rivalsa_fee_value' ) );

		$netAmount = $amount - $stampDuty - $rivalsa;

		if ( $netAmount < 0 ) {
			$netAmount = 0;
		}

		return max( 0, $netAmount );
	}

	/**
	 * Extend duplicate-check lookup for PMPro numeration.
	 *
	 * @param bool   $exists
	 * @param int    $number
	 * @param string $optionKeyName
	 *
	 * @return bool
	 */
	public static function pmproNextNumberAlreadyAssigned( $exists, $number, $optionKeyName ) {
		if ( $exists || (int) $number < 1 ) {
			return (bool) $exists;
		}

		$lastAssigned = self::pmproLastAssignedNumberForSeries( 0, $optionKeyName );

		return ( $lastAssigned > 0 && $lastAssigned === (int) $number );
	}

	/**
	 * Contribute PMPro last assigned candidate for current series.
	 *
	 * @param array  $candidate
	 * @param string $optionKeyName
	 *
	 * @return array
	 */
	public static function pmproLastAssignedCandidateForSeries( $candidate, $optionKeyName ) {
		$candidate = \WcElectronInvoice\Functions\normalizeLastAssignedCandidate( $candidate );

		if ( 'number_next_invoice' !== $optionKeyName && 'number_next_receipt' !== $optionKeyName ) {
			return $candidate;
		}

		try {
			$provider = OrderQuery::instance()->get_provider( 'pmpro' );
			if ( ! $provider || ! method_exists( $provider, 'getOrders' ) ) {
				return $candidate;
			}

			$limit = (int) apply_filters(
				'wc_el_inv-next_number_recent_provider_orders_limit',
				200,
				'pmpro',
				$optionKeyName,
				0
			);
			if ( $limit < 1 ) {
				$limit = 1;
			}

			$orders = $provider->getOrders( [
				'status'  => [ 'completed', 'processing' ],
				'limit'   => $limit,
				'orderby' => 'date',
				'order'   => 'DESC',
			] );
			if ( ! is_array( $orders ) ) {
				return $candidate;
			}

			$seriesContext = \WcElectronInvoice\Functions\getNextNumberSeriesContext( $optionKeyName );
			$pmproCandidate = [];

			foreach ( $orders as $order ) {
				if ( ! is_object( $order ) || ! method_exists( $order, 'get_meta' ) ) {
					continue;
				}

				$invoiceNumber = (int) $order->get_meta( 'order_number_invoice', true );
				if ( $invoiceNumber < 1 ) {
					continue;
				}

				$choiceType = (string) $order->get_meta( '_billing_choice_type', true );
				$isReceipt  = ( 'receipt' === $choiceType );
				if ( 'number_next_receipt' === $optionKeyName && ! $isReceipt ) {
					continue;
				}
				if ( 'number_next_invoice' === $optionKeyName && $isReceipt ) {
					continue;
				}

				if ( \WcElectronInvoice\Functions\isOrderInNextNumberSeries( $order, $seriesContext ) ) {
					$pmproCandidate = [
						'number'    => $invoiceNumber,
						'timestamp' => (int) \WcElectronInvoice\Functions\getOrderComparableTimestamp( $order ),
						'provider'  => 'pmpro',
						'order_id'  => method_exists( $order, 'get_id' ) ? (int) $order->get_id() : 0,
					];
					break;
				}
			}
		} catch ( \Throwable $e ) {
			return $candidate;
		}

		return \WcElectronInvoice\Functions\pickBetterLastAssignedCandidate( $candidate, $pmproCandidate );
	}

	/**
	 * Legacy int-based callback kept for backward compatibility.
	 *
	 * @param int    $lastAssigned
	 * @param string $optionKeyName
	 * @param array  $currentCandidate
	 *
	 * @return int
	 */
	public static function pmproLastAssignedNumberForSeries( $lastAssigned, $optionKeyName, $currentCandidate = [] ) {
		$baseCandidate = \WcElectronInvoice\Functions\normalizeLastAssignedCandidate( array_merge(
			[
				'number' => (int) $lastAssigned,
			],
			is_array( $currentCandidate ) ? $currentCandidate : []
		) );
		$bestCandidate = self::pmproLastAssignedCandidateForSeries( $baseCandidate, $optionKeyName );

		return (int) $bestCandidate['number'];
	}

	/** ---------------------------------------------------------- **
	 * RIVALSA
	 * ----------------------------------------------------------- **/

	/**
	 * pmProAddRivalsaFee
	 *
	 * Add fee and increment - initial_payment
	 *
	 * @param $level
	 *
	 * @return mixed
	 */
	public static function pmProAddRivalsaFee( $level ) {
		if ( ! GeneralFields::isItalianRivalsaActive() ) {
			return $level;
		}

		// Rivalsa INPS
		$addRivalsa = OptionPage::init()->getOptions( 'add_rivalsa_inps' );
		if ( 'on' !== $addRivalsa ) {
			return $level;
		}

		$initialPayment = floatval( $level->initial_payment );
		if ( $initialPayment > 0 ) {
			$fee_percentage = floatval( apply_filters( 'wc_el_inv-rivalsa_inps_aliquota', 0.04 ) );

			$level->initial_payment += $initialPayment * $fee_percentage;
			$level->billing_amount  += floatval( $level->billing_amount ) * $fee_percentage;

			// Salva dati per ordine
			$level->rivalsa_fee_name  = apply_filters( 'wc_el_inv-rivalsa_inps_name', 'Rivalsa INPS' );
			$level->rivalsa_fee_value = $initialPayment * $fee_percentage;
			$level->rivalsa_applied   = true;
		}

		return $level;
	}

	/**
	 * pmProShowRivalsaNotice
	 *
	 * @param $level
	 *
	 * @return void
	 */
	public static function pmProShowRivalsaNotice( $level ) {
		if ( ! GeneralFields::isItalianRivalsaActive() ) {
			return;
		}

		$addRivalsa    = OptionPage::init()->getOptions( 'add_rivalsa_inps' );
		$vat_rate      = floatval( $level->vat_rate ?? 0 );
		$initialAmount = floatval( $level->initial_payment );

		if ( 'on' === $addRivalsa && $vat_rate == 0 && $initialAmount > 0 ) {
			$rivalsa = $level->rivalsa_fee_value ?? 0;
			$message = sprintf(
				'<div class="pmpro_rivalsa_notice"><small>%s</small></div>',
				sprintf(
				/* translators: %s = rivalsa amount */
					__( 'INPS contribution (rivalsa) of €%s has been applied to this VAT-exempt transaction.',
						WC_EL_INV_TEXTDOMAIN ),
					number_format( $rivalsa, 2 )
				)
			);

			echo apply_filters( 'wc_el_inv-pmpro_rivalsa_notice_html', $message, $level );
		}
	}

	/**
	 * pmProShowInOrderRivalsaNotice
	 *
	 * @param $invoice
	 *
	 * @return void
	 * @throws \Exception
	 */
	public static function pmProShowInOrderRivalsaNotice( $invoice ) {
		$order     = OrderQuery::instance()->getProviderOrder( $invoice->id, 'pmpro' );
		$fee_name  = $order->get_meta( 'rivalsa_fee_name' );
		$fee_value = $order->get_meta( 'rivalsa_fee_value' );

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

		printf(
			'<li><strong>%s:</strong> €%s</li>',
			esc_html__( $fee_name, WC_EL_INV_TEXTDOMAIN ),
			number_format( (float) $fee_value, 2 )
		);
	}

	/** ---------------------------------------------------------- **
	 * BOLLO
	 * ----------------------------------------------------------- **/

	/**
	 * pmProAddStampDutyFee
	 *
	 * Add fee and increment - initial_payment
	 *
	 * @param $level
	 *
	 * @return mixed
	 */
	public static function pmProAddStampDutyFee( $level ) {
		if ( ! GeneralFields::isItalianStampDutyActive() ) {
			return $level;
		}

		$initialAmount = floatval( $level->initial_payment );
		if ( $initialAmount > 77.47 ) {
			$paidCustomerDuty = OptionPage::init()->getOptions( 'stamp_duty_paid_customer' );

			$fee_value = 2.00;

			if ( 'on' === $paidCustomerDuty ) {
				$level->initial_payment += $fee_value;

				if ( $level->billing_amount > 77.47 ) {
					$level->billing_amount += $fee_value;
				}
			}

			$level->stamp_duty_fee_name  = apply_filters( 'wc_el_inv-pmpro_stamp_duty_name', 'Imposta di Bollo' );
			$level->stamp_duty_fee_value = $fee_value;
			$level->stamp_duty_paid_by   = ( 'on' === $paidCustomerDuty ) ? 'customer' : 'merchant';
			$level->stamp_duty_applied   = true;
		}

		return $level;
	}

	/**
	 * pmProShowStampDutyNotice
	 *
	 * @param $level
	 *
	 * @return void
	 */
	public static function pmProShowStampDutyNotice( $level ) {
		if ( ! GeneralFields::isItalianStampDutyActive() ) {
			return $level;
		}

		$paidCustomerDuty = OptionPage::init()->getOptions( 'stamp_duty_paid_customer' );
		$initialAmount    = floatval( $level->initial_payment );

		// Apply only if no VAT and total > €77.47
		if ( $initialAmount > 77.47 ) {
			$fee_value = 2.00;
			if ( 'on' === $paidCustomerDuty ) {
				$message = sprintf(
					'<div class="pmpro_stamp_duty_notice"><small>%s</small></div>',
					sprintf(
					/* translators: %s = stamp duty amount (e.g. 2.00) */
						__( 'A stamp duty of €%s has been applied, as required for VAT-exempt transactions exceeding €77.47.',
							WC_EL_INV_TEXTDOMAIN ),
						number_format( $fee_value, 2 )
					)
				);
			} else {
				$message = sprintf(
					'<div class="pmpro_stamp_duty_notice"><small>%s</small></div>',
					sprintf(
					/* translators: %s = stamp duty amount (e.g. 2.00) */
						__( 'VAT-exempt transaction exceeding €77.47 — stamp duty of €%s paid by the merchant.',
							WC_EL_INV_TEXTDOMAIN ),
						number_format( $fee_value, 2 )
					)
				);
			}

			echo apply_filters( 'wc_el_inv-pmpro_stamp_duty_notice_html', $message, $level );
		}
	}

	/**
	 * pmProShowInOrderStampDutyNotice
	 *
	 * @param $invoice
	 *
	 * @return void
	 * @throws \Exception
	 */
	public static function pmProShowInOrderStampDutyNotice( $invoice ) {
		$order     = OrderQuery::instance()->getProviderOrder( $invoice->id, 'pmpro' );
		$fee_name  = $order->get_meta( 'stamp_duty_fee_name' );
		$fee_value = $order->get_meta( 'stamp_duty_fee_value' );

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

		printf(
			'<li><strong>%s:</strong> €%s</li>',
			esc_html__( $fee_name, WC_EL_INV_TEXTDOMAIN ),
			number_format( (float) $fee_value, 2 )
		);
	}

    /** ---------------------------------------------------------- **
     * CREDIT NOTE
     * ----------------------------------------------------------- **/

	/**
	 * Generate a credit note for a PMPro refunded order.
	 *
	 * - Only if the original invoice was already issued (order_number_invoice exists).
	 * - Uses the correct credit note numeration series:
	 *   number_next_credit_note or number_next_credit_note_receipt
	 * - Saves deterministic refund date.
	 * - Delegates XML generation to BuildXml passing the OrderRefund object.
	 *
	 * @param int    $orderId
	 * @param string $provider
	 *
	 * @return void
	 * @throws \Exception
	 */
	public static function pmProGenerateCreditNote($orderId, $provider = 'pmpro')
	{
		$orderId = (int) $orderId;
		if ($orderId < 1) {
			return;
		}

		$providerKey = sanitize_key($provider);
		$order = \WcElectronInvoice\Providers\OrderQuery::instance()->getProviderOrder($orderId, $providerKey);
		if (! $order || ! method_exists($order, 'get_refunds')) {
			return;
		}

		// Guard: original invoice must be issued
		$originalInvoiceNumber = $order->get_meta('order_number_invoice', true);
		if (empty($originalInvoiceNumber)) {
			// No original invoice issued — do not generate credit note
			return;
		}

		$refunds = $order->get_refunds();
		if (empty($refunds) || ! is_array($refunds)) {
			return;
		}

		/** @var \POPxPMPro\Providers\OrderRefund $refund */
		$refund = $refunds[0];

		// Check if credit note number already assigned
		$creditNoteNumber = $refund->get_meta("refund_number_invoice-{$orderId}", true);
		if (! empty($creditNoteNumber)) {
			// Already generated — skip
			return;
		}

	// Determine correct numeration series for credit note:
	// Use the original order's billing_choice_type (invoice or receipt)
	$choiceType = $order->get_billing_choice_type() ?: 'invoice';
	$numerationSeries = ('receipt' === $choiceType)
		? 'credit_note_receipt'
		: 'credit_note';

	// Only use credit note series if it is active; otherwise fall back to standard series
	$isCreditNoteActive = \WcElectronInvoice\Admin\Settings\OptionPage::init()->getOptions("numeration_{$numerationSeries}_active");
	if ('on' !== $isCreditNoteActive) {
		// Fall back to standard invoice/receipt series
		$numerationSeries = ('receipt' === $choiceType) ? 'receipt' : 'invoice';
	}

	$number = \WcElectronInvoice\Functions\getNextInvoiceNumber("number_next_{$numerationSeries}");
		if (empty($number) || (int) $number < 1) {
			return;
		}

		// Save credit note number and formatted number
		$refund->update_meta("refund_number_invoice-{$orderId}", $number);
		\WcElectronInvoice\Functions\setFormattedInvoiceNumber($refund, $numerationSeries);
		$refund->save();

		// Save deterministic refund date (only if not already set)
		$refundedDate = $refund->get_meta('refunded_date', true);
		if (empty($refundedDate)) {
			$refundedDate = current_time('mysql');
			$refund->update_meta('refunded_date', $refundedDate);
		}

		// Generate XML using BuildXml with the OrderRefund object
		$buildXml = new \WcElectronInvoice\Xml\BuildXml();
		$buildXml->singleOrderXmlData($refund, \POPxPMPro\Providers\OrderRefund::class);

		$createXml = new \WcElectronInvoice\Xml\CreateXml(new \SimpleXMLElement(
			'<?xml version="1.0" encoding="UTF-8"?><xmlns:p:FatturaElettronica/>',
			LIBXML_NOERROR,
			false,
			'p',
			true)
		);

		// Capture XML output (mirrors getXmlFromOrder behavior)
		ob_start();
		$createXml->create($buildXml->xmlData);
		$xmlOutput = ob_get_clean();

		// Save XML to file if configured
		$enableSaving = \WcElectronInvoice\Admin\Settings\OptionPage::init()->getOptions('invoice_saving_directory_xml_enable');
		if ('on' === $enableSaving) {
			$xmlDir = \WcElectronInvoice\Admin\Settings\OptionPage::init()->getOptions('invoice_saving_directory_xml');
			if (empty($xmlDir)) {
				$xmlDir = trailingslashit(wp_upload_dir()['basedir']) . 'pop-invoice';
				wp_mkdir_p($xmlDir);
			}

			$fileName = \WcElectronInvoice\Functions\getOrderInvoiceFileProgressiveNumber($refund);
			$file     = $xmlDir . "/TD04_refund_{$orderId}_" . $fileName . '.xml';

			// Use the same filter as CreateXml for file path
			$file = apply_filters(
				'wc_el_inv-invoice_create_xml_by_order_status_file',
				$file,
				$xmlDir,
				'TD04',
				'refund',
				$orderId,
				$fileName,
				$providerKey
			);

			file_put_contents($file, $xmlOutput);
			\WcElectronInvoice\Functions\log(
				sprintf('POP PMPro - Credit note XML saved for order #%d: %s', $orderId, $file)
			);
		}

		\WcElectronInvoice\Functions\log(
			sprintf('POP PMPro - Credit note generated for order #%d, number: %s', $orderId, $number)
		);
	}

	/** ---------------------------------------------------------- **
	 * SAVE FEES
	 * ----------------------------------------------------------- **/

	/**
	 * Save Fees
	 *
	 * @return void
	 * @throws \Exception
	 */
	public static function pmProSaveFeesCheckout( $morder ) {
		\WcElectronInvoice\Functions\log(
			sprintf( 'POP PMPro - pmProSaveFees called for order #%d — %s', $morder->id, 'checkout' )
		);

		$order = OrderQuery::instance()->getProviderOrder( $morder->id, 'pmpro' );
		$level = $morder->membership_level ?? pmpro_getLevel( $morder->membership_id );

		// fetch from level
		$stamp_duty_fee_name  = $level->stamp_duty_fee_name ?? '';
		$stamp_duty_fee_value = $level->stamp_duty_fee_value ?? 0;
		$stamp_duty_paid_by   = $level->stamp_duty_paid_by ?? 'merchant';
		$stamp_duty_applied   = $level->stamp_duty_applied ?? false;

		if ( $stamp_duty_applied && $stamp_duty_fee_value > 0 ) {
			$order->update_meta( 'stamp_duty_fee_name', $stamp_duty_fee_name );
			$order->update_meta( 'stamp_duty_fee_value', $stamp_duty_fee_value );
			$order->update_meta( 'stamp_duty_paid_by', $stamp_duty_paid_by );
		}

		$rivalsa_fee_name  = $level->rivalsa_fee_name ?? '';
		$rivalsa_fee_value = $level->rivalsa_fee_value ?? 0;
		$rivalsa_applied   = $level->rivalsa_applied ?? false;

		if ( $rivalsa_applied && $rivalsa_fee_value > 0 ) {
			$order->update_meta( 'rivalsa_fee_name', $rivalsa_fee_name );
			$order->update_meta( 'rivalsa_fee_value', $rivalsa_fee_value );
		}
	}

	/**
	 * Save Fees
	 *
	 * @return void
	 * @throws \Exception
	 */
	public static function pmProSaveFeesRenewal( $morder ) {
		global $wpdb;

		\WcElectronInvoice\Functions\log(
			sprintf( 'POP PMPro - pmProSaveFees called for order #%d — %s', $morder->id, 'renewal' )
		);

		$order = OrderQuery::instance()->getProviderOrder( $morder->id, 'pmpro' );

		// Renewal (webhook)
		$previous_order_id = $wpdb->get_var( $wpdb->prepare( "
            SELECT id FROM {$wpdb->pmpro_membership_orders}
            WHERE subscription_transaction_id = %s
              AND id <> %d
            ORDER BY id DESC
            LIMIT 1
        ", $morder->subscription_transaction_id, $morder->id ) );

		// todo: enhance. having a previous order is great to copy the configuration, but this is basically a workaround.
		//       we should re-calculate fees from the renewal gross, which might differ from the original order.
		//       and also the fees to be applied might differ is the amount changes.
		if ( $previous_order_id ) {
			$previousOrder = OrderQuery::instance()->getProviderOrder( $previous_order_id, 'pmpro' );
			// Copy meta from previous order
			$stamp_duty_fee_name  = $previousOrder->get_meta( 'stamp_duty_fee_name' );
			$stamp_duty_fee_value = $previousOrder->get_meta( 'stamp_duty_fee_value' );
			$stamp_duty_paid_by   = $previousOrder->get_meta( 'stamp_duty_paid_by' );
			$rivalsa_fee_name     = $previousOrder->get_meta( 'rivalsa_fee_name' );
			$rivalsa_fee_value    = $previousOrder->get_meta( 'rivalsa_fee_value' );

			if ( ! empty( $stamp_duty_fee_value ) ) {
				$order->update_meta( 'stamp_duty_fee_name', $stamp_duty_fee_name );
				$order->update_meta( 'stamp_duty_fee_value', $stamp_duty_fee_value );
				$order->update_meta( 'stamp_duty_paid_by', $stamp_duty_paid_by );
			}

			if ( ! empty( $rivalsa_fee_value ) ) {
				$order->update_meta( 'rivalsa_fee_name', $rivalsa_fee_name );
				$order->update_meta( 'rivalsa_fee_value', $rivalsa_fee_value );
			}
		}
	}

	public static function pmProSaveOrderNaturaRifNorm( $order, $country, $vatNumber, $customerInvoiceType ) {
		if ( $country && $vatNumber ) {

            $sanMarinoSupport    = sanMarinoSupport($customerInvoiceType, $country);

            // Country IT return no SM
            if ('SM' !== $sanMarinoSupport->shopCountry && 'IT' === $country) {
                return;
            }

			// Set prefix type used for retrieved nature or ref normative value
			$prefix = 'ue_';
			if ( ! in_array( $country, GeneralFields::getEuCountries(), true ) ) {
				$prefix = 'extra_ue_';
			}

			// Set type
			$type      = in_array( $customerInvoiceType, [ 'company', 'freelance' ] ) ? 'company' : 'private';
			$vatNumber = str_replace( $country, '', $vatNumber );

			$vies = false;
			if ( ! empty( $vatNumber ) && 'company' === $type ) {
				$vies = InvoiceFields::viesCheck( $vatNumber, $country );
				if ( false === $vies ) {
					$type = "{$type}_no_vies";
				}
			}

			update_pmpro_membership_order_meta( $order->id, 'order_vies_check', $vies );

			$nature            = TaxFields::getReverseChargeNature( "{$prefix}{$type}" );
			$refNormative      = TaxFields::getReverseChargeRefNormative( "{$prefix}{$type}" );
			$refNormativeValue = esc_html( $refNormative );

			if ( 'SM' === $sanMarinoSupport->shopCountry ) {
				$nature            = $sanMarinoSupport->nature;
				$refNormativeValue = $sanMarinoSupport->refNormative;
			}

			if ( '' !== $nature ) {
				$nature = apply_filters( 'wc_el_inv-before_nature_rc_pmpro_save', $nature, $order->id, $_POST );
				update_pmpro_membership_order_meta( $order->id, 'nature_rc', $nature );

				if ( '' !== $refNormativeValue ) {
					$refNormativeValue = apply_filters( 'wc_el_inv-before_ref_norm_rc_pmpro_save', $refNormativeValue, $order->id, $_POST );
					update_pmpro_membership_order_meta( $order->id, 'ref_norm_rc', $refNormativeValue );
				}
			}
		}
	}

	public static function pmProAdjustRenewalTaxes( $order ) {
		global $wpdb;
		$debugTag = 'POP_PMPRO_RENEWAL_DEBUG';

		$enable_tax_calculation = get_option( 'pop_pmpro_enable_tax_calculation' );
		if ( ! $enable_tax_calculation ) {
			\WcElectronInvoice\Functions\log(
				sprintf( '%s pmProAdjustRenewalTaxes skipped (tax calc disabled) order_id=%d', $debugTag, (int) $order->id )
			);
			return $order;
		}

		//error_log( "2006112 into pmproAdjustRenewalTax" );

		// PMPro 3.2+ is needed, which introduced the pmpro business address setting.
		$business_country = pop_pmproGetBusinessAddressCountry();
		//error_log( "2006112 business c=$business_country" );
		if ( empty( $business_country ) ) {
			\WcElectronInvoice\Functions\log(
				sprintf( '%s pmProAdjustRenewalTaxes skipped (missing business country) order_id=%d', $debugTag, (int) $order->id )
			);
			return $order;
		}

		$vat_number = get_pmpro_membership_order_meta( $order->id, 'billing_vat_number', true );
		//error_log( "2006112 vat no=$vat_number" );

		$customer_country = get_pmpro_membership_order_meta( $order->id, 'billing_country', true );
		if ( empty( $customer_country ) && ! empty( $order->billing_country ) ) {
			$customer_country = $order->billing_country;
		}
		if ( empty( $customer_country ) && isset( $order->billing->country ) ) {
			$customer_country = $order->billing->country;
		}
		if ( empty( $customer_country ) && ! empty( $order->user_id ) ) {
			$customer_country = get_user_meta( $order->user_id, 'pmpro_bcountry', true );
		}
		if ( empty( $customer_country ) && ! empty( $order->user_id ) ) {
			$customer_country = get_user_meta( $order->user_id, 'billing_country', true );
		}

		// calculate the tax rate
		$tax_rate = pop_getTaxRateForCustomer( $business_country, $customer_country, $vat_number );
		//error_log( "2006112 tax rate=$tax_rate" );
		$vatMasked = '';
		if ( ! empty( $vat_number ) ) {
			$vatMasked = strlen( $vat_number ) > 4 ? str_repeat( '*', max( 0, strlen( $vat_number ) - 4 ) ) . substr( $vat_number, -4 ) : '****';
		}

		\WcElectronInvoice\Functions\log(
			sprintf(
				'%s pmProAdjustRenewalTaxes order_id=%d business_country=%s customer_country=%s vat=%s total_before=%s subtotal_before=%s tax_before=%s tax_rate=%s',
				$debugTag,
				(int) $order->id,
				(string) $business_country,
				(string) $customer_country,
				(string) $vatMasked,
				(string) ( $order->total ?? '' ),
				(string) ( $order->subtotal ?? '' ),
				(string) ( $order->tax ?? '' ),
				(string) $tax_rate
			)
		);

		$original_amount = $order->total / ( 1 + $tax_rate );
		$order->subtotal = round( $original_amount, 2 );

		$order->tax = $order->total - $order->subtotal;

		$updatedRows = $wpdb->update(
			$wpdb->pmpro_membership_orders,
			array( 'tax' => $order->tax, 'subtotal' => $order->subtotal ),
			array( 'id' => $order->id ),
			array( '%s', '%s' ),
			array( '%d' )
		);

		\WcElectronInvoice\Functions\log(
			sprintf(
				'%s pmProAdjustRenewalTaxes updated order_id=%d rows=%s db_error=%s subtotal_after=%s tax_after=%s',
				$debugTag,
				(int) $order->id,
				(string) $updatedRows,
				(string) ( $wpdb->last_error ?? '' ),
				(string) ( $order->subtotal ?? '' ),
				(string) ( $order->tax ?? '' )
			)
		);

		return $order;
	}
}
