<?php
/**
 * Payment request helper
 *
 * @author    Pronamic <info@pronamic.eu>
 * @copyright 2005-2026 Pronamic
 * @license   GPL-3.0-or-later
 * @package   Pronamic\WordPress\Pay\Gateways\Adyen
 */

namespace Pronamic\WordPress\Pay\Gateways\Adyen;

use Pronamic\WordPress\Money\TaxedMoney;
use Pronamic\WordPress\Number\Number;
use Pronamic\WordPress\Pay\Payments\Payment;

/**
 * Payment request helper class
 */
class PaymentRequestHelper {
	/**
	 * Complement WordPress Pay payment to Adyen payment request.
	 *
	 * @param Payment                $payment WordPress Pay payment to convert.
	 * @param AbstractPaymentRequest $request Adyen payment request.
	 * @param Config                 $config  Configuration object.
	 * @return void
	 * @throws \Exception Throws exception on invalid metadata.
	 */
	public static function complement( Payment $payment, AbstractPaymentRequest $request, Config $config ) {
		/**
		 * Merchant order reference.
		 *
		 * @link https://docs.adyen.com/api-explorer/#/CheckoutService/v68/post/payments__reqParam_merchantOrderReference
		 * @link https://docs.adyen.com/api-explorer/#/CheckoutService/v68/post/sessions__reqParam_merchantOrderReference
		 */
		$merchant_order_reference = $payment->format_string( $config->get_merchant_order_reference() );

		if ( '' === $merchant_order_reference ) {
			$merchant_order_reference = $payment->get_id();
		}

		/**
		 * Filters the Adyen merchant order reference.
		 *
		 * This reference allows linking multiple transactions to each other
		 * for reporting purposes (i.e. order auth-rate). The reference should
		 * be unique per billing cycle. The same merchant order reference
		 * should never be reused after the first authorised attempt. If used,
		 * this field should be supplied for all incoming authorisations.
		 *
		 * @param string  $merchant_order_reference Merchant order reference.
		 * @param Payment $payment                  Payment.
		 * @link https://docs.adyen.com/api-explorer/Checkout/68/post/payments#request-merchantOrderReference
		 * @since 4.5.0 Added.
		 */
		$merchant_order_reference = apply_filters( 'pronamic_pay_adyen_merchant_order_reference', $merchant_order_reference, $payment );

		$request->set_merchant_order_reference( $merchant_order_reference );

		/**
		 * Country code.
		 *
		 * @link https://docs.adyen.com/api-explorer/#/CheckoutService/v68/post/payments__reqParam_countryCode
		 * @link https://docs.adyen.com/api-explorer/#/CheckoutService/v68/post/sessions__resParam_countryCode
		 */
		$request->set_country_code( Util::get_country_code( $payment ) );

		/**
		 * Application info.
		 *
		 * @link https://docs.adyen.com/api-explorer/#/CheckoutService/v68/post/payments__reqParam_applicationInfo
		 * @link https://docs.adyen.com/api-explorer/#/CheckoutService/v68/post/sessions__reqParam_applicationInfo
		 * @link https://docs.adyen.com/development-resources/building-adyen-solutions
		 */
		$application_info = new ApplicationInfo();

		$application_info->merchant_application = (object) [
			'name'    => 'Pronamic Pay',
			'version' => \pronamic_pay_plugin()->get_version(),
		];

		$application_info->external_platform = (object) [
			'integrator' => 'Pronamic',
			'name'       => 'WordPress',
			'version'    => \get_bloginfo( 'version' ),
		];

		$request->set_application_info( $application_info );

		// Channel.
		$request->set_channel( Channel::WEB );

		// Shopper.
		$request->set_shopper_statement( $payment->get_description() );

		// Customer.
		$customer = $payment->get_customer();

		if ( null !== $customer ) {
			/*
			 * When sending in the shopper reference we always create a recurring contract. If you would not
			 * like to store the details, we recommend to exclude the shopper reference.
			 *
			 * $user_id = $customer->get_user_id();
			 * $request->set_shopper_reference( \is_null( $user_id ) ? null : \strval( $user_id ) );
			 */
			$request->set_shopper_ip( $customer->get_ip_address() );
			$request->set_shopper_locale( $customer->get_locale() );
			$request->set_telephone_number( $customer->get_phone() );
			$request->set_shopper_email( $customer->get_email() );

			// Shopper name.
			$name = $customer->get_name();

			if ( null !== $name ) {
				$shopper_name = new Name(
					(string) $name->get_first_name(),
					(string) $name->get_last_name(),
					GenderTransformer::transform( $customer->get_gender() )
				);

				$request->set_shopper_name( $shopper_name );
			}

			// Date of birth.
			$request->set_date_of_birth( $customer->get_birth_date() );
		}

		// Billing address.
		$billing_address = $payment->get_billing_address();

		if ( null !== $billing_address ) {
			$address = AddressTransformer::transform( $billing_address );

			$request->set_billing_address( $address );
		}

		// Delivery address.
		$shipping_address = $payment->get_shipping_address();

		if ( null !== $shipping_address ) {
			$address = AddressTransformer::transform( $shipping_address );

			$request->set_delivery_address( $address );
		}

		// Lines.
		$lines = $payment->get_lines();

		if ( null !== $lines ) {
			$line_items = $request->new_line_items();

			$i = 1;

			foreach ( $lines as $line ) {
				// Description.
				$description = $line->get_description();

				// Use line item name as fallback for description.
				if ( null === $description ) {
					/* translators: %s: item index */
					$description = sprintf( __( 'Item %s', 'pronamic-ideal' ), $i++ );

					if ( null !== $line->get_name() && '' !== $line->get_name() ) {
						$description = $line->get_name();
					}
				}

				$total_amount = $line->get_total_amount();

				$quantity = $line->get_quantity();

				if ( null === $quantity ) {
					$quantity = new Number( 0 );
				}

				// Handle decimal quantities.
				if ( ! $quantity->is_whole_number() ) {
					$description = \sprintf(
						'%s × %s',
						$quantity->format_i18n_non_trailing_zeros(),
						(string) $description
					);

					$quantity = new Number( 1 );
				}

				$item = $line_items->new_item(
					(string) $description,
					$quantity->to_int(),
					$total_amount->get_minor_units()->to_int()
				);

				$item->set_id( $line->get_id() );

				// Tax amount.
				if ( $total_amount instanceof TaxedMoney ) {
					$item->set_amount_excluding_tax( $total_amount->get_excluding_tax()->get_minor_units()->to_int() );

					$tax_amount = $total_amount->get_tax_amount();

					if ( null !== $tax_amount ) {
						$item->set_tax_amount( $tax_amount->get_minor_units()->to_int() );
						$item->set_tax_percentage( (int) $total_amount->get_tax_percentage() * 100 );
					}
				}
			}
		}

		/*
		 * Additional data.
		 */
		$additional_data = new AdditionalData();

		// Order date.
		$additional_data->esd_order_date = $payment->get_date();

		// Customer reference (required for Level 2/3).
		$additional_data->esd_customer_reference = '';

		// Tax amount (required for Level 2/3).
		$total_amount = $payment->get_total_amount();

		if ( $total_amount instanceof TaxedMoney ) {
			$tax_amount = $total_amount->get_tax_amount();

			if ( null !== $tax_amount ) {
				$additional_data->esd_total_tax_amount = $tax_amount->get_minor_units()->get_value();
			}
		}

		// Shipping amount.
		$shipping_amount = $payment->get_shipping_amount();

		if ( null !== $shipping_amount ) {
			$additional_data->esd_freight_amount = $shipping_amount->get_minor_units()->get_value();
		}

		// Destination address.
		$shipping_address = $payment->get_shipping_address();

		if ( null !== $shipping_address ) {
			// Postal code (required for Level 2/3 with Amex).
			$postal_code = $shipping_address->get_postal_code();

			if ( ! empty( $postal_code ) ) {
				$additional_data->esd_destination_postal_code = $postal_code;
			}

			// Country code.
			$country_code = $shipping_address->get_country_code();

			if ( ! empty( $country_code ) ) {
				$additional_data->esd_destination_country_code = $country_code;
			}
		}

		// Line items.
		$additional_data->set_line_items( $request->get_line_items() );

		$request->set_additional_data( $additional_data );

		// Metadata.
		$metadata = [
			'network_id' => \get_current_network_id(),
			'blog_id'    => \get_current_blog_id(),
			'payment_id' => $payment->get_id(),
		];

		/**
		 * Filters the Adyen payment metadata.
		 *
		 * Maximum 20 key-value pairs per request. When exceeding, the "177" error occurs: "Metadata size exceeds limit".
		 *
		 * @param array $metadata Payment request metadata.
		 * @param Payment $payment Payment.
		 * @link https://docs.adyen.com/api-explorer/#/CheckoutService/v64/post/payments__reqParam_metadata
		 * @since 1.1.1 Added.
		 */
		$metadata = apply_filters( 'pronamic_pay_adyen_payment_metadata', $metadata, $payment );

		/*
		 * Maximum 20 key-value pairs per request. When exceeding, the "177" error occurs: "Metadata size exceeds limit".
		 *
		 * @link https://docs.adyen.com/api-explorer/#/PaymentSetupAndVerificationService/v51/payments__reqParam_metadata
		 * @link https://docs.adyen.com/api-explorer/#/PaymentSetupAndVerificationService/v41/paymentSession__reqParam_metadata
		 */
		if ( ! \is_array( $metadata ) ) {
			throw new \Exception( 'Adyen metadata must be an array.' );
		}

		if ( count( $metadata ) > 20 ) {
			throw new \Exception( 'Adyen metadata exceeds maximum of 20 items.' );
		}

		$request->set_metadata( $metadata );
	}
}
