<?php

namespace WeeConnectPay\WordPress\Plugin\includes;


use Exception;
use WC_Order;
use WeeConnectPay\Api\Requests\RefundCloverChargeRequest;
use WeeConnectPay\CloverReceiptsHelper;
use WeeConnectPay\Dependencies\GuzzleHttp\Exception\InvalidArgumentException;
use WeeConnectPay\Integrations\LogService;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Provides static methods as helpers.
 *
 * @since 1.0.0
 */
class WeeConnectPayHelper {

	/**
	 * Formats the description on the clover receipt. Also used in refunds.
	 * @param $name
	 * @param $qty
	 *
	 * @since 1.2.0
	 *
	 * @return string
	 */
	public static function name_and_qty_as_clover_line_desc( $name, $qty ): string
    {
		return $name . ' x ' . $qty;
	}

	/**
	 * Safely formats a string amount value to an int representation of its value in cents.
	 * @param string $string_amount
	 * @since 1.3.3
	 *
	 * @return int
	 */
	public static function safe_amount_to_cents_int( string $string_amount ): int
    {
		return (int) number_format( $string_amount, 2, '', '' );
	}

    /**
     * Save a successful credit card transaction to the WooCommerce order meta.
     *
     * Instead of using a generated UUID, this method uses the provided Clover charge ID
     * as the key, ensuring that the record always exists for a successful transaction.
     *
     * @param WC_Order $order The WooCommerce order instance.
     * @param int $amountInCents The transaction amount in cents.
     * @param string $currency The currency of the charge (e.g., CAD, USD).
     * @param string $cardType The type of card (e.g., Visa, MasterCard).
     * @param string $last4Digits The last 4 digits of the card.
     * @param string|null $month The expiry month of the card. -- Google Pay does not provide us with this information
     * @param string|null $year The expiry year of the card. -- Google Pay does not provide us with this information
     * @param string $postalCode The postal code provided during the card tokenization that passed the AVS for this transaction
     * @param string $chargeId The unique Clover charge ID.
     * @param string $type The type of charge (e.g., payment, refund).
     *
     * @return array The saved charge data.
     *
     */
    public static function saveCreditCardCharge(WC_Order $order, int $amountInCents, string $currency, string $cardType, string $last4Digits, ?string $month, ?string $year, string $postalCode, string $chargeId, string $type, ?array $threedsData = null): array
    {
        // Validate inputs.
        // Skip validation for subscription renewals and capture charges (capture responses don't include full card details)
        $skip_validation = self::get_order_type( $order ) === 'subscription_renewal' || $type === 'capture';
        if ( ! $skip_validation ) {
            if (trim($cardType) === '') {
                throw new InvalidArgumentException('Card type must be a non-empty string.');
            }
            if (trim($currency) === '') {
                throw new InvalidArgumentException('Currency must be a non-empty string.');
            }
            if (trim($last4Digits) === '') {
                throw new InvalidArgumentException('Last 4 digits must be provided.');
            }
    // Maybe in the future we will look at whether this is Google Pay, and provide specific ways of handling Google Pay or Apple Pay
    //        if (trim($month) === '') {
    //            throw new InvalidArgumentException('Month must be a non-empty string.');
    //        }
    //        if (trim($year) === '') {
    //            throw new InvalidArgumentException('Year must be a non-empty string.');
    //        }
            if (trim($postalCode) === '') {
                throw new InvalidArgumentException('Postal code must be a non-empty string.');
            }
        }
        if (trim($chargeId) === '') {
            throw new InvalidArgumentException('Clover Charge ID must be provided.');
        }
        if ($amountInCents <= 0) {
            throw new InvalidArgumentException('Amount must be a positive integer in cents.');
        }

        // Retrieve existing charges from order meta.
        $charges = $order->get_meta('weeconnectpay_charges', true);
        if (!is_array($charges)) {
            $charges = [];
        }

        // Check if a record for this charge ID already exists.
        if (isset($charges[$chargeId])) {
            throw new InvalidArgumentException("A charge with ID {$chargeId} already exists.");
        }

        // Create the new charge record.
        $chargeData = [
            'charge_id'    => sanitize_text_field($chargeId),
            'type'         => sanitize_text_field($type),
            'amount'       => absint($amountInCents),
            'currency'     => sanitize_text_field($currency),
            'card_type'    => sanitize_text_field($cardType),
            'card_last4' => sanitize_text_field($last4Digits),
            'card_exp_month' => sanitize_text_field($month),
            'card_exp_year' => sanitize_text_field($year),
            'card_postal_code' => sanitize_text_field($postalCode),
            'status'       => 'success', // Immediately mark as success on transaction completion.
        ];

        // Persist the 3-D Secure outcome alongside the charge so the metabox
        // badge and tooltip can render without re-querying the backend.
        if ( ! empty( $threedsData ) && is_array( $threedsData ) ) {
            $chargeData['threeds'] = array(
                'validation_result'           => isset( $threedsData['validation_result'] ) ? sanitize_text_field( (string) $threedsData['validation_result'] ) : null,
                'liability_protection_status' => isset( $threedsData['liability_protection_status'] ) ? sanitize_text_field( (string) $threedsData['liability_protection_status'] ) : null,
                'server_transaction_id'       => isset( $threedsData['server_transaction_id'] ) ? sanitize_text_field( (string) $threedsData['server_transaction_id'] ) : null,
                'acs_transaction_id'          => isset( $threedsData['acs_transaction_id'] ) ? sanitize_text_field( (string) $threedsData['acs_transaction_id'] ) : null,
                'flow_status'                 => isset( $threedsData['flow_status'] ) ? sanitize_text_field( (string) $threedsData['flow_status'] ) : null,
            );
        }

        // Save the new charge record keyed by its charge ID.
        $charges[$chargeId] = $chargeData;

        // Save back to order meta.
        $order->update_meta_data('weeconnectpay_charges', $charges);

        // Displays the card type brand as the payment method on the order receipt page.
        if ( ! empty( $cardType ) ) {
            $order->set_payment_method_title( $cardType );
        }
        $order->save();


        return $chargeData;
    }

    /**
     * Retrieve a credit card charge record from the order meta using the Clover charge ID.
     *
     * @param WC_Order $order    The WooCommerce order instance.
     * @param string   $chargeId The unique Clover charge ID to search for.
     *
     * @return array|null The charge record if found, or null if not found.
     *
     * @throws InvalidArgumentException If the provided charge ID is empty.
     */
    public static function getCreditCardCharge(WC_Order $order, string $chargeId): ?array
    {
        if (trim($chargeId) === '') {
            throw new InvalidArgumentException('Clover Charge ID must be a non-empty string.');
        }

        $charges = $order->get_meta('weeconnectpay_charges', true);
        if (!is_array($charges)) {
            return null;
        }

        return $charges[$chargeId] ?? null;
    }

    /**
     * Process a Clover charge refund via Guzzle.
     *
     * @param string $cloverChargeUuid The Clover charge ID (UUID).
     * @param int    $refundAmount     The refund amount in cents.
     * @param string $reason           The reason for refund. Allowed values: 'requested_by_customer', 'duplicate', 'fraudulent'.
     *                                 Defaults to 'requested_by_customer'.
     * @param string $externalReferenceId Optional external reference ID (max 12 characters).
     *
     * @return array The refund data from Clover.
     *
     * @throws Exception if the refund fails.
     */
    public static function processCloverChargeRefund(
        string $cloverChargeUuid,
        int $refundAmount,
        string $reason = 'requested_by_customer',
        string $externalReferenceId = ''
    ): array {
        // Validate the external reference ID (if provided)
        if (!empty($externalReferenceId) && strlen($externalReferenceId) > 12) {
            throw new InvalidArgumentException('External Reference ID must not exceed 12 characters.');
        }

        // Validate the reason.
        $allowedReasons = ['requested_by_customer', 'duplicate', 'fraudulent'];
        if (!in_array($reason, $allowedReasons, true)) {
            throw new InvalidArgumentException('Invalid reason provided. Allowed values: ' . implode(', ', $allowedReasons));
        }

        try {
            $refundRequest = new RefundCloverChargeRequest();
            $response = $refundRequest->POST(
                $cloverChargeUuid,
                $reason,
                $externalReferenceId,
                $refundAmount // refund amount is provided in cents
            );
            $responseBody = json_decode((string)$response->getBody(), true);

            // Check if the refund was successful.
            if (isset($responseBody['result']) && $responseBody['result'] === 'success') {
                // Return the refund data.
                return $responseBody['data'];
            } else {
                // Parse the response for a meaningful error message instead of dumping raw data
                $error_message = 'Unknown error';
                if ( isset( $responseBody['data']['error']['message'] ) ) {
                    $error_message = $responseBody['data']['error']['message'];
                } elseif ( isset( $responseBody['data']['error']['code'] ) ) {
                    $error_message = $responseBody['data']['error']['code'];
                } elseif ( isset( $responseBody['data']['message'] ) ) {
                    $error_message = $responseBody['data']['message'];
                }
                throw new Exception( 'Refund request failed: ' . $error_message );
            }
        } catch ( Exception $e ) {
            // If it's a Guzzle exception with a response, try to extract the API error
            if ( strpos( $e->getMessage(), 'Refund request failed:' ) === 0 ) {
                throw $e; // Already a parsed error from above, re-throw as-is
            }
            $user_message = $e->getMessage();
            if ( method_exists( $e, 'getResponse' ) && $e->getResponse() ) {
                $body = json_decode( (string) $e->getResponse()->getBody(), true );
                if ( $body ) {
                    if ( isset( $body['data']['error']['message'] ) ) {
                        $user_message = $body['data']['error']['message'];
                    } elseif ( isset( $body['error']['message'] ) ) {
                        $user_message = $body['error']['message'];
                    } elseif ( isset( $body['data']['message'] ) ) {
                        $user_message = $body['data']['message'];
                    } elseif ( isset( $body['message'] ) ) {
                        $user_message = $body['message'];
                    }
                }
            }
            throw new Exception( 'Refund request failed: ' . $user_message );
        }
    }

    /**
     * Update a credit card charge record to 'refunded' status.
     *
     * @param \WC_Order $order
     * @param string    $chargeId
     * @param string    $status The new status ('refunded' or 'voided'). Defaults to 'refunded'.
     *
     * @return void
     *
     * @throws Exception If the charge record is not found.
     */
    public static function updateCreditCardChargeToRefunded(\WC_Order $order, string $chargeId, string $status = 'refunded'): void {
        $charges = $order->get_meta('weeconnectpay_charges', true);
        if (!is_array($charges)) {
            throw new Exception('No credit card charges found for this order.');
        }

        if (!isset($charges[$chargeId])) {
            throw new Exception('Credit card charge with the provided Clover Charge ID not found.');
        }

        $charges[$chargeId]['status'] = $status;
        $order->update_meta_data('weeconnectpay_charges', $charges);
        $order->save();
    }

    /**
     * Add a refund event to a charge's metadata and update its status accordingly.
     *
     * Appends a refund event to the charge's `refunds` array, calculates the total
     * refunded amount, and sets the charge status to 'partially_refunded', 'refunded', or 'voided'.
     *
     * @param WC_Order $order     The WooCommerce order.
     * @param string   $chargeId  The Clover charge ID.
     * @param string   $refundId  The Clover refund ID.
     * @param int      $amount    The refund amount in cents.
     * @param string   $reason    The reason for the refund.
     * @param bool     $isVoid    Whether this is a void (preauth).
     *
     * @return array The updated charge data including the new refund event.
     * @throws Exception If the charge is not found.
     */
    public static function addRefundEventToCharge(
        WC_Order $order,
        string $chargeId,
        string $refundId,
        int $amount,
        string $reason,
        bool $isVoid = false
    ): array {
        $charges = $order->get_meta( 'weeconnectpay_charges', true );
        if ( ! is_array( $charges ) || ! isset( $charges[ $chargeId ] ) ) {
            throw new Exception( 'Charge not found.' );
        }

        $charge = $charges[ $chargeId ];

        // Initialize refunds array if missing (backward compatibility with old charges).
        if ( ! isset( $charge['refunds'] ) || ! is_array( $charge['refunds'] ) ) {
            $charge['refunds'] = array();
        }

        // Build the refund event.
        $event = array(
            'refund_id' => sanitize_text_field( $refundId ),
            'amount'    => absint( $amount ),
            'reason'    => sanitize_text_field( $reason ),
            'timestamp' => time(),
        );

        $charge['refunds'][] = $event;

        // Calculate total refunded from all events.
        $totalRefunded  = self::getChargeRefundedTotal( $charge );
        $originalAmount = (int) $charge['amount'];

        // Determine new status.
        if ( $isVoid ) {
            $charge['status'] = 'voided';
        } elseif ( $totalRefunded >= $originalAmount ) {
            $charge['status'] = 'refunded';
        } else {
            $charge['status'] = 'partially_refunded';
        }

        $charges[ $chargeId ] = $charge;
        $order->update_meta_data( 'weeconnectpay_charges', $charges );
        $order->save();

        return $charge;
    }

    /**
     * Calculate the total refunded amount from a charge's refunds array.
     *
     * @param array $charge The charge data array.
     * @return int Total refunded in cents.
     */
    public static function getChargeRefundedTotal( array $charge ): int {
        if ( ! isset( $charge['refunds'] ) || ! is_array( $charge['refunds'] ) ) {
            return 0;
        }

        $total = 0;
        foreach ( $charge['refunds'] as $refund ) {
            $total += (int) ( $refund['amount'] ?? 0 );
        }
        return $total;
    }

    /**
     * Retrieve all credit card charge metadata from the WooCommerce order.
     *
     * This function retrieves all credit card charge records stored in the order meta under
     * 'weeconnectpay_charges' and returns an array of metadata arrays. Each metadata array contains:
     * - 'charge_id'
     * - 'amount'
     * - 'currency'
     * - 'card_type'
     * - 'card_postal_code'
     * - 'status'
     *
     *
     * @param WC_Order $order The WooCommerce order instance.
     * @return array An array of credit card charge metadata.
     */
    public static function getAllCreditCardCharges(WC_Order $order): array
    {
        $charges = $order->get_meta('weeconnectpay_charges', true);
        if (!is_array($charges)) {
            return [];
        }
        $metadata = [];
        foreach ($charges as $charge) {
            $metadata[] = [
                'charge_id'         => $charge['charge_id'] ?? '',
                'amount'            => $charge['amount'] ?? 0,
                'currency'          => $charge['currency'] ?? '',
                'card_type'         => $charge['card_type'] ?? '',
                'card_postal_code'  => $charge['card_postal_code'] ?? '',
                'status'            => $charge['status'] ?? '',
            ];
        }
        return $metadata;
    }

    /**
     * Add refund notes to an order based on the refund response.
     * This method provides consistent refund note handling across different refund types (credit card, custom tender, etc.)
     *
     * @param WC_Order $order The WooCommerce order
     * @param array|object $refundResponse The decoded response from the refund request
     * @param string|null $reason Optional reason for the refund
     * @return void
     */
    public static function addRefundNotesToOrder(WC_Order $order, $refundResponse, ?string $reason = null): void
    {
        // Convert object to array if needed
        $response = is_object($refundResponse) ? json_decode(json_encode($refundResponse), true) : $refundResponse;

        // Handle standard charge refund response
        if (isset($response['result']) && $response['result'] === 'success' && isset($response['data'])) {
            $data = $response['data'];
            
            if (isset($data['id'], $data['amount'], $data['charge'], $data['status']) && $data['status'] === 'succeeded') {
                $formatted_refund_amount = number_format((float)$data['amount'] / 100, 2, '.', '');

                LogService::info(sprintf(
                    'Refund successful for order #%d - Amount: %.2f, Refund ID: %s, Charge ID: %s',
                    $order->get_id(),
                    $formatted_refund_amount,
                    $data['id'],
                    $data['charge']
                ));

                $chargeRefundNote = '<b>' . __('Refunded: ', 'weeconnectpay') . '</b>';
                $chargeRefundNote .= sprintf(
                    __('%1$s %2$s', 'weeconnectpay'),
                    $formatted_refund_amount,
                    $order->get_currency()
                ) . '<br>';
                $chargeRefundNote .= '<b>' . __('Refund ID: ', 'weeconnectpay') . '</b>' . $data['id'] . '<br>';
                
                // Look for custom tender by charge ID using helper
                $customTenders = WeeConnectPayCustomTenderHelper::getCustomTenders($order);
                if (!empty($customTenders)) {
                    foreach ($customTenders as $tender) {
                        if (isset($tender['charge_id']) && $tender['charge_id'] === $data['charge']) {
                            $chargeRefundNote .= '<b>' . __('Custom Tender: ', 'weeconnectpay') . '</b>' . 
                                esc_html($tender['provider']) . ' (ID: ' . esc_html($tender['id']) . ')<br>';
                            break;
                        }
                    }
                }
                
                $refundedChargeReceiptUrl = CloverReceiptsHelper::getEnvReceiptUrl(
                    $data['charge'],
                    CloverReceiptsHelper::RECEIPT_TYPES['CHARGE']
                );
                $chargeRefundNote .= '<b>' . __('Refunded charge ID: ', 'weeconnectpay') . '</b>' .
                    '<a href="' . esc_url($refundedChargeReceiptUrl) . '">' . esc_html($data['charge']) . '</a><br>';

                if ($reason !== null && $reason !== '') {
                    $chargeRefundNote .= '<b>' . __('Reason: ', 'weeconnectpay') . '</b>' . esc_html($reason);
                }

                $order->add_order_note($chargeRefundNote);
                return;
            }

            // Handle return response (for items)
            if (isset($data['id'], $data['amount_returned'], $data['items'], $data['status']) && $data['status'] === 'returned') {
                $formatted_returned_amount = number_format((float)$data['amount_returned'] / 100, 2, '.', '');

                LogService::info(sprintf(
                    'Return successful for order #%d - Amount: %.2f, Return ID: %s',
                    $order->get_id(),
                    $formatted_returned_amount,
                    $data['id']
                ));

                $returnString = '<b>' . __('Refunded: ', 'weeconnectpay') . '</b>';
                $returnString .= sprintf(
                    __('%1$s %2$s', 'weeconnectpay'),
                    $formatted_returned_amount,
                    $order->get_currency()
                ) . '<br>';
                $returnString .= '<b>' . __('Refund ID: ', 'weeconnectpay') . '</b>' . $data['id'] . '<br>';

                if ($reason !== null && $reason !== '') {
                    $returnString .= '<b>' . __('Reason: ', 'weeconnectpay') . '</b>' . esc_html($reason) . '<br>';
                }

                foreach ($data['items'] as $item_returned) {
                    if (isset($item_returned['parent'], $item_returned['description'], $item_returned['amount'])) {
                        $clover_item_id = $item_returned['parent'];
                        $clover_item_returned_description = $item_returned['description'] ?? null;
                        $formatted_return_amount = number_format((float)$item_returned['amount'] / 100, 2, '.', '');

                        LogService::debug(sprintf(
                            'Returned item details - Order #%d, Item ID: %s, Description: %s, Amount: %.2f',
                            $order->get_id(),
                            $clover_item_id,
                            $clover_item_returned_description,
                            $formatted_return_amount
                        ));

                        $returnString .= '<b>' . __('Returned clover item ID: ', 'weeconnectpay') . '</b>';
                        $returnString .= sprintf(
                            __('%1$s(%2$s %3$s) - %4$s', 'weeconnectpay'),
                            $clover_item_id,
                            $formatted_return_amount,
                            $order->get_currency(),
                            $clover_item_returned_description
                        ) . '<br>';
                    }
                }
                $order->add_order_note($returnString);
                return;
            }
        }

        // Fallback for other response formats or errors
        $errorNote = '<b>' . __('⚠️ Refund Note: ', 'weeconnectpay') . '</b><br>';
        if (isset($response['error']['message'])) {
            $errorNote .= '<b>' . __('Error: ', 'weeconnectpay') . '</b>' . 
                esc_html($response['error']['message']) . '<br>';
        }
        if (isset($response['error']['code'])) {
            $errorNote .= '<b>' . __('Error Code: ', 'weeconnectpay') . '</b>' . 
                esc_html($response['error']['code']);
        }
        
        $order->add_order_note($errorNote);
        LogService::error(sprintf(
            'Unable to format standard refund note for order #%d - Response: %s',
            $order->get_id(),
            json_encode($response)
        ));
    }

	/**
	 * Gets the order type.
	 *
	 * @param WC_Order $order The WooCommerce order.
	 * @return string The order type.
	 */
	public static function get_order_type(WC_Order $order): string
	{
		$order_type = 'default';
		if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order ) ) {
			$order_type = 'subscription';
		} elseif ( 'shop_subscription' === $order->get_type() || 'subscription' === $order->get_created_via() ) {
			$order_type = 'subscription_renewal';
		} elseif ( function_exists( 'wcs_get_subscriptions_for_order' ) && ! empty( wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'any' ) ) ) ) {
			$order_type = 'subscription_early_renewal';
		}
		return $order_type;
	}


	/**
	 * Gets all related orders for a subscription order.
	 *
	 * @param WC_Order $order The WooCommerce order.
	 * @param bool $include_original_order Whether to include the original order in the array.
	 * @return array An array of related orders.
	 */
	public static function get_all_related_orders_for_subscription_order(WC_Order $order, bool $include_original_order = true): array
	{
		// 1. Get the original order.
		$orders = array();
		if ( $include_original_order ) {
			$orders['original_order'] = $order;
		}

		// 2. Get the parent order.
		$parent_id = $order->get_parent_id();
		if ( $parent_id > 0 ) {
			$orders['parent_order'] = wc_get_order( $parent_id );
		}

		// 3. Get the subscription.
		$subscription = wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'any' ) );
		if ( is_array( $subscription ) && ! empty( $subscription ) ) {
			$orders['subscription'] = reset( $subscription );
			$parent_subscription    = $orders['subscription']->get_parent();

			// 4. Maybe get the parent subscription.
			if ( ! empty( $parent_subscription ) ) {
				$orders['parent_subscription'] = $parent_subscription;
			}

			// 5. Get the related orders.
			$related_orders = $orders['subscription']->get_related_orders( 'all' );
			if ( ! empty( $related_orders ) ) {
				foreach ( $related_orders as $related_order ) {
					$orders[ 'related_order_' . $related_order->get_id() ] = $related_order;
				}
			}
		}

		$orders = array_unique( array_filter( $orders ) ); // Removes any empty or duplicatevalues.

		return $orders;
	}

	/**
	 * Gets the token UUID from the subscription order.
	 *
	 * @param WC_Order $order The WooCommerce order.
	 * @return string|null The token UUID, or null if not found.
	 */
	public static function get_token_uuid_from_subscription_order(WC_Order $order): ?string
	{
		$token_uuid = null;

		LogService::info( 'Trying to get existing token UUID from order #' . $order->get_id() );
		// Prevents errors when WooCommerce Subscriptions is not installed.
		if ( ! function_exists( 'wcs_get_subscriptions_for_order' ) ) {
			LogService::error( 'WooCommerce Subscriptions is not installed, returning null.' );
			return $token_uuid;
		}

		// If the order is a subscription payment, get the subscription UUID from the order metadata.
		// Depending on if this is a renewal order, or an update payment request, the data might be available through various ways.
		// First let's check the original order.
		$token_uuid = $order->get_meta( 'weeconnectpay_subscription_uuid' );

		// If the original order doesn't have the token UUID, let's check the related orders.
		if ( empty( $token_uuid ) ) {
			$orders = self::get_all_related_orders_for_subscription_order( $order, false );

			// Loop through each order for the token UUID.
			if ( ! empty( $orders ) ) {
				foreach ( $orders as $key => $current_order ) {
					LogService::info( 'Checking order #' . $current_order->get_id() . ' for token UUID.' );
					$token_uuid = $current_order->get_meta( 'weeconnectpay_subscription_uuid' );
					if ( ! empty( $token_uuid ) ) {
						LogService::info( sprintf(
							'Token UUID %s found in order #%d (%s). Returning.',
							substr( $token_uuid, 0, 8 ) . '-****-****-****-************', // Anonymizes the UUID token displayed in the logs.
							$current_order->get_id(),
							$key
						) );
						break;
					}
				}
			}
		} else {
			LogService::info( sprintf(
				'Token UUID %s found in original order #%d. Returning.',
				substr( $token_uuid, 0, 8 ) . '-****-****-****-************', // Anonymizes the UUID token displayed in the logs.
				$order->get_id()
			) );
		}

		if ( empty( $token_uuid ) ) {
			LogService::info( sprintf(
				'No token UUID found in order #%d or any of its related orders.',
				$order->get_id()
			) );
		}
		$token_uuid = empty( $token_uuid ) ? null : $token_uuid; // Makes sure it returns null if no token UUID is found, and not an empty string.

		return $token_uuid;
	}

	/**
	 * Check if an order is eligible for charge capture.
	 *
	 * @param WC_Order $order WooCommerce order object.
	 * @param string $gateway_id Payment gateway ID (default: 'weeconnectpay').
	 * @return bool True if order can have charge captured, false otherwise.
	 * @since 3.17.0
	 */
	public static function canCaptureCharge( WC_Order $order, string $gateway_id = 'weeconnectpay' ): bool {
		// Not valid if not an order object or wrong payment method
		if ( ! $order instanceof WC_Order || $order->get_payment_method() !== $gateway_id ) {
			return false;
		}

		// Cannot capture if order is cancelled, refunded, or failed
		if ( in_array( $order->get_status(), array( 'cancelled', 'refunded', 'failed' ), true ) ) {
			return false;
		}

		// Cannot capture if order has already been paid
		if ( $order->get_date_paid() ) {
			return false;
		}

		// Can only capture if charge status is 'preauth'
		return 'preauth' === $order->get_meta( 'weeconnectpay_clover_charge_status' );
	}

	/**
	 * Determines the payment method change context based on the order data and subscription information.
	 *
	 * @param array $data The payment form data.
	 * @param WC_Order $order The WooCommerce order instance.
	 * @param string|null $uuid_token Optional. The UUID token for the subscription.
	 * @param string|null $order_type Optional. The order type (e.g., 'subscription_early_renewal').
	 * @return string|null The payment context, or FALSE if the payment method is not being changed, or NULL if no specific context is determined.
	 */
	public static function get_payment_method_change_context(array $data, WC_Order $order, ?string $uuid_token = null, ?string $order_type = null): ?string
	{
		$order_type = empty( $order_type ) ? self::get_order_type( $order ) : $order_type;
		$uuid_token = empty( $uuid_token ) ? self::get_token_uuid_from_subscription_order( $order ) : $uuid_token;

		$is_changing_payment    = ! empty( $data['woocommerce_change_payment'] );
		$has_new_payment_method = array_key_exists( 'wc-weeconnectpay-new-payment-method', $data );
		$has_uuid_token         = ! empty( $uuid_token );

		switch ( true ) {
			// User is explicitly updating the payment method.
			case $is_changing_payment:
				LogService::debug( sprintf( 'The user is updating the payment method for the subscription order (#%d).', $order->get_id() ) );
				return 'change_payment_method';

			// New payment method with existing token (early renewal).
			case $has_uuid_token && $order_type === 'subscription_early_renewal':
				LogService::debug( sprintf( 'The user is renewing an order early (#%d). The credit card used while renewing early will be used for all future renewals.', $order->get_id() ) );
				return 'pay_early_renewal_order';

			// New payment method with existing token (failed payment).
			case $has_new_payment_method && $has_uuid_token:
			case $has_uuid_token && $order_type === 'subscription_renewal' && 'failed' === $order->get_status(): // This is a special case for the classic checkout.
				LogService::debug( sprintf( 'The subscription order (#%d) was on hold. Overriding the credit card token with the UUID token.', $order->get_id() ) );
				return 'pay_failed_order';

			// New payment method without token (first payment).
			case ! $has_uuid_token:
				LogService::debug( sprintf( 'The first payment for the subscription order (#%d) was not processed.', $order->get_id() ) );
				return false;

			// Automatic renewal (empty data).
			case empty( $data ):
				LogService::debug( sprintf( 'This is an automatic renewal payment for the subscription order (#%d).', $order->get_id() ) );
				return false;

			// Default case - no specific context determined.
			default:
				return null;
		}
	}
}


