<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase, WordPress.Files.FileName.InvalidClassFileName
/**
 * Payment Utilities
 *
 * Static utility methods used across multiple payment gateway integrations.
 * Replaces standalone utility functions from the legacy src/payments.php.
 *
 * @package Autoship Cloud
 * @since 2.13.0
 */

namespace Autoship\Modules\Payments;

use Autoship\Modules\Payments\Services\PaymentMethodTypeRegistry;
use WC_Customer;
use WC_Payment_Token_CC;
use WC_Payment_Tokens;

/**
 * Shared payment utility methods.
 *
 * @package Autoship\Modules\Payments
 * @since 2.13.0
 */
class PaymentUtilities {

	/**
	 * Returns the token ID or token object based on the supplied token string.
	 *
	 * Replaces autoship_get_related_tokenized_id().
	 *
	 * @param string $token The token string to look up.
	 * @param bool   $tokenize If true return the WC Token object, else the ID.
	 *
	 * @return \WC_Payment_Token|int|null The WC Payment Token object or Token ID.
	 */
	public static function get_related_tokenized_id( $token, $tokenize = true ) {
		global $wpdb;
		$token_id = $wpdb->get_var( $wpdb->prepare( "SELECT token_id FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token = %s", $token ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching

		return $tokenize ? WC_Payment_Tokens::get( $token_id ) : $token_id;
	}

	/**
	 * Returns the payment method general customer data.
	 *
	 * Replaces autoship_get_general_payment_method_customer_data().
	 *
	 * @param int|WC_Customer $wc_customer The WC Customer ID or Object.
	 * @param array           $data Optional. Customer Data to use as an override.
	 *
	 * @return array The Customer Data.
	 */
	public static function get_general_payment_method_customer_data( $wc_customer, array $data = array() ): array {
		if ( is_numeric( $wc_customer ) ) {
			$wc_customer = new WC_Customer( $wc_customer );
		}

		$data = array_merge( array( 'customerId' => $wc_customer->get_id() ), $data );

		return \autoship_generate_customer_billing_data( $wc_customer, $data );
	}

	/**
	 * Generates a WC Payment Token for gateways that do not fully implement the
	 * WC Payment Token Class (e.g., braintree_paypal).
	 *
	 * Replaces autoship_tokenize_non_fully_implemented_token_classes().
	 *
	 * @param object $token The current WC Payment Token.
	 *
	 * @return WC_Payment_Token_CC The normalized token.
	 */
	public static function tokenize_non_fully_implemented_token_classes( $token ) {
		if ( empty( $token ) || 'braintree_paypal' != $token->get_gateway_id() ) { //phpcs:ignore
			return $token;
		}

		$original_token = $token;
		$token          = new WC_Payment_Token_CC();
		$token->set_user_id( $original_token->get_user_id() );
		$token->set_token( $original_token->get_token() );
		$token->set_gateway_id( 'braintree_paypal' );
		$token->set_card_type( 'Paypal' );
		$token->add_meta_data( 'payer_email', $original_token->get_meta( 'payer_email' ) );

		return $token;
	}

	/**
	 * Tokenize non-standard payment methods (Skyverge framework).
	 *
	 * Replaces autoship_tokenize_non_standard_methods().
	 *
	 * @param object $token The current WC Payment Token.
	 * @param string $autoship_method_id The method ID.
	 * @param string $autoship_method_type The method type.
	 *
	 * @return object The tokenized payment method.
	 */
	public static function tokenize_non_standard_methods( $token, $autoship_method_id, $autoship_method_type ) {
		if ( 'standard' === $autoship_method_type ) {
			return $token;
		}

		$original_token = $token;
		$token          = new WC_Payment_Token_CC();
		$token->set_user_id( get_current_user_id() );
		$token->set_token( $autoship_method_id );
		$token->set_gateway_id( $autoship_method_type );

		$test_ext = apply_filters( 'autoship_payment_method_sandbox_metadata_field_test_ext', '_test', $autoship_method_type );
		$meta_ext = apply_filters( 'autoship_payment_method_sandbox_metadata_field_ext', '', $test_ext, $autoship_method_type );

		$user_tokens = get_user_meta( get_current_user_id(), "_wc_{$autoship_method_type}_payment_tokens{$meta_ext}", true );

		if ( empty( $user_tokens ) || ! isset( $user_tokens[ $autoship_method_id ] ) ) {
			return $original_token;
		}

		if ( 'braintree_paypal' != $autoship_method_type ) { //phpcs:ignore
			$token->set_last4( $user_tokens[ $autoship_method_id ]['last_four'] );
			$token->set_expiry_year( strlen( $user_tokens[ $autoship_method_id ]['exp_year'] ) < 4 ? 2000 + $user_tokens[ $autoship_method_id ]['exp_year'] : $user_tokens[ $autoship_method_id ]['exp_year'] );
			$token->set_expiry_month( $user_tokens[ $autoship_method_id ]['exp_month'] );
			$token->set_card_type( $user_tokens[ $autoship_method_id ]['card_type'] );
		} else {
			$token->set_card_type( 'Paypal' );
		}

		return $token;
	}

	/**
	 * Display "Update All Scheduled Orders" notice after a payment method is saved.
	 *
	 * Replaces autoship_after_save_payment_method_autoship_action_notice().
	 *
	 * @param string $token_id The new token ID.
	 * @param string $gateway_id The gateway ID.
	 * @param string $name The payment method name.
	 *
	 * @return void
	 */
	public static function after_save_payment_method_notice( $token_id, $gateway_id = 'standard', $name = '' ): void {
		$hide = apply_filters( 'autoship_display_save_payment_method_at_checkout', is_checkout() ) || ! apply_filters( 'autoship_display_customer_update_actions', true, get_current_user_id() );

		if ( $hide ) {
			return;
		}

		$schedule_label = \autoship_translate_text( 'Scheduled Orders' );

		$url = self::get_update_payment_method_url( $token_id, $gateway_id );
		$url = wp_nonce_url( $url, 'autoship-apply-payment-method-to-orders-' . $token_id );

		// translators: %1$s is the label for Scheduled Orders, %2$s is the URL to update the payment method, %3$s is the Update button text.
		$message = empty( $name ) ? sprintf( __( 'Use the newly Added Payment Method for your <strong>%1$s</strong> as well? <a href="%2$s" tabindex="1" class="button">%3$s</a>', 'autoship' ), $schedule_label, esc_url( $url ), esc_html__( 'Update', 'autoship' ) ) : sprintf( __( 'Use the new Payment Method ( %1$s ) for your <strong>%2$s</strong> as well? <a href="%3$s" tabindex="1" class="button">%4$s</a>', 'autoship' ), $name, $schedule_label, esc_url( $url ), esc_html__( 'Update', 'autoship' ) );
		$notice  = apply_filters(
			'autoship_apply_payment_method_to_all_scheduled_orders_default_action_notice',
			array(
				'message' => $message,
				'code'    => 'success',
			),
			$token_id,
			$gateway_id,
			$name
		);

		wc_add_notice( $notice['message'], $notice['code'] );
	}

	/**
	 * Generates a URL to update Scheduled Orders Payment Method.
	 *
	 * Replaces autoship_get_scheduled_orders_update_customer_payment_method_info_url().
	 *
	 * @param string $method_id The Payment Method ID.
	 * @param string $method_type Optional. The Payment Method gateway type. Default 'standard'.
	 *
	 * @return string The action URL.
	 */
	public static function get_update_payment_method_url( $method_id, $method_type = 'standard' ): string {
		return apply_filters(
			'autoship_get_update_scheduled_orders_customer_payment_method_info_url',
			add_query_arg(
				array(
					'autoship_method_id'   => $method_id,
					'autoship_method_type' => $method_type,
				),
				wc_get_page_permalink( 'myaccount' )
			),
			$method_id,
			$method_type
		);
	}

	/**
	 * Display the Skyverge-style "Apply to All Orders" button.
	 *
	 * Replaces autoship_display_apply_payment_method_to_all_scheduled_orders_skyverge_btn().
	 *
	 * @param array  $list_item The list item array.
	 * @param object $payment_token The payment token.
	 * @param object $instance The My Payment Methods instance.
	 * @param string $gateway The gateway ID.
	 *
	 * @return array The filtered list item.
	 */
	public static function display_skyverge_apply_btn( $list_item, $payment_token, $instance, string $gateway ): array {
		if ( ! apply_filters( 'autoship_display_customer_update_actions', true, get_current_user_id() ) ) {
			return $list_item;
		}

		$apply_url = self::get_update_payment_method_url( $payment_token->get_id(), $gateway );
		$apply_url = wp_nonce_url( $apply_url, 'autoship-apply-payment-method-to-orders-' . $payment_token->get_id() );

		$list_item['autoship_update_all_orders'] = apply_filters(
			'autoship_apply_payment_method_to_all_scheduled_orders_row_action',
			array(
				'url'   => $apply_url,
				'name'  => apply_filters( 'autoship_update_orders_icon_action', '' ),
				'class' => 'autoship_update_all_orders',
			),
			$apply_url,
			$payment_token,
			$list_item
		);

		return apply_filters( 'autoship_apply_payment_method_to_all_scheduled_orders_non_standard_row', $list_item, $payment_token, $instance );
	}

	/**
	 * Determine sandbox metadata field extension based on test mode.
	 *
	 * Replaces autoship_skyverge_payment_method_sandbox_metadata_field_ext_init().
	 *
	 * @param string $ext The non-test mode extension.
	 * @param string $alt_ext The test mode extension.
	 * @param string $autoship_method_type The gateway ID.
	 *
	 * @return string The metadata field extension.
	 */
	public static function sandbox_metadata_field_ext_init( string $ext, string $alt_ext, string $autoship_method_type ): string {
		$gateways = WC()->payment_gateways->payment_gateways();

		if ( ! empty( $gateways ) && isset( $gateways[ $autoship_method_type ] ) ) {
			return $gateways[ $autoship_method_type ]->is_test_environment() ? $alt_ext : $ext;
		}

		return $ext;
	}

	/**
	 * Map gateway to test mode extension prefix.
	 *
	 * Replaces autoship_payment_method_sandbox_metadata_field_ext_types().
	 *
	 * @param string $ext The current extension.
	 * @param string $autoship_method_type The gateway ID.
	 *
	 * @return string The filtered extension.
	 */
	public static function sandbox_metadata_field_ext_types( string $ext, string $autoship_method_type ): string {
		$types = array(
			'braintree_paypal'              => '_sandbox',
			'braintree_credit_card'         => '_sandbox',
			'authorize_net_cim_credit_card' => '_test',
			'cybersource_credit_card'       => '_test',
			'square_credit_card'            => '_sandbox',
		);

		return isset( $types[ $autoship_method_type ] ) ? $types[ $autoship_method_type ] : $ext;
	}

	/**
	 * Filter checkout to only show Autoship-supported gateways.
	 *
	 * Replaces autoship_filter_checkout_supported_gateways().
	 *
	 * @param array $gateways Available payment gateways.
	 *
	 * @return array Filtered gateways.
	 */
	public static function filter_checkout_supported_gateways( array $gateways ): array {
		if ( ! \autoship_cart_has_valid_autoship_items() ) {
			return $gateways;
		}

		$valid_gateways = apply_filters(
			'autoship_valid_payment_method_ids',
			array(
				'authorize_net_cim_credit_card'       => 'authorize_net',
				'cybersource_credit_card'             => 'cybersource_cc',
				'stripe'                              => 'stripe',
				'stripe_sepa'                         => 'stripe_sepa',
				'stripe_sepa_debit'                   => 'stripe_sepa_debit',
				'trustcommerce'                       => 'trustcommerce',
				'autoship-test-gateway'               => 'test_gateway',
				'ppec_paypal'                         => 'ppec',
				'braintree_credit_card'               => 'braintree',
				'braintree_paypal'                    => 'braintree_paypal',
				'cybersource'                         => 'cybersource',
				'nmi_gateway_woocommerce_credit_card' => 'nmi',
				'sagepaymentsusaapi'                  => 'sagepaymentsusaapi',
				'square_credit_card'                  => 'square',
				'sagepaydirect'                       => 'sage',
				'wc_checkout_com_cards'               => 'checkout_com',
				'nmi'                                 => 'nmi_gateway',
				'airwallex_card'                      => 'airwallex_card',
				'fkwcs_stripe'                        => 'fkwcs_stripe',
				'fkwcs_stripe_ach'                    => 'fkwcs_stripe_ach',
				'woocommerce_payments'                => 'woocommerce_payments',
			)
		);

		foreach ( $gateways as $key => $gateway ) {
			if ( ! isset( $valid_gateways[ $key ] ) ) {
				unset( $gateways[ $key ] );
			}
		}

		return $gateways;
	}

	/**
	 * Add Autoship info to the WC payment gateway settings page.
	 *
	 * Replaces autoship_adjust_payment_gateway_option_description().
	 *
	 * @param array $settings The payment gateway settings.
	 *
	 * @return array The filtered settings.
	 */
	public static function adjust_payment_gateway_option_description( array $settings ): array {
		if ( ! isset( $settings[0] ) || ! isset( $settings[0]['desc'] ) ) {
			return $settings;
		}

		// translators: %1$s is the URL to the Autoship Plugin, %2$s is the URL to the Autoship Support Article.
		$settings[0]['desc'] .= sprintf( __( '<br><hr><img class="autoship-payment-icon" width="24" height="24" src="%1$s"/><span class="autoship-payment-description">Autoship Cloud Supported Payment Gateways. <a href="%2$s">Learn More</a>' ), Autoship_Plugin_Url . '/images/scheduled_order.svg', 'https://support.autoship.cloud/article/1002-payment-integrations' );

		return $settings;
	}

	/**
	 * Force Stripe to save source when cart has autoship items.
	 *
	 * Replaces autoship_stripe_force_save().
	 *
	 * @param bool $val The current force save value.
	 *
	 * @return bool Whether to force save.
	 */
	public static function stripe_force_save( $val ) {
		if ( \autoship_cart_has_valid_autoship_items() ) {
			return true;
		}

		return $val;
	}

	/**
	 * Filter Stripe intent request for autoship carts.
	 *
	 * Replaces autoship_stripe_intent_request_filter().
	 *
	 * @param array    $request The Stripe request data.
	 * @param WC_Order $order The order.
	 *
	 * @return array The filtered request.
	 */
	public static function stripe_intent_request_filter( $request, $order ) {
		if ( \autoship_order_total_scheduled_items( $order ) > 0 ) {
			$request['setup_future_usage']       = 'off_session';
			$request['metadata']['payment_type'] = 'recurring';
		}

		return $request;
	}

	/**
	 * Add Stripe Link mandate data to scheduled order data.
	 *
	 * Replaces autoship_stripe_link_order_mandate_data().
	 *
	 * @param array $order_data The order data.
	 * @param int   $order_id The WC Order ID.
	 *
	 * @return array The filtered order data.
	 */
	public static function stripe_link_order_mandate_data( $order_data, $order_id ) {
		if ( isset( $order_data['paymentMethod']['type'] ) && isset( $order_data['paymentMethod']['gatewayPaymentType'] ) ) {
			if ( 'Stripe' === $order_data['paymentMethod']['type'] && 32 === $order_data['paymentMethod']['gatewayPaymentType'] ) {
				$order = wc_get_order( $order_id );
				if ( $order ) {
					$order_data['metadata']['stripe_mandate_data'] = array(
						'customer_acceptance' => array(
							'type'   => 'online',
							'online' => array(
								'ip_address' => $order->get_customer_ip_address(),
								'user_agent' => $order->get_customer_user_agent(),
							),
						),
					);
				}
			}
		}

		return $order_data;
	}

	/**
	 * Force NMI payment tokenization.
	 *
	 * Replaces autoship_force_nmi_payment_tokenization().
	 *
	 * @param bool $force The current force value.
	 *
	 * @return bool Always true.
	 */
	public static function force_nmi_payment_tokenization( $force ): bool { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
		return true;
	}

	/**
	 * Filter deleted payment method gateway IDs (stripe_sepa / stripe_sepa_debit → stripe).
	 *
	 * Replaces autoship_filter_deleted_payment_method_gateway_ids().
	 *
	 * @param string $gateway_id The gateway ID.
	 * @param object $token The token.
	 *
	 * @return string The filtered gateway ID.
	 */
	public static function filter_deleted_payment_method_gateway_ids( $gateway_id, $token ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
		if ( 'stripe_sepa' === $gateway_id || 'stripe_sepa_debit' === $gateway_id ) {
			return 'stripe';
		}

		return $gateway_id;
	}

	/**
	 * Retrieve the first Airwallex customer ID associated with a user.
	 *
	 * Replaces autoship_get_airwallex_customer_id().
	 *
	 * @param int $user_id The user ID.
	 *
	 * @return mixed|null The Airwallex customer ID, or null.
	 */
	public static function get_airwallex_customer_id( int $user_id ) {
		global $wpdb;

		$meta_key_prefix = 'airwallex_customer_id_';

		$result = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT meta_value FROM {$wpdb->usermeta} WHERE user_id = %d AND meta_key LIKE %s LIMIT 1",
				$user_id,
				$wpdb->esc_like( $meta_key_prefix ) . '%'
			),
			ARRAY_A
		);

		return $result ? maybe_unserialize( $result['meta_value'] ) : null;
	}

	/**
	 * Retrieve PayPal Payments tokens for a customer.
	 *
	 * Replaces autoship_get_paypal_payments_tokens_for_customer().
	 *
	 * @param string $gateway_customer_id The PayPal customer ID.
	 *
	 * @return array The customer's payment tokens.
	 */
	public static function get_paypal_payments_tokens_for_customer( $gateway_customer_id ): array {
		$payment_tokens_endpoint = PaymentMethodTypeRegistry::resolve_ppcp_service( 'api.endpoint.payment-tokens' );

		if ( null === $payment_tokens_endpoint ) {
			return array();
		}

		try {
			$customer_tokens = $payment_tokens_endpoint->payment_tokens_for_customer( $gateway_customer_id );
		} catch ( \Throwable $exception ) {
			$customer_tokens = array();
		}

		return $customer_tokens;
	}

	/**
	 * Add refund metadata for PayPal Express Checkout orders.
	 *
	 * Replaces autoship_add_adjusted_gateway_metadata_ppec_paypal().
	 *
	 * @param array     $payment_meta The gateway response data.
	 * @param \WC_Order $order The WC order.
	 * @param array     $payment_method The payment method data.
	 *
	 * @return void
	 */
	public static function add_adjusted_gateway_metadata_ppec_paypal( $payment_meta, $order, $payment_method ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
		$status = 'Completed' == $payment_meta['PAYMENTSTATUS'] ? $payment_meta['PAYMENTSTATUS'] : $payment_meta['PAYMENTSTATUS'] . '_' . $payment_meta['PENDINGREASON']; //phpcs:ignore

		$refund_data = array(
			'txn_type'        => 'sale',
			'refundable_txns' => array(
				array(
					'txnID'           => $payment_meta['TRANSACTIONID'],
					'amount'          => $payment_meta['AMT'],
					'refunded_amount' => 0,
				),
			),
			'status'          => $status,
		);

		$order->update_meta_data( '_woo_pp_txnData', $refund_data );
		$order->save();
	}

	/**
	 * Add refund metadata for PayPal Commerce Platform orders.
	 *
	 * Replaces autoship_add_adjusted_gateway_metadata_ppep_paypal().
	 *
	 * @param array     $payment_meta The gateway response data.
	 * @param \WC_Order $order The WC order.
	 * @param array     $payment_method The payment method data.
	 *
	 * @return void
	 */
	public static function add_adjusted_gateway_metadata_ppep_paypal( $payment_meta, $order, $payment_method ): void {
		$order->update_meta_data( 'payment_token_id', $payment_method['gatewayPaymentId'] );
		$order->update_meta_data( '_ppcp_paypal_order_id', $payment_meta['id'] );
		$order->update_meta_data( '_ppcp_paypal_intent', 'CAPTURE' );
		$order->save();

		if ( isset( $payment_meta['links'] ) && ! empty( $payment_meta['links'] ) && ( strpos( $payment_meta['links'][0]['href'], 'https://api.sandbox.paypal.com' ) != false ) ) { //phpcs:ignore
			$order->update_meta_data( '_ppcp_paypal_payment_mode', 'sandbox' );
			$order->save();
		}
	}

	/**
	 * Retrieve Checkout.com charge by transaction ID.
	 *
	 * Replaces autoship_get_checkout_com_charge_by_transaction_id().
	 *
	 * @param string $transaction_id The transaction ID.
	 *
	 * @return array|false|null The charge data, false on error, null if SDK unavailable.
	 */
	public static function get_checkout_com_charge_by_transaction_id( $transaction_id ) {
		if ( class_exists( 'Checkout\CheckoutDefaultSdk' ) ) {
			$checkout = new \Checkout_SDK();

			try {
				$details = $checkout->get_builder()->getPaymentsClient()->getPaymentDetails( $transaction_id );

				return array(
					'token'       => $details['source']['id'],
					'customer_id' => $details['customer']['id'],
				);
			} catch ( \Exception $ex ) {
				\autoship_log_entry( __( 'Autoship Payment Methods Error', 'autoship' ), sprintf( 'Checkout.com Transaction #%s Retrieval failed.  Additional Details: %s', $transaction_id, $ex->getMessage() ) );

				return false;
			}
		}

		return null;
	}
}
