<?php

namespace WPEverest\URMembership\Admin\Services\Stripe;

use Stripe\Exception\ApiErrorException;
use WPEverest\URMembership\Admin\Repositories\MembershipRepository;
use WPEverest\URMembership\Admin\Repositories\MembersOrderRepository;
use WPEverest\URMembership\Admin\Repositories\MembersSubscriptionRepository;
use WPEverest\URMembership\Admin\Repositories\OrdersRepository;
use WPEverest\URMembership\Admin\Services\EmailService;
use WPEverest\URMembership\Admin\Services\OrderService;
use WPEverest\URMembership\Admin\Services\SubscriptionService;
use WPEverest\URMembership\Admin\Services\PaymentGatewayLogging;
use WPEverest\URMembership\Admin\Services\UpgradeMembershipService;
use WPEverest\URMembership\Local_Currency\Admin\CoreFunctions;

/**
 * Stripe Service Class.
 */
class StripeService {

	/**
	 * Members orders repository instance.
	 *
	 * @var MembersOrderRepository
	 */
	protected $members_orders_repository;

	/**
	 * Members subscription repository instance.
	 *
	 * @var MembersSubscriptionRepository
	 */
	protected $members_subscription_repository;

	/**
	 * Membership repository instance.
	 *
	 * @var MembershipRepository
	 */
	protected $membership_repository;

	/**
	 * Orders repository instance.
	 *
	 * @var OrdersRepository
	 */
	protected $orders_repository;

	/**
	 * Construct.
	 */
	public function __construct() {

		// Initialize necessary repositories.
		$this->members_orders_repository       = new MembersOrderRepository();
		$this->members_subscription_repository = new MembersSubscriptionRepository();
		$this->membership_repository           = new MembershipRepository();
		$this->orders_repository               = new OrdersRepository();

		if ( ! class_exists( 'Stripe\Stripe' ) ) {
			require_once plugin_dir_path( __FILE__ ) . 'lib/stripe-php/init.php';
		}

		try {
			$stripe_settings = self::get_stripe_settings();

			if ( empty( $stripe_settings['secret_key'] ) ) {
				throw new \Exception( 'Stripe secret key is not configured' );
			}

			// Set your secret key.
			\Stripe\Stripe::setApiKey( $stripe_settings['secret_key'] );

		} catch ( \Exception $e ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Failed to initialize Stripe service',
				array(
					'error_code'    => 'INITIALIZATION_FAILED',
					'error_message' => $e->getMessage(),
				)
			);
		}
	}

	/**
	 * Get Stripe settings.
	 *
	 * @return array
	 */
	public static function get_stripe_settings() {
		$is_enabled        = get_option( 'user_registration_stripe_enabled', '' );
		$stripe_default    = ur_string_to_bool( get_option( 'urm_is_new_installation', false ) );
		$has_user_changed  = ur_string_to_bool( get_option( 'urm_stripe_updated_connection_status', false ) );
		$is_stripe_enabled = ( $is_enabled ) ? $is_enabled : ( $has_user_changed ? $stripe_default : ! $stripe_default );

		$mode            = get_option( 'user_registration_stripe_test_mode', false ) ? 'test' : 'live';
		$publishable_key = get_option( sprintf( 'user_registration_stripe_%s_publishable_key', $mode ) );
		$secret_key      = get_option( sprintf( 'user_registration_stripe_%s_secret_key', $mode ) );

		return array(
			'is_stipe_enabled' => $is_stripe_enabled,
			'mode'             => $mode,
			'publishable_key'  => $publishable_key,
			'secret_key'       => $secret_key,
		);
	}

	/**
	 * Default for webhook registration.
	 *
	 * @return string[]
	 */
	public static function get_handled_webhook_events() {
		return array(
			'invoice.payment_succeeded',
			'invoice.payment_failed',
			'payment_intent.payment_failed',
		);
	}

	/**
	 * Create webhook endpoint in Stripe for a given mode.
	 *
	 * @param string $mode 'test' or 'live'.
	 * @return array
	 */
	public static function create_webhook( $mode ) {
		if ( ! class_exists( 'Stripe\Stripe' ) ) {
			require_once plugin_dir_path( __FILE__ ) . 'lib/stripe-php/init.php';
		}
		$secret_key = get_option( 'user_registration_stripe_' . $mode . '_secret_key', '' );
		if ( empty( $secret_key ) ) {
			return array(
				'success' => false,
				'message' => 'No API key configured for ' . $mode . ' mode',
			);
		}

		$webhook_id_option     = 'user_registration_stripe_webhook_id_' . $mode;
		$webhook_secret_option = 'user_registration_stripe_webhook_secret_' . $mode;
		$existing_webhook_id   = get_option( $webhook_id_option, '' );

		if ( ! empty( $existing_webhook_id ) ) {
			try {
				\Stripe\Stripe::setApiKey( $secret_key );
				$webhook = \Stripe\WebhookEndpoint::retrieve( $existing_webhook_id );
				if ( $webhook && $webhook->id ) {
					return array(
						'success'    => true,
						'message'    => 'Webhook already exists for ' . $mode . ' mode',
						'webhook_id' => $webhook->id,
						'mode'       => $mode,
					);
				}
			} catch ( \Exception $e ) {
				delete_option( $webhook_id_option );
				delete_option( $webhook_secret_option );
			}
		}

		try {
			\Stripe\Stripe::setApiKey( $secret_key );
			$webhook_url    = rest_url( 'user-registration/stripe-webhook' );
			$default_events = self::get_handled_webhook_events();
			$enabled_events = apply_filters( 'urm_stripe_webhook_enabled_events', $default_events, $mode );
			$webhook        = \Stripe\WebhookEndpoint::create(
				array(
					'url'            => $webhook_url,
					'enabled_events' => array_values( array_filter( (array) $enabled_events ) ),
					'api_version'    => '2023-10-16',
				)
			);

			$signing_secret = isset( $webhook->secret ) ? $webhook->secret : ( isset( $webhook->signing_secret ) ? $webhook->signing_secret : '' );
			if ( ! empty( $signing_secret ) ) {
				update_option( $webhook_secret_option, $signing_secret );
			}
			update_option( $webhook_id_option, $webhook->id );

			return array(
				'success'        => true,
				'message'        => 'Webhook created successfully for ' . $mode . ' mode',
				'webhook_id'     => $webhook->id,
				'webhook_secret' => $signing_secret,
				'mode'           => $mode,
			);
		} catch ( ApiErrorException $e ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Webhook creation failed',
				array(
					'error_code'    => 'WEBHOOK_CREATE_FAILED',
					'error_message' => $e->getMessage(),
					'mode'          => $mode,
				)
			);
			return array(
				'success' => false,
				'message' => $e->getMessage(),
				'mode'    => $mode,
			);
		}
	}

	/**
	 * Ensure webhook exists for current mode (for backwards compatibility on first payment).
	 */
	public static function ensure_webhook_for_current_mode() {
		$settings = self::get_stripe_settings();
		$mode     = isset( $settings['mode'] ) ? $settings['mode'] : 'test';
		$secret   = get_option( 'user_registration_stripe_webhook_secret_' . $mode, '' );
		if ( ! empty( $secret ) ) {
			return;
		}
		$legacy = get_option( 'user_registration_stripe_webhook_secret', '' );
		if ( ! empty( $legacy ) ) {
			return;
		}
		$result = self::create_webhook( $mode );
		if ( ! empty( $result['success'] ) ) {
			PaymentGatewayLogging::log_general(
				'stripe',
				'Webhook created on first payment',
				'notice',
				array(
					'event_type' => 'webhook_ensure',
					'mode'       => $mode,
				)
			);
		}
	}

	/**
	 * Deprecation function for creating price and product in stripe.
	 *
	 * @deprecated 5.1.5 Use sync_product_and_price_in_stripe().
	 * @param array $post_data          Post data.
	 * @param array $meta_data          Meta data.
	 * @param bool  $should_create_new_price Should create new price.
	 * @return array|void
	 */
	public function create_stripe_product_and_price( $post_data, $meta_data, $should_create_new_price ) {
		$product_version = UR_PRO_ACTIVE ? '6.1.4' : '5.1.5';
		ur_deprecated_function( __METHOD__, $product_version, 'sync_product_and_price_in_stripe' );
		return $this->sync_product_and_price_in_stripe( $post_data );
	}

	/**
	 * Create or Update product and price in stripe based on provided data.
	 *
	 * @param array $membership Membership data.
	 * @return array|void
	 */
	public function sync_product_and_price_in_stripe( $membership ) {

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

		$membership_metas = $membership['meta_value'] ?? array();

		if ( ! is_array( $membership_metas ) ) {
			$membership_metas = json_decode( $membership_metas, true );
		}

		if ( empty( $membership_metas['type'] ) ) {
			return;
		}

		$membership_id   = $membership['ID'];
		$membership_type = $membership_metas['type'];

		$product_name = $membership['post_title'] ?? __( 'Membership Plan', 'user-registration' );
		$amount       = $membership_metas['amount'] ?? 0;
		$currency     = get_option( 'user_registration_payment_currency', 'USD' );

		$stripe_meta = $membership_metas['payment_gateways']['stripe'] ?? array();
		$product_id  = $stripe_meta['product_id'] ?? '';
		$price_id    = $stripe_meta['price_id'] ?? '';

		$amount_in_stripe = $this->calculate_stripe_amount( $amount, $currency );
		$product          = null;

		// Ensure Product Exists.
		if ( ! empty( $product_id ) ) {
			try {
				$product = \Stripe\Product::retrieve( $product_id );
			} catch ( \Stripe\Exception\InvalidRequestException $e ) {
				$product = null;
			}
		}

		if ( ! $product ) {
			$product = \Stripe\Product::create(
				array(
					'name'     => $product_name,
					'metadata' => array(
						'membership_id' => $membership_id,
					),
				)
			);
		}

		$product_id = $product->id;

		// Ensure Price Exists / Is Correct.
		$price_id = $this->ensure_price_in_stripe(
			$membership_type,
			$product_id,
			$price_id,
			$amount_in_stripe,
			$currency,
			$membership_metas
		);

		// Update meta.
		if ( ! isset( $membership_metas['payment_gateways']['stripe'] ) || ! is_array( $membership_metas['payment_gateways']['stripe'] ) ) {
			$membership_metas['payment_gateways']['stripe'] = array();
		}

		$membership_metas['payment_gateways']['stripe']['product_id'] = $product_id;
		$membership_metas['payment_gateways']['stripe']['price_id']   = $price_id;

		update_post_meta(
			$membership_id,
			'ur_membership',
			wp_json_encode( $membership_metas )
		);

		return array( 'success' => true );
	}

	/**
	 * Process Stripe payment.
	 *
	 * @param array $payment_data  Payment data.
	 * @param array $response_data Response data.
	 * @return array
	 */
	public function process_stripe_payment( $payment_data, $response_data ) {
		self::ensure_webhook_for_current_mode();
		$currency   = get_option( 'user_registration_payment_currency', 'USD' );
		$user_email = $response_data['email'];
		$member_id  = $response_data['member_id'];
		$username   = ! empty( $response_data['username'] ) ? $response_data['username'] : '';
		$amount     = 0;
		$team_id    = $payment_data['team_id'] ?? 0;
		if ( $team_id && ! empty( $payment_data['team_data'] ) ) {
			$team_data  = $payment_data['team_data'];
			$seat_model = $team_data['seat_model'] ?? '';

			if ( 'fixed' === $seat_model ) {
				$amount = (float) $team_data['team_price'];
			} else {
				$team_seats = absint( $team_data['team_seats'] ?? 0 );
				if ( $team_seats <= 0 ) {
					PaymentGatewayLogging::log_error(
						'stripe',
						'Payment stopped - Invalid team seats',
						array(
							'error_code' => 'INVALID_TEAM_SEATS',
							'amount'     => $amount,
							'member_id'  => $member_id,
						)
					);
					if ( empty( $payment_data['upgrade'] ) ) {
						wp_delete_user( absint( $member_id ) );
						if ( $team_id ) {
							wp_delete_post( absint( $team_id ) );
						}
					}
					wp_send_json_error(
						array(
							'message' => __( 'Stripe Payment stopped, Invalid team seats.', 'user-registration' ),
						)
					);
				}
				$pricing_model = $team_data['pricing_model'] ?? '';
				if ( 'per_seat' === $pricing_model ) {
					$amount = $team_seats * (float) $team_data['per_seat_price'];
				} else {
					$tier = $payment_data['team_tier_info'] ?? '';
					if ( ! $tier ) {
						PaymentGatewayLogging::log_error(
							'stripe',
							'Payment stopped - Invalid pricing tier',
							array(
								'error_code' => 'INVALID_TIER',
								'amount'     => $amount,
								'member_id'  => $member_id,
							)
						);
						if ( empty( $payment_data['upgrade'] ) ) {
							wp_delete_user( absint( $member_id ) );
							if ( $team_id ) {
								wp_delete_post( absint( $team_id ) );
							}
						}
						wp_send_json_error(
							array(
								'message' => __( 'Stripe Payment stopped, Invalid pricing tier.', 'user-registration' ),
							)
						);
					}
					$amount = $team_seats * (float) $payment_data['team_tier_info']['tier_per_seat_price'];
				}
			}

			$membership_type = $team_data['team_plan_type'] ?? 'unknown';
			if ( 'one-time' === $membership_type ) {
				$membership_type = 'paid';
			}
		} else {
			$amount          = $payment_data['amount'];
			$membership_type = $payment_data['type'] ?? 'unknown';
		}

		$local_currency = ! empty( $response_data['switched_currency'] ) ? $response_data['switched_currency'] : '';
		$ur_zone_id     = ! empty( $response_data['urm_zone_id'] ) ? $response_data['urm_zone_id'] : '';

		if ( ! empty( $local_currency ) && ! empty( $ur_zone_id ) && ur_check_module_activation( 'local-currency' ) ) {
			$currency            = $local_currency;
			$pricing_data        = CoreFunctions::ur_get_pricing_zone_by_id( $ur_zone_id );
			$local_currency_data = ! empty( $payment_data['local_currency'] ) ? $payment_data['local_currency'] : array();

			if ( ! empty( $local_currency_data ) && ur_string_to_bool( $local_currency_data['is_enable'] ) ) {
				$amount = CoreFunctions::ur_get_amount_after_conversion( $amount, $currency, $pricing_data, $local_currency_data, $ur_zone_id );
			}
		}

		if ( ! empty( $response_data['tax_rate'] ) && ! empty( $response_data['tax_calculation_method'] ) && ur_string_to_bool( $response_data['tax_calculation_method'] ) ) {
			$tax_rate   = (float) $response_data['tax_rate'];
			$tax_amount = $amount * $tax_rate / 100;
			$amount     = $amount + $tax_amount;
		}

		PaymentGatewayLogging::log_transaction_start(
			'stripe',
			'Processing Stripe payment',
			array(
				'member_id'       => $member_id,
				'amount'          => $amount,
				'currency'        => $currency,
				'membership_type' => $membership_type,
				'email'           => $user_email,
			)
		);

		$response = array(
			'type' => $membership_type,
		);

		if ( isset( $payment_data['upgrade'] ) && $payment_data['upgrade'] ) {
			$amount = $payment_data['amount'];

		} elseif ( isset( $payment_data['coupon'] ) && ! empty( $payment_data['coupon'] ) && ur_check_module_activation( 'coupon' ) ) {
			$coupon_details = ur_get_coupon_details( $payment_data['coupon'] );

			if ( isset( $coupon_details['coupon_discount_type'] ) && isset( $coupon_details['coupon_discount'] ) ) {
				$discount_amount = ( 'fixed' === $coupon_details['coupon_discount_type'] ) ? $coupon_details['coupon_discount'] : $amount * $coupon_details['coupon_discount'] / 100;
				$amount          = $amount - $discount_amount;
			}
		}

		if ( 'JPY' === $currency ) {
			$amount = abs( $amount );
		} else {
			$amount = abs( $amount ) * 100;
		}

		if ( $amount < 1 ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Payment stopped - Amount less than minimum',
				array(
					'error_code' => 'AMOUNT_TOO_LOW',
					'amount'     => $amount,
					'member_id'  => $member_id,
				)
			);
			if ( empty( $payment_data['upgrade'] ) ) {
				wp_delete_user( absint( $member_id ) );
			}
			wp_send_json_error(
				array(
					'message' => __( 'Stripe Payment stopped, Total amount after discount cannot be less than One.', 'user-registration' ),
				)
			);
		}
		// Return if invalid amount.
		if ( empty( $amount ) || user_registration_sanitize_amount( 0, $currency ) == $amount ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Payment stopped - Invalid or empty amount',
				array(
					'error_code' => 'INVALID_AMOUNT',
					'amount'     => $amount,
					'member_id'  => $member_id,
				)
			);
			if ( empty( $payment_data['upgrade'] ) ) {
				wp_delete_user( absint( $member_id ) );
			}
			wp_send_json_error(
				array(
					'message' => __( 'Stripe Payment stopped, Invalid/Empty amount', 'user-registration' ),
				)
			);
		}

		try {
			PaymentGatewayLogging::log_api_request(
				'stripe',
				'Creating Stripe customer',
				array(
					'endpoint'  => 'Customer::create',
					'email'     => $user_email,
					'member_id' => $member_id,
				)
			);

			$customer = \Stripe\Customer::create(
				array(
					'email' => $user_email,
					'name'  => $username,
				)
			);

			$response['stripe_cus_id'] = $customer->id;

			PaymentGatewayLogging::log_api_response(
				'stripe',
				'Stripe customer created successfully',
				array(
					'customer_id' => $customer->id,
					'member_id'   => $member_id,
				)
			);

			if ( ! empty( $customer ) ) {
				update_user_meta( $member_id, 'ur_payment_customer', $customer->id );
			}
			if ( 'paid' === $membership_type ) {
				PaymentGatewayLogging::log_api_request(
					'stripe',
					'Creating payment intent',
					array(
						'endpoint'    => 'PaymentIntent::create',
						'amount'      => $amount,
						'currency'    => $currency,
						'customer_id' => $customer->id,
					)
				);

				$intent = \Stripe\PaymentIntent::create(
					array(
						'amount'               => $amount,
						'currency'             => $currency,
						'payment_method_types' => array( 'card' ),
						'customer'             => $customer->id,
					)
				);

				$response['client_secret'] = $intent->client_secret;

				PaymentGatewayLogging::log_transaction_success(
					'stripe',
					'Payment intent created successfully',
					array(
						'payment_intent_id' => $intent->id,
						'amount'            => $amount / 100,
						'currency'          => $currency,
						'member_id'         => $member_id,
						'membership_type'   => $membership_type,
					)
				);
			}

			return $response;
		} catch ( ApiErrorException $e ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Stripe API error occurred',
				array(
					'error_code'    => 'STRIPE_API_ERROR',
					'error_message' => $e->getMessage(),
					'member_id'     => $member_id,
				)
			);

			if ( empty( $payment_data['upgrade'] ) ) {
				wp_delete_user( absint( $member_id ) );
			}

			wp_send_json_error(
				array(
					'message' => __( 'Stripe Payment stopped, Incomplete Stripe setup.', 'user-registration' ),
				)
			);
		}
	}

	/**
	 * Update order after Stripe payment confirmation.
	 *
	 * @param array $data Data from frontend.
	 * @return array
	 */
	public function update_order( $data ) {
		$transaction_id         = $data['payment_result']['paymentIntent']['id'] ?? $data['payment_result']['id'] ?? '';
		$payment_status         = sanitize_text_field( $data['payment_status'] );
		$member_id              = isset( $_POST['member_id'] ) ? absint( wp_unslash( $_POST['member_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$membership_process     = urm_get_membership_process( $member_id );
		$selected_membership_id = isset( $_POST['selected_membership_id'] ) && '' !== $_POST['selected_membership_id'] ? absint( wp_unslash( $_POST['selected_membership_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$current_membership_id  = isset( $_POST['current_membership_id'] ) && '' !== $_POST['current_membership_id'] ? absint( wp_unslash( $_POST['current_membership_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$is_purchasing_multiple = ! empty( $membership_process['multiple'] ) && in_array( $selected_membership_id, $membership_process['multiple'], true );
		$is_upgrading           = ! empty( $membership_process['upgrade'] ) && isset( $membership_process['upgrade'][ $current_membership_id ] );
		$transaction_id         = $data['payment_result']['paymentIntent']['id'] ?? '';
		$team_id                = isset( $_POST['team_id'] ) && '' !== $_POST['team_id'] ? absint( wp_unslash( $_POST['team_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$order_id               = $data['order_id'] ?? '';

		if ( empty( $transaction_id ) ) {
			$transaction_id = $data['payment_result']['latest_invoice']['payment_intent']['id'] ?? '';
		}

		$three_d_secure_2_source = $data['payment_result']['latest_invoice']['payment_intent']['next_action']['use_stripe_sdk']['three_d_secure_2_source'] ?? '';

		PaymentGatewayLogging::log_webhook_received(
			'stripe',
			'Stripe payment confirmation callback received',
			array(
				'webhook_type'           => 'payment_confirmation',
				'transaction_id'         => $transaction_id,
				'payment_status'         => $payment_status,
				'member_id'              => $member_id,
				'is_upgrade'             => $is_upgrading,
				'is_purchasing_multiple' => $is_purchasing_multiple,
			)
		);

		$response = array(
			'status' => true,
		);

		$stripe_settings = self::get_stripe_settings();

		if ( empty( $stripe_settings['secret_key'] ) ) {
			$response['status']  = false;
			$response['message'] = __( 'Stripe secret key is not configured.', 'user-registration' );

			return $response;
		}

		\Stripe\Stripe::setApiKey( $stripe_settings['secret_key'] );

		$pi_id = sanitize_text_field( $transaction_id );

		if ( empty( $pi_id ) ) {
			$pi_id = sanitize_text_field( ! empty( $data['payment_result']['latest_invoice']['payment_intent']['id'] ) ? $data['payment_result']['latest_invoice']['payment_intent']['id'] : '' );
		}

		if ( empty( $pi_id ) ) {
			$existing_order = $this->members_orders_repository->get_member_orders( $member_id );
			if ( ! empty( $existing_order ) && 'completed' === $existing_order['status'] ) {
				$response['status']  = true;
				$response['message'] = __( 'Payment already verified.', 'user-registration' );
				return $response;
			}

			PaymentGatewayLogging::log_error(
				'stripe',
				'Payment confirmation rejected: missing PaymentIntent ID',
				array(
					'error_code' => 'MISSING_PAYMENT_INTENT',
					'member_id'  => $member_id,
				)
			);
			$response['status']  = false;
			$response['message'] = __( 'Payment verification failed: missing payment identifier.', 'user-registration' );
			return $response;
		}

		$intent = \Stripe\PaymentIntent::retrieve( $pi_id );

		if ( $intent->status !== 'succeeded' ) {
			$response['status']  = false;
			$response['message'] = __( 'Payment not completed.', 'user-registration' );
			return $response;
		}

		$payment_status = $intent->status;

		$latest_order = $this->orders_repository->get_order_by_transaction_id( $intent->id );
		if ( empty( $latest_order ) ) {
			$latest_order = $this->members_orders_repository->get_member_orders( $member_id );
		}
		$latest_order = is_array( $latest_order ) ? $latest_order : ( $latest_order ? (array) $latest_order : array() );

		if ( ! empty( $latest_order ) && 'stripe' !== $latest_order['payment_method'] ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Payment method mismatch: order payment method is not stripe',
				array(
					'error_code'     => 'PAYMENT_METHOD_MISMATCH',
					'member_id'      => $member_id,
					'order_id'       => $latest_order['ID'],
					'payment_method' => $latest_order['payment_method'],
				)
			);
			$response['status']  = false;
			$response['message'] = __( 'Invalid payment method for this order.', 'user-registration' );
			return $response;
		}

		if ( $this->members_orders_repository->does_transaction_id_exists( $transaction_id, $order_id ) ) {
			$response['status']  = false;
			$response['message'] = __( 'Duplicate transaction id.', 'user-registration' );

			return $response;
		}

		if ( empty( $latest_order ) ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Order not found for member',
				array(
					'error_code' => 'ORDER_NOT_FOUND',
					'member_id'  => $member_id,
				)
			);
			$response['status']  = false;
			$response['message'] = __( 'Order not found for  ' . $member_id, 'user-registration' );

			return $response;
		}

		$membership       = $this->membership_repository->get_single_membership_by_ID( $latest_order['item_id'] );
		$membership_metas = wp_unslash( json_decode( $membership['meta_value'], true ) );
		if ( $team_id ) {
			$team_data = get_post_meta( $team_id, 'urm_team_data', true );
			if ( ! $team_data ) {
				PaymentGatewayLogging::log_error(
					'stripe',
					'Invalid team data',
					array(
						'error_code' => 'INVALID_TEAM_DATA',
						'member_id'  => $member_id,
					)
				);

				return $response;
			}
			$membership_type = $team_data['team_plan_type'] ?? 'unknown';
		} else {
			$membership_type = $membership_metas['type'] ?? 'unknown';
		}

		if ( 'failed' === $payment_status ) {
			$is_renewing = ! empty( $membership_process['renew'] ) && in_array( $latest_order['item_id'], $membership_process['renew'], true );

			$error_msg = __( 'Stripe Payment failed.', 'user-registration' );
			$error_msg = $data['payment_result']['error']['message'] ?? $error_msg;

			PaymentGatewayLogging::log_transaction_failure(
				'stripe',
				'Stripe payment failed',
				array(
					'error_code'      => 'PAYMENT_FAILED',
					'error_message'   => $error_msg,
					'member_id'       => $member_id,
					'transaction_id'  => $transaction_id,
					'membership_type' => $membership_type,
					'is_renewing'     => $is_renewing,
				)
			);

			$this->members_orders_repository->update(
				$latest_order['ID'],
				array(
					'status' => 'failed',
				)
			);

			do_action( 'ur_membership_order_status_failed', $latest_order['ID'], $latest_order, 'failed' );

			if ( ! $is_upgrading && ! $is_renewing && ! $is_purchasing_multiple ) {
				wp_delete_user( absint( $member_id ) );
				$this->members_orders_repository->delete_member_order( $member_id );
			}
			if ( $is_renewing ) {
				unset( $membership_process['upgrade'][ $latest_order['item_id'] ] );
				update_user_meta( $member_id, 'urm_membership_process', $membership_process );

				do_action( 'user_registration_membership_renewal_failed', $member_id, $latest_order['item_id'] );
			}

			$response['message'] = $error_msg;
			$response['status']  = false;

			return $response;
		} elseif ( 'succeeded' === $payment_status ) {
			$member_order = $this->members_orders_repository->get_member_orders( $member_id );

			if ( 'completed' === $member_order['status'] ) {
				$response['message'] = $is_upgrading ? __( 'Membership upgraded successfully.', 'user-registration' ) : __( 'New member has been successfully created with successful stripe payment.', 'user-registration' );
				$response['status']  = true;

				return $response;
			}

			$membership                     = $this->membership_repository->get_single_membership_by_ID( $member_order['item_id'] );
			$membership_metas               = wp_unslash( json_decode( $membership['meta_value'], true ) );
			$membership_metas['post_title'] = $membership['post_title'];
			$member_subscription            = $this->members_subscription_repository->get_subscription_data_by_member_and_membership_id( $member_id, $member_order['item_id'] );
			$is_order_updated               = $this->members_orders_repository->update(
				$member_order['ID'],
				array(
					'status'         => 'completed',
					'transaction_id' => $transaction_id,
				)
			);

			if ( $is_order_updated && in_array( $member_order['order_type'], array( 'paid', 'subscription' ), true ) ) {
				$this->members_subscription_repository->update(
					$member_subscription['ID'],
					array(
						'status'     => 'active',
						'start_date' => date( 'Y-m-d 00:00:00' ),
					)
				);

				if ( $is_upgrading ) {
					PaymentGatewayLogging::log_general(
						'stripe',
						'Upgrade payment completed - Order status changed to completed',
						'success',
						array(
							'event_type'      => 'upgrade_payment_completed',
							'old_status'      => $member_order['status'] ?? 'unknown',
							'new_status'      => 'completed',
							'order_id'        => $member_order['ID'],
							'member_id'       => $member_id,
							'transaction_id'  => $transaction_id,
							'membership_type' => $membership_type,
						)
					);
				} else {
					PaymentGatewayLogging::log_general(
						'stripe',
						'Order status changed to completed',
						'success',
						array(
							'event_type'      => 'status_change',
							'old_status'      => $member_order['status'] ?? 'unknown',
							'new_status'      => 'completed',
							'order_id'        => $member_order['ID'],
							'member_id'       => $member_id,
							'transaction_id'  => $transaction_id,
							'membership_type' => $membership_type,
						)
					);
				}

				PaymentGatewayLogging::log_general(
					'stripe',
					'Subscription status changed to active',
					'success',
					array(
						'event_type'      => $is_upgrading ? 'upgrade_subscription_activated' : 'status_change',
						'old_status'      => $member_subscription['status'] ?? 'unknown',
						'new_status'      => 'active',
						'subscription_id' => $member_subscription['ID'],
						'member_id'       => $member_id,
						'membership_type' => $membership_type,
						'is_upgrade'      => $is_upgrading,
					)
				);
			}
		}

		return $response;
	}

	/**
	 * Sends an email.
	 *
	 * @param int   $id                 The ID of the order.
	 * @param mixed $member_subscription The subscription details of the member.
	 * @param array $membership_metas   Metadata related to the membership.
	 * @param int   $member_id          The ID of the member.
	 * @param array $response           The response data.
	 * @param bool  $is_upgrading       Whether this is an upgrade.
	 *
	 * @return array The result of the email operation.
	 */
	public function sendEmail( int $ID, $member_subscription, array $membership_metas, int $member_id, array $response, $is_upgrading = false ): array {
		$email_service = new EmailService();
		$order_detail  = $this->orders_repository->get_order_detail( $ID );

		if ( ! empty( $order_detail['coupon'] ) ) {
			$order_detail['coupon_discount']      = get_user_meta( $order_detail['user_id'], 'ur_coupon_discount', true );
			$order_detail['coupon_discount_type'] = get_user_meta( $order_detail['user_id'], 'ur_coupon_discount_type', true );
		}

		$email_data = array(
			'subscription'     => $member_subscription,
			'order'            => $order_detail,
			'membership_metas' => $membership_metas,
			'member_id'        => $member_id,
			'membership'       => $member_subscription['item_id'],
		);

		$mail_send = $email_service->send_email( $email_data, 'payment_successful' );

		if ( ! $mail_send ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Payment email could not be sent',
				array(
					'error_code' => 'EMAIL_SEND_FAILED',
					'member_id'  => $member_id,
					'order_id'   => $ID,
				)
			);
		}

		return array(
			'message' => $is_upgrading ? __( 'Membership upgraded successfully.', 'user-registration' ) : __( 'New member has been successfully created with successful stripe payment.', 'user-registration' ),
			'status'  => true,
		);
	}

	/**
	 * Create Stripe subscription.
	 *
	 * @param string $customer_id       Customer ID.
	 * @param string $payment_method_id Payment method ID.
	 * @param int    $member_id         Member ID.
	 * @param bool   $is_upgrading      Whether this is an upgrade.
	 * @param int    $team_id           Team ID.
	 * @return array|\WP_Error
	 */
	public function create_subscription( $customer_id, $payment_method_id, $member_id, $is_upgrading, $team_id ) {
		self::ensure_webhook_for_current_mode();
		$member_order     = $this->members_orders_repository->get_member_orders( $member_id );
		$membership       = $this->membership_repository->get_single_membership_by_ID( $member_order['item_id'] );
		$membership_metas = wp_unslash( json_decode( $membership['meta_value'], true ) );

		$membership_metas['post_title'] = $membership['post_title'];

		$member_subscription = $this->members_subscription_repository->get_subscription_data_by_member_and_membership_id( $member_id, $membership['ID'] );
		$is_automatic        = 'automatic' === get_option( 'user_registration_renewal_behaviour', 'automatic' );

		$membership_process = urm_get_membership_process( $member_id );
		$is_renewing        = ! empty( $membership_process['renew'] ) && in_array( $member_order['item_id'], $membership_process['renew'], true );

		$response = array(
			'status' => false,
		);

		if ( $team_id ) {
			$team_data = get_post_meta( $team_id, 'urm_team_data', true );
			if ( ! $team_data ) {
				PaymentGatewayLogging::log_error(
					'stripe',
					'Invalid team data',
					array(
						'error_code' => 'INVALID_TEAM_DATA',
						'member_id'  => $member_id,
					)
				);

				return $response;
			}
			$membership_type = $team_data['team_plan_type'] ?? 'unknown';
			if ( 'one-time' === $membership_type ) {
				$membership_type = 'paid';
				PaymentGatewayLogging::log_error(
					'stripe',
					'Not a subscription membership',
					array(
						'error_code' => 'NOT_SUBSCRIPTION',
						'member_id'  => $member_id,
					)
				);

				return $response;
			} else {
				$subscription_value    = $team_data['team_duration_value'];
				$subscription_duration = $team_data['team_duration_period'];
			}
		} else {
			$membership_type       = $membership_metas['type'] ?? 'unknown';
			$subscription_value    = $membership_metas['subscription']['value'];
			$subscription_duration = $membership_metas['subscription']['duration'];
		}

		PaymentGatewayLogging::log_api_request(
			'stripe',
			'Creating Stripe subscription',
			array(
				'customer_id'       => $customer_id,
				'payment_method_id' => $payment_method_id,
				'member_id'         => $member_id,
				'is_upgrading'      => $is_upgrading,
				'membership_type'   => $membership_type,
			)
		);

		if ( empty( $member_subscription ) ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Subscription not found for member',
				array(
					'error_code' => 'SUBSCRIPTION_NOT_FOUND',
					'member_id'  => $member_id,
				)
			);

			return $response;
		}

		$stripe_product_details = $membership_metas['payment_gateways']['stripe'] ?? array();

		try {

			$order_detail     = $this->orders_repository->get_order_detail( $member_order['ID'] );
			$order_repository = new OrdersRepository();
			$local_currency   = $order_repository->get_order_meta_by_order_id_and_meta_key( $order_detail['order_id'], 'local_currency' );

			$currency = ! empty( $local_currency['meta_value'] )
				? strtoupper( $local_currency['meta_value'] )
				: strtoupper( get_option( 'user_registration_payment_currency', 'USD' ) );

			$total_amount = ! empty( $member_order['total_amount'] )
				? (float) $member_order['total_amount']
				: 0.0;

			if ( in_array( $currency, array( 'JPY', 'KRW', 'VND', 'CLP', 'IDR' ), true ) ) {
				$total_amount = (int) round( $total_amount );
			} else {
				$total_amount = (int) round( $total_amount * 100 );
			}

			$customer       = \Stripe\Customer::retrieve( $customer_id );
			$payment_method = \Stripe\PaymentMethod::retrieve( $payment_method_id );
			$payment_method->attach(
				array(
					'customer' => $customer->id,
				)
			);

			\Stripe\Customer::update(
				$customer->id,
				array(
					'invoice_settings' => array(
						'default_payment_method' => $payment_method->id,
					),
				)
			);
			$subscription_details = array(
				'customer' => $customer->id,
				'items'    => array(
					array(
						'price' => $stripe_product_details['price_id'],
					),
				),
			);

			// Handle trial period.
			if ( isset( $membership_metas['trial_status'] ) && 'on' === $membership_metas['trial_status'] && ! $team_id ) {
				$trail_period                      = strtotime( date( 'Y-m-d H:i:s', strtotime( '+' . $membership_metas['trial_data']['value'] . ' ' . $membership_metas['trial_data']['duration'] ) ) );
				$subscription_details['trial_end'] = $trail_period;
			} else {
				$subscription_details['expand'] = array( 'latest_invoice.payment_intent' );
			}

			// Handle coupon section.
			if ( ! empty( $order_detail['coupon'] ) ) {
				$coupon_details = ur_get_coupon_details( $order_detail['coupon'] );
				if ( ! empty( $coupon_details['stripe_coupon_id'] ) ) {
					$subscription_details['coupon'] = $coupon_details['stripe_coupon_id'];
				}
			}

			if ( $is_upgrading ) {
				PaymentGatewayLogging::log_general(
					'stripe',
					'Processing Stripe membership upgrade',
					'notice',
					array(
						'event_type'      => 'upgrade_processing',
						'member_id'       => $member_id,
						'membership_type' => $membership_type,
					)
				);

				$previous_subscription = json_decode( get_user_meta( $member_id, 'urm_previous_subscription_data', true ), true );

				if ( $previous_subscription ) {
					$previous_membership = $this->membership_repository->get_single_membership_by_ID( $previous_subscription['item_id'] );

					if ( ! empty( $previous_membership['meta_value'] ) ) {
						$previous_membership_metas = json_decode( wp_unslash( $previous_membership['meta_value'] ), true );

						if ( isset( $previous_membership_metas['type'], $previous_membership_metas['amount'] ) && 'free' !== $previous_membership_metas['type'] ) {
							$new_price     = isset( $membership_metas['amount'] ) ? $membership_metas['amount'] : 0;
							$current_price = $previous_membership_metas['amount'];

							$membership_upgrade_service      = new UpgradeMembershipService();
							$previous_membership_metas['ID'] = $previous_membership['ID'];
							$upgrade_details                 = $membership_upgrade_service->get_upgrade_details( $previous_membership_metas );
							$upgrade_type                    = $upgrade_details['upgrade_type'] ?? '';
							$first_month_price               = $new_price - $current_price;

							if ( 'full' === $upgrade_type ) {
								$first_month_price = $new_price;
							}

							if ( ( $new_price > $current_price ) && ( ! empty( $order_detail['coupon'] ) || 'pro-rata' == $upgrade_type ) ) {
								if ( isset( $order_detail['coupon'] ) && ! empty( $order_detail['coupon'] ) && ur_check_module_activation( 'coupon' ) ) {
									$coupon_details  = ur_get_coupon_details( $order_detail['coupon'] );
									$discount_amount = ( 'fixed' === $coupon_details['coupon_discount_type'] ) ? $coupon_details['coupon_discount'] : $first_month_price * $coupon_details['coupon_discount'] / 100;

									if ( 'full' === $upgrade_type ) {
										$amount = $discount_amount;
									} else {
										$amount = $current_price + $discount_amount;
									}
								} elseif ( 'pro-rata' === $upgrade_type ) {
									$amount = $new_price - $first_month_price;
								}

								$currency = get_option( 'user_registration_payment_currency', 'USD' );
								$amount   = ( 'JPY' === $currency ) ? $amount : $amount * 100;

								PaymentGatewayLogging::log_general(
									'stripe',
									'Creating upgrade discount coupon',
									'debug',
									array(
										'event_type'      => 'upgrade_coupon_created',
										'member_id'       => $member_id,
										'old_price'       => $current_price,
										'new_price'       => $new_price,
										'discount_amount' => $amount,
									)
								);

								$coupon = \Stripe\Coupon::create(
									array(
										'amount_off' => $amount,
										'currency'   => $currency,
										'duration'   => 'once',
										'name'       => 'UpgradeCoupon',
									)
								);

								$subscription_details['coupon'] = $coupon->id;
							}
						}
					}
				}

				$next_subscription = json_decode( get_user_meta( $member_id, 'urm_next_subscription_data', true ), true );
				if ( ! empty( $next_subscription['delayed_until'] ) ) {
					$subscription_details['trial_end'] = strtotime( $next_subscription['delayed_until'] );

					PaymentGatewayLogging::log_general(
						'stripe',
						'Upgrade scheduled with delayed start',
						'notice',
						array(
							'event_type'    => 'upgrade_delayed_start',
							'member_id'     => $member_id,
							'delayed_until' => $next_subscription['delayed_until'],
						)
					);
				} elseif (
					! empty( $previous_subscription['trial_end_date'] ) &&
					isset( $membership_metas['type'], $membership_metas['trial_status'] ) &&
					'subscription' === $membership_metas['type'] &&
					'on' === $membership_metas['trial_status']
				) {
					$subscription_details['trial_end'] = strtotime( $previous_subscription['trial_end_date'] );

					PaymentGatewayLogging::log_general(
						'stripe',
						'Upgrade preserving trial period',
						'notice',
						array(
							'event_type'     => 'upgrade_trial_preserved',
							'member_id'      => $member_id,
							'trial_end_date' => $previous_subscription['trial_end_date'],
						)
					);
				}
			}

			if ( ( ! $is_automatic && ! $is_upgrading ) || $is_renewing ) {
				$value    = $subscription_value;
				$duration = $subscription_duration;

				$subscription_details['cancel_at'] = ( new \DateTime( "+ $value $duration" ) )->getTimestamp();
				if ( $is_renewing ) {
					$next_billing_date = new \DateTime( $member_subscription['next_billing_date'] );

					// Reset next billing date to today if it is in the past.
					$today = new \DateTime( 'today' );
					if ( $next_billing_date < $today ) {
						$next_billing_date = $today;
					}

					$next_billing_date                 = $next_billing_date->modify( "+ $value $duration" )->getTimestamp();
					$subscription_details['cancel_at'] = $next_billing_date;
				}
			}

			PaymentGatewayLogging::log_debug(
				'stripe',
				'Creating subscription with details',
				array(
					'price_id'    => $stripe_product_details['price_id'],
					'customer_id' => $customer_id,
					'member_id'   => $member_id,
					'has_trial'   => isset( $subscription_details['trial_end'] ),
					'has_coupon'  => isset( $subscription_details['coupon'] ),
				)
			);

			$subscription        = \Stripe\Subscription::create( $subscription_details );
			$subscription_status = $subscription->status ?? '';
			PaymentGatewayLogging::log_api_response(
				'stripe',
				'Stripe subscription created',
				array(
					'subscription_id'     => $subscription->id,
					'subscription_status' => $subscription_status,
					'member_id'           => $member_id,
					'membership_type'     => $membership_type,
				)
			);

			$three_ds2_source = $subscription->latest_invoice->payment_intent->next_action->use_stripe_sdk->three_d_secure_2_source ?? '';

			if ( 'active' === $subscription_status || 'trialing' === $subscription_status || ( 'incomplete' === $subscription_status && ! empty( $three_ds2_source ) ) ) {
				$status = ( 'incomplete' === $subscription_status && ! empty( $three_ds2_source ) ) ? 'pending' : 'completed';
				$this->members_orders_repository->update(
					$member_order['ID'],
					array(
						'status'         => $status,
						'transaction_id' => $subscription->latest_invoice->payment_intent->id,
					)
				);

				switch ( $subscription_status ) {
					case 'trialing':
						$subscription_status = ( $is_upgrading && ! empty( $next_subscription['delayed_until'] ) ) ? 'active' : 'trial';
						break;
					case 'incomplete':
						if ( ! empty( $three_ds2_source ) ) {
							$subscription_status = 'pending';
						}
						break;
					default:
						break;
				}

				$this->members_subscription_repository->update(
					$member_order['subscription_id'],
					array(
						'subscription_id' => sanitize_text_field( $subscription->id ),
						'status'          => $subscription_status,
					)
				);

				PaymentGatewayLogging::log_transaction_success(
					'stripe',
					'Stripe subscription activated successfully',
					array(
						'subscription_id'     => $subscription->id,
						'subscription_status' => $subscription_status,
						'order_id'            => $member_order['ID'],
						'member_id'           => $member_id,
						'membership_type'     => $membership_type,
					)
				);

				$this->sendEmail( $member_order['ID'], $member_subscription, $membership_metas, $member_id, $response );

				$response['subscription'] = $subscription;
				$response['message']      = __( 'New member has been successfully created with successful stripe subscription.', 'user-registration' );
				$response['status']       = true;
			} elseif ( 'incomplete' === $subscription_status ) {
				PaymentGatewayLogging::log_general(
					'stripe',
					'Stripe subscription requires 3D Secure verification',
					'notice',
					array(
						'subscription_id'     => $subscription->id,
						'subscription_status' => $subscription_status,
						'member_id'           => $member_id,
						'membership_type'     => $membership_type,
						'requires_action'     => true,
					)
				);

				$this->members_subscription_repository->update(
					$member_order['subscription_id'],
					array(
						'subscription_id' => sanitize_text_field( $subscription->id ),
						'status'          => 'pending',
					)
				);

				$response['subscription'] = $subscription;
				$response['message']      = __( 'Payment requires additional verification.', 'user-registration' );
				$response['status']       = true;
			}

			return $response;
		} catch ( \Exception $e ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Stripe subscription creation failed',
				array(
					'error_code'      => 'STRIPE_SUBSCRIPTION_ERROR',
					'error_message'   => $e->getMessage(),
					'member_id'       => $member_id,
					'membership_type' => $membership_type,
				)
			);

			if ( ! $is_upgrading && ! $is_renewing ) {
				wp_delete_user( absint( $member_id ) );
				$this->members_orders_repository->delete_member_order( $member_id );
				$customer = \Stripe\Customer::retrieve( $customer_id );
				$customer->delete();
			}

			wp_send_json_error(
				array(
					'message' => $e->getMessage(),
				)
			);
		}
	}

	/**
	 * Sync coupon to Stripe.
	 *
	 * @param array $data            Coupon data.
	 * @param array $old_coupon_data Old coupon data.
	 *
	 * @return array
	 * @throws ApiErrorException When Stripe API error occurs.
	 */
	public function sync_coupon( $data, $old_coupon_data ) {

		$currency         = get_option( 'user_registration_payment_currency', 'USD' );
		$all_coupon       = \Stripe\Coupon::all();
		$coupon_exists    = false;
		$stripe_coupon_id = '';
		foreach ( $all_coupon->data as $key => $coupon ) {
			if ( $coupon->metadata->coupon === strtolower( $data['post_data']['post_content'] ) ) {
				$coupon_exists    = true;
				$stripe_coupon_id = $coupon->id;
			}
		}
		// Delete coupon if new changes introduced.
		if ( $coupon_exists && ( $old_coupon_data['coupon_discount'] !== $data['post_meta_data']['coupon_discount'] || $old_coupon_data['coupon_discount_type'] !== $data['post_meta_data']['coupon_discount_type'] ) ) {
			$coupon = \Stripe\Coupon::retrieve( $stripe_coupon_id );
			$coupon->delete();
		}
		$amount = $data['post_meta_data']['coupon_discount'];
		if ( 'JPY' === $currency ) {
			$amount = abs( $amount );
		} else {
			$amount = abs( $amount ) * 100;
		}

		$coupon_details = array(
			'currency' => strtolower( $currency ),
			'duration' => 'once',
			'metadata' => array(
				'coupon' => strtolower( $data['post_data']['post_content'] ),
			),
		);
		if ( 'fixed' === $data['post_meta_data']['coupon_discount_type'] ) {
			$coupon_details['amount_off'] = $amount;
		} elseif ( 'percent' === $data['post_meta_data']['coupon_discount_type'] ) {
			$coupon_details['percent_off'] = $data['post_meta_data']['coupon_discount'];
		}
		$coupon           = \Stripe\Coupon::create( $coupon_details );
		$stripe_coupon_id = $coupon->id;

		$data['post_meta_data']['stripe_coupon_id'] = $stripe_coupon_id;

		return $data;
	}

	/**
	 * Cancel Stripe subscription.
	 *
	 * @param array $order        Order data.
	 * @param array $subscription Subscription data.
	 * @return array
	 */
	public function cancel_subscription( $order, $subscription ) {

		$response = array(
			'status' => false,
		);

		if ( empty( $subscription['subscription_id'] ) ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Stripe subscription ID not found for cancellation',
				array(
					'error_code' => 'MISSING_SUBSCRIPTION_ID',
					'order_id'   => $order['ID'] ?? 'unknown',
				)
			);

			return $response;
		}

		PaymentGatewayLogging::log_general(
			'stripe',
			'Cancelling Stripe subscription',
			'notice',
			array(
				'event_type'      => 'cancellation_initiated',
				'subscription_id' => $subscription['subscription_id'],
				'order_id'        => $order['ID'] ?? 'unknown',
			)
		);

		$stripe_subscription = \Stripe\Subscription::retrieve( $subscription['subscription_id'] );
		if ( $stripe_subscription ) {
			$deleted_sub = \Stripe\Subscription::update(
				$subscription['subscription_id'],
				array(
					'cancel_at_period_end' => true,
				)
			);
		}
		if ( isset( $deleted_sub['canceled_at'] ) && '' !== $deleted_sub['canceled_at'] ) {
			$response['status'] = true;

			PaymentGatewayLogging::log_general(
				'stripe',
				'Subscription cancelled successfully',
				'success',
				array(
					'event_type'           => 'cancellation_success',
					'subscription_id'      => $subscription['subscription_id'],
					'canceled_at'          => date( 'Y-m-d H:i:s', $deleted_sub['canceled_at'] ),
					'cancel_at_period_end' => $deleted_sub['cancel_at_period_end'] ?? false,
				)
			);
		}

		return $response;
	}

	/**
	 * Reactivates stripe subscription if it has been soft cancelled.
	 *
	 * @param string $subscription_id Stripe's Subscription Id.
	 *
	 * @return array $response array Response with status flag and message.
	 */
	public function reactivate_subscription( $subscription_id ) {
		$response           = array(
			'status' => false,
		);
		$subscription       = \Stripe\Subscription::retrieve( $subscription_id );
		$local_subscription = $this->members_subscription_repository->get_subscription_by_subscription_id_meta( $subscription_id );
		if ( isset( $subscription->id ) ) {

			if ( 'active' === $subscription->status ) {

				if ( 'canceled' === $local_subscription['status'] && true === $subscription->cancel_at_period_end ) {
					\Stripe\Subscription::update(
						$subscription_id,
						array(
							'cancel_at_period_end' => false,
						)
					);

					return array(
						'status'  => true,
						'message' => __( 'Subscription reactivated successfully.', 'user-registration' ),
					);
				} else {
					return array(
						'status'  => true,
						'message' => __( 'Subscription reactivated successfully.', 'user-registration' ),
					);
				}
			} elseif ( 'canceled' !== $subscription->status && true === $subscription->cancel_at_period_end ) {
				\Stripe\Subscription::update(
					$subscription_id,
					array(
						'cancel_at_period_end' => false,
					)
				);

				return array(
					'status'  => true,
					'message' => __( 'Subscription reactivated successfully.', 'user-registration' ),
				);
			} else {
				PaymentGatewayLogging::log_general(
					'stripe',
					'Subscription not reactivable',
					'info',
					array(
						'subscription_id'     => $subscription_id,
						'subscription_status' => $subscription->status,
						'event_type'          => 'reactivation_failed',
					)
				);
			}
		} else {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Subscription not found in Stripe',
				array(
					'error_code'      => 'SUBSCRIPTION_NOT_FOUND_IN_STRIPE',
					'subscription_id' => $subscription_id,
				)
			);
			wp_send_json_error(
				array(
					'message' => __( 'Error reactivating the stripe subscription.', 'user-registration' ),
				)
			);
		}

		return $response;
	}

	/**
	 * Handle Stripe webhook.
	 *
	 * @param array  $event           Event data.
	 * @param string $subscription_id Subscription ID.
	 * @return void
	 */
	public function handle_webhook( $event, $subscription_id ) {
		// Verify that the event was sent by Stripe.
		if ( isset( $event['id'] ) ) {
			try {
				$event_id        = sanitize_text_field( $event['id'] );
				$retrieved_event = (array) \Stripe\Event::retrieve( $event_id );
			} catch ( \Exception $e ) {
				PaymentGatewayLogging::log_webhook_received(
					'stripe',
					$e->getMessage(),
					array(
						'webhook_type'    => 'invoice.payment_failed',
						'subscription_id' => $subscription_id,
						'event_id'        => $event['id'] ?? 'unknown',
					)
				);

				wp_die();
			}
		} else {
			wp_die();
		}
		switch ( $event['type'] ) {
			case 'invoice.paid':
			case 'invoice.payment_succeeded':
				$this->handle_succeeded_invoice( $event, $subscription_id );
				break;
			case 'invoice.payment_failed':
				$this->handle_failed_invoice( $event, $subscription_id );
				break;
			case 'payment_intent.payment_failed':
				$this->handle_failed_payment_intent( $event );
				break;
			default:
				break;
		}
	}

	/**
	 * Handle successful invoice webhook.
	 *
	 * @param array  $event           Event.
	 * @param string $subscription_id Subscription ID.
	 * @return void
	 */
	public function handle_succeeded_invoice( $event, $subscription_id ) {
		PaymentGatewayLogging::log_webhook_received(
			'stripe',
			'Invoice payment succeeded webhook received',
			array(
				'webhook_type'    => 'invoice.payment_succeeded',
				'subscription_id' => $subscription_id,
				'event_id'        => $event['id'] ?? 'unknown',
			)
		);

		if ( null === $subscription_id ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Subscription ID is null in webhook',
				array(
					'error_code'   => 'NULL_SUBSCRIPTION_ID',
					'webhook_type' => 'invoice.payment_succeeded',
				)
			);

			return;
		}

		$current_subscription = $this->members_subscription_repository->get_membership_by_subscription_id( $subscription_id, true );

		if ( empty( $current_subscription ) ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Subscription not found for subscription ID',
				array(
					'error_code'      => 'SUBSCRIPTION_NOT_FOUND',
					'subscription_id' => $subscription_id,
				)
			);

			return;
		}
		$subscription_status = ( 'trial' === $current_subscription['status'] ) ? 'active' : $current_subscription['status'];

		$member_id         = $current_subscription['user_id'];
		$membership_id     = $current_subscription['item_id'];
		$invoice_id        = $event['data']['object']['id'];
		$payment_intent_id = $event['data']['object']['payment_intent'] ?? null;
		$invoice_amount    = $event['data']['object']['amount_due']; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable

		// Create new order.
		$order_info = array(
			'membership_data' => array(
				'membership'     => $membership_id,
				'payment_method' => 'stripe',
			),
		);

		$membership       = $this->membership_repository->get_single_membership_by_ID( $membership_id );
		$membership_metas = wp_unslash( json_decode( $membership['meta_value'], true ) );
		$membership_type  = $membership_metas['type'] ?? 'unknown';

		$order_service    = new OrderService();
		$order_repository = new OrdersRepository();

		$existing_order = $order_repository->get_order_by_transaction_id( $payment_intent_id );
		if ( empty( $existing_order ) ) {
			$order_data                                   = $order_service->prepare_orders_data( $order_info, $member_id, $current_subscription );
			$order_data['orders_data']['status']          = 'completed';
			$order_data['orders_data']['user_id']         = $member_id;
			$order_data['orders_data']['created_by']      = $member_id;
			$order_data['orders_data']['trial_status']    = 'off';
			$order_data['orders_data']['notes']           = sanitize_text_field( esc_html__( 'Generated with stripe webhook', 'user-registration' ) );
			$order_data['orders_data']['total_amount']    = $membership_metas['amount'];
			$order_data['orders_data']['transaction_id']  = $payment_intent_id ? sanitize_text_field( $payment_intent_id ) : '';
			$order_data['orders_data']['subscription_id'] = $current_subscription['sub_id'];

			$order_id = $order_repository->create( $order_data );
		} else {
			$order_id = $existing_order['id'] ?? '';
		}

		PaymentGatewayLogging::log_general(
			'stripe',
			'New renewal order created via webhook',
			'notice',
			array(
				'event_type'      => 'renewal_order_created',
				'order_id'        => $order_id,
				'subscription_id' => $subscription_id,
				'invoice_id'      => $invoice_id,
				'amount'          => $membership_metas['amount'],
				'member_id'       => $member_id,
				'membership_type' => $membership_type,
			)
		);

		$next_billing_date = SubscriptionService::get_expiry_date( date( 'Y-m-d' ), $membership_metas['subscription']['duration'], $membership_metas['subscription']['value'] );

		$this->members_subscription_repository->update(
			$current_subscription['sub_id'],
			array(
				'start_date'        => date( 'Y-m-d 00:00:00' ),
				'next_billing_date' => $next_billing_date,
				'expiry_date'       => $next_billing_date,
				'status'            => $subscription_status,
			)
		);
		$membership_process = urm_get_membership_process( $member_id );
		$is_renewing        = ! empty( $membership_process['renew'] ) && in_array( $membership_id, $membership_process['renew'], true );

		if ( $is_renewing ) {
			$subscription_service = new SubscriptionService();
			$subscription_service->update_subscription_data_for_renewal( $current_subscription, $membership_metas );
		}

		PaymentGatewayLogging::log_webhook_processed(
			'stripe',
			'Subscription renewed successfully',
			array(
				'subscription_id'     => $subscription_id,
				'new_billing_date'    => $next_billing_date,
				'subscription_status' => $subscription_status,
				'member_id'           => $member_id,
				'membership_type'     => $membership_type,
			)
		);
		delete_user_meta( $member_id, 'urm_user_just_created' );
	}

	/**
	 * Handle failed invoice webhook.
	 *
	 * @param array  $event           Event.
	 * @param string $subscription_id Subscription ID.
	 * @return void
	 */
	public function handle_failed_invoice( $event, $subscription_id ) {
		PaymentGatewayLogging::log_webhook_received(
			'stripe',
			'Invoice payment failed webhook received',
			array(
				'webhook_type'    => 'invoice.payment_failed',
				'subscription_id' => $subscription_id,
				'event_id'        => isset( $event['id'] ) ? $event['id'] : 'unknown',
			)
		);

		if ( empty( $subscription_id ) ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Subscription ID is empty in invoice.payment_failed webhook',
				array(
					'error_code'   => 'NULL_SUBSCRIPTION_ID',
					'webhook_type' => 'invoice.payment_failed',
				)
			);

			return;
		}

		$current_subscription = $this->members_subscription_repository->get_membership_by_subscription_id( $subscription_id, true );

		if ( empty( $current_subscription ) ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Subscription not found for subscription ID',
				array(
					'error_code'      => 'SUBSCRIPTION_NOT_FOUND',
					'subscription_id' => $subscription_id,
				)
			);

			return;
		}

		$this->members_subscription_repository->update(
			$current_subscription['sub_id'],
			array(
				'status' => 'pending',
			)
		);

		$member_id = $current_subscription['user_id'];
		PaymentGatewayLogging::log_webhook_processed(
			'stripe',
			'Invoice payment failed handled',
			array(
				'subscription_id' => $subscription_id,
				'sub_id'          => $current_subscription['sub_id'],
				'member_id'       => $member_id,
			)
		);
		delete_user_meta( $member_id, 'urm_user_just_created' );
	}

	/**
	 * Handle failed payment intent webhook.
	 *
	 * @param array $event Event.
	 * @return void
	 */
	public function handle_failed_payment_intent( $event ) {
		$payment_intent    = isset( $event['data']['object'] ) ? $event['data']['object'] : array();
		$payment_intent_id = isset( $payment_intent['id'] ) ? $payment_intent['id'] : '';

		PaymentGatewayLogging::log_webhook_received(
			'stripe',
			'Payment intent payment failed webhook received',
			array(
				'webhook_type'      => 'payment_intent.payment_failed',
				'payment_intent_id' => $payment_intent_id,
				'event_id'          => isset( $event['id'] ) ? $event['id'] : 'unknown',
			)
		);

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

		$order = $this->orders_repository->get_order_by_transaction_id( $payment_intent_id );

		$order_id = ( ! empty( $order ) && isset( $order['ID'] ) ) ? $order['ID'] : ( isset( $order['id'] ) ? $order['id'] : null );
		if ( $order_id ) {
			$this->orders_repository->update(
				$order_id,
				array(
					'status' => 'failed',
				)
			);
			PaymentGatewayLogging::log_webhook_processed(
				'stripe',
				'Order marked as failed for failed payment intent',
				array(
					'order_id'          => $order_id,
					'payment_intent_id' => $payment_intent_id,
				)
			);
		}
		delete_user_meta( $order['user_id'], 'urm_user_just_created' );
	}

	/**
	 * Validate Stripe setup.
	 *
	 * @return bool
	 */
	public function validate_setup() {
		$stripe_settings = self::get_stripe_settings();

		return ( empty( $stripe_settings['publishable_key'] ) || empty( $stripe_settings['secret_key'] ) || empty( $stripe_settings['is_stipe_enabled'] ) );
	}

	/**
	 * Revert subscription.
	 *
	 * @param int $member_id Member ID.
	 *
	 * @return void
	 */
	public function revert_subscription( int $member_id ): void {
		$last_subscription = json_decode( get_user_meta( $member_id, 'urm_previous_subscription_data', true ), true );
		$subscription_id   = $last_subscription['ID'];
		unset( $last_subscription['ID'] );
		$last_order   = $this->members_orders_repository->get_member_orders( $member_id );
		$subscription = $this->members_subscription_repository->retrieve( $subscription_id );

		$this->members_subscription_repository->update( $subscription_id, $last_subscription );
		$this->members_orders_repository->delete_member_order( $member_id, false );
		$refund_response = $this->refund( $last_order, $subscription ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable

		delete_user_meta( $member_id, 'urm_previous_subscription_data' );
		$membership_process = urm_get_membership_process( $member_id );

		if ( ! empty( $membership_process ) && isset( $membership_process['upgrade'][ $_POST['current_membership_id'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			unset( $membership_process['upgrade'][ $_POST['current_membership_id'] ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
			update_user_meta( $member_id, 'urm_membership_process', $membership_process );
		}
	}

	/**
	 * Refund an order.
	 *
	 * @param array $order        Order data.
	 * @param array $subscription Subscription data.
	 * @return array
	 */
	public function refund( $order, $subscription ) {
		$response = array(
			'status' => false,
		);
		if ( 'stripe' !== $order['payment_method'] || 0 === absint( $order['total_amount'] ) ) {
			return $response;
		}
		if ( 'subscription' === $order['order_type'] ) {
			return $this->refund_subscription( $subscription, null, true );
		} else {
			return $this->refund_transaction( $order['transaction_id'] );
		}
	}

	/**
	 * Refund subscription.
	 *
	 * @param array    $subscription       Subscription data.
	 * @param int|null $refund_amount      Refund amount.
	 * @param bool     $cancel_subscription Whether to cancel subscription.
	 * @param bool     $cancel_at_end_period Whether to cancel at period end.
	 * @return array
	 */
	public function refund_subscription( $subscription, $refund_amount = null, $cancel_subscription = false, $cancel_at_end_period = false ) {
		try {
			$response        = array(
				'status' => false,
			);
			$subscription_id = $subscription['subscription_id'];
			if ( empty( $subscription_id ) ) {
				return $response;
			}

			$subscription = \Stripe\Subscription::retrieve( $subscription_id );

			$invoice_id = $subscription->latest_invoice;
			if ( ! $invoice_id ) {
				return $response;
			}
			$invoice = \Stripe\Invoice::retrieve( $invoice_id );

			$charge_id = $invoice->charge;
			if ( ! $charge_id ) {
				return $response;
			}

			$refund_params = array(
				'charge' => $charge_id,
			);
			if ( null !== $refund_amount ) {
				$refund_params['amount'] = $refund_amount;
			}

			$refund = \Stripe\Refund::create( $refund_params );

			$cancel_result = null;
			if ( $cancel_subscription ) {
				\Stripe\Subscription::update(
					$subscription_id,
					array(
						'cancel_at_period_end' => $cancel_at_end_period,
					)
				);

				if ( ! $cancel_at_end_period ) {
					$subscription->cancel();
				}
			}

			return array(
				'success'      => true,
				'refund'       => $refund,
				'cancellation' => $cancel_result,
			);
		} catch ( \Exception $e ) {
			return array(
				'success' => false,
				'error'   => $e->getMessage(),
			);
		}
	}

	/**
	 * Refund transaction using payment intent ID.
	 *
	 * @param string $payment_intent_id Payment intent ID.
	 * @return array
	 */
	public function refund_transaction( $payment_intent_id ) {
		try {
			// Full refund using payment intent.
			$refund = \Stripe\Refund::create(
				array(
					'payment_intent' => $payment_intent_id,
				)
			);

			return array(
				'success' => true,
				'refund'  => $refund,
			);

		} catch ( \Stripe\Exception\ApiErrorException $e ) {
			return array(
				'success' => false,
				'error'   => $e->getMessage(),
			);
		}
	}

	/**
	 * Checks if the product exists or not in stripe.
	 *
	 * @param string $product_id Product ID.
	 *
	 * @since 4.4.2
	 * @return array
	 */
	public function check_exists_product_in_stripe( $product_id ) {
		try {
			$product = \Stripe\Product::retrieve( $product_id );

			return array(
				'success' => true,
				'product' => $product,
			);
		} catch ( \Stripe\Exception\ApiErrorException $ex ) {
			return array(
				'success' => false,
				'message' => $ex->getMessage(),
			);
		} catch ( \Exception $ex ) {
			return array(
				'success' => false,
				'message' => $ex->getMessage(),
			);
		}
	}

	/**
	 * Checks if price id exists or not in stripe.
	 *
	 * @param string $price_id Price ID.
	 *
	 * @since 4.4.2
	 * @return array
	 */
	public function check_price_exists_in_stripe( $price_id ) {
		try {
			$price = \Stripe\Price::retrieve( $price_id );

			return array(
				'success' => true,
				'price'   => $price,
			);
		} catch ( \Stripe\Exception\ApiErrorException $ex ) {
			return array(
				'success' => false,
				'message' => $ex->getMessage(),
			);
		}
	}

	/**
	 * Creates price for existing product if price_id not found.
	 *
	 * @param string $product_id Product ID.
	 * @param array  $meta_data  Meta data.
	 *
	 * @since 4.4.2
	 * @return array
	 */
	public function create_stripe_price_for_existing_product( $product_id, $meta_data ) {
		$currency = get_option( 'user_registration_payment_currency', 'USD' );

		try {
			// Calculate amount in cents (or leave as-is for JPY).
			$amount = ( 'JPY' === $currency ) ? abs( $meta_data['amount'] ) : abs( $meta_data['amount'] ) * 100;

			// Prepare price data.
			$price_details = array(
				'unit_amount' => $amount,
				'currency'    => strtolower( $currency ),
				'product'     => $product_id,
			);

			// Add recurring details if it's a subscription.
			if ( 'subscription' === $meta_data['type'] ) {
				$price_details['recurring'] = array(
					'interval'       => $meta_data['subscription']['duration'],
					'interval_count' => $meta_data['subscription']['value'],
				);
			}

			// Create new price in Stripe.
			$price = \Stripe\Price::create( $price_details );

			return array(
				'success' => true,
				'price'   => $price,
			);

		} catch ( \Stripe\Exception\ApiErrorException $e ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Error creating Stripe price',
				array(
					'error_code'    => 'PRICE_CREATION_FAILED',
					'error_message' => $e->getMessage(),
				)
			);

			return array(
				'success' => false,
				'message' => $e->getMessage(),
			);
		}
	}

	/**
	 * Retries subscription for Stripe subscription payments.
	 *
	 * @param array $subscription Subscription data.
	 * @return array
	 */
	public function retry_subscription( $subscription ) {
		$response = array(
			'status'  => false,
			'message' => '',
		);

		if ( empty( $subscription['sub_id'] ) ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Stripe subscription ID not found for retry',
				array(
					'error_code' => 'MISSING_SUBSCRIPTION_ID',
				)
			);

			$response['message'] = __( 'Subscription ID not found', 'user-registration' );

			return $response;
		}

		PaymentGatewayLogging::log_general(
			'stripe',
			'Retrying Stripe subscription payment',
			'notice',
			array(
				'event_type'      => 'retry_initiated',
				'subscription_id' => $subscription['sub_id'],
				'user_id'         => $subscription['user_id'] ?? 'unknown',
				'item_id'         => $subscription['item_id'] ?? 'unknown',
			)
		);

		try {
			$stripe_subscription = \Stripe\Subscription::retrieve( $subscription['sub_id'] );

			if ( ! $stripe_subscription ) {
				PaymentGatewayLogging::log_error(
					'stripe',
					'Stripe subscription not found for retry',
					array(
						'subscription_id' => $subscription['sub_id'],
					)
				);

				$response['message'] = __( 'Subscription not found in Stripe', 'user-registration' );

				return $response;
			}

			if ( in_array( $stripe_subscription->status, array( 'past_due', 'unpaid' ), true ) ) {
				// Update subscription to retry payment.
				$updated_subscription = \Stripe\Subscription::update(
					$subscription['sub_id'],
					array(
						'default_payment_method' => $stripe_subscription->default_payment_method,
						'off_session'            => true,
					)
				);

				PaymentGatewayLogging::log_api_response(
					'stripe',
					'Stripe subscription updated for retry',
					array(
						'subscription_id' => $subscription['sub_id'],
						'old_status'      => $stripe_subscription->status,
						'new_status'      => $updated_subscription->status,
					)
				);

				if ( 'active' === $updated_subscription->status || 'trialing' === $updated_subscription->status ) {
					PaymentGatewayLogging::log_transaction_success(
						'stripe',
						'Subscription payment retry successful',
						array(
							'subscription_id' => $subscription['sub_id'],
							'user_id'         => $subscription['user_id'] ?? 'unknown',
							'status'          => $updated_subscription->status,
						)
					);

					$response['status']  = true;
					$response['message'] = __( 'Subscription payment retried successfully', 'user-registration' );
				} else {
					PaymentGatewayLogging::log_error(
						'stripe',
						'Subscription payment retry - Unexpected status',
						array(
							'subscription_id' => $subscription['sub_id'],
							'status'          => $updated_subscription->status,
						)
					);

					// Notify user via email about a failed retry attempt.
					$current_subscription = $this->members_subscription_repository->get_membership_by_subscription_id( $subscription['sub_id'], true );
					if ( ! empty( $current_subscription ) ) {
						$member_id = $current_subscription['user_id'];

						if ( 1 === (int) get_user_meta( $member_id, 'urm_is_payment_retrying', true ) ) {
							$latest_order     = $this->members_orders_repository->get_member_orders( $member_id );
							$membership       = $this->membership_repository->get_single_membership_by_ID( $current_subscription['item_id'] );
							$membership_metas = wp_unslash( json_decode( $membership['meta_value'], true ) );
							$email_service    = new EmailService();
							$email_data       = array(
								'subscription'     => $current_subscription,
								'order'            => $latest_order,
								'membership_metas' => $membership_metas,
								'member_id'        => $member_id,
							);
							$email_service->send_email( $email_data, 'payment_retry_failed' );
						}
						$response['message'] = __( 'Subscription retry did not resolve the issue', 'user-registration' );
					}
				}
			} elseif ( 'active' === $stripe_subscription->status || 'trialing' === $stripe_subscription->status ) {
				// Scenario: if automatic retry is enabled in stripe dashboard, it might be already active via smart retry.
				PaymentGatewayLogging::log_general(
					'stripe',
					'Subscription is already active - no retry needed',
					'notice',
					array(
						'subscription_id' => $subscription['sub_id'],
						'status'          => $stripe_subscription->status,
						'user_id'         => $subscription['user_id'] ?? 'unknown',
					)
				);
				$response['status']  = true;
				$response['message'] = __( 'Subscription is already active', 'user-registration' );
			} else {
				PaymentGatewayLogging::log_error(
					'stripe',
					'Subscription cannot be recovered.',
					array(
						'subscription_id' => $subscription['sub_id'],
						'status'          => $stripe_subscription->status,
					)
				);

				$response['message'] = sprintf( __( 'Subscription status is %s and cannot be retried', 'user-registration' ), $stripe_subscription->status );
			}

			return $response;
		} catch ( ApiErrorException $e ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Stripe subscription retry failed',
				array(
					'error_code'      => $e->getStripeCode(),
					'error_message'   => $e->getMessage(),
					'subscription_id' => $subscription['subscription_id'],
					'user_id'         => $subscription['user_id'] ?? 'unknown',
				)
			);

			$response['message'] = $e->getMessage();

			return $response;
		}
	}

	/**
	 * Validate Stripe API credentials.
	 *
	 * @return bool True if credentials are valid, false otherwise.
	 */
	public function validate_credentials() {
		try {
			\Stripe\Account::retrieve();
			PaymentGatewayLogging::log_general(
				'stripe',
				'Stripe credentials validated successfully',
				'success',
				array(
					'event_type' => 'credential_validation',
				)
			);

			return true;
		} catch ( \Stripe\Exception\AuthenticationException $e ) {
			PaymentGatewayLogging::log_error(
				'stripe',
				'Stripe authentication failed - Invalid credentials',
				array(
					'error_code'    => 'AUTHENTICATION_FAILED',
					'error_message' => $e->getMessage(),
				)
			);

			return false;
		} catch ( \Exception $e ) {

			PaymentGatewayLogging::log_error(
				'stripe',
				'Stripe credential validation failed',
				array(
					'error_code'    => 'VALIDATION_ERROR',
					'error_message' => $e->getMessage(),
				)
			);

			return false;
		}
	}

	/**
	 * Calculate the amount to be sent to Stripe based on the currency and amount.
	 *
	 * @param int    $amount   The amount in the standard currency format (e.g., 10.00 for $10).
	 * @param string $currency The currency code (e.g., 'USD', 'JPY').
	 * @return int The amount in the smallest currency unit (e.g., cents for USD).
	 */
	private function calculate_stripe_amount( $amount, $currency ) {

		$zero_decimal = array( 'JPY', 'KRW', 'VND', 'CLP', 'IDR' );

		if ( in_array( strtoupper( $currency ), $zero_decimal, true ) ) {
			return (int) round( $amount );
		}

		return (int) round( $amount * 100 );
	}

	/**
	 * Ensure the price exists in Stripe and is up to date, otherwise create a new price.
	 *
	 * @param string $type             The type of the price, e.g., 'paid' or 'subscription'.
	 * @param string $product_id       The Stripe product ID to which the price is associated.
	 * @param string $price_id         The existing Stripe price ID, if any.
	 * @param int    $amount           The amount in the smallest currency unit.
	 * @param string $currency         The currency code (e.g., 'USD', 'JPY').
	 * @param array  $membership_metas The membership metadata which may contain subscription details.
	 * @return string The final price ID.
	 */
	private function ensure_price_in_stripe(
		$type,
		$product_id,
		$price_id,
		$amount,
		$currency,
		$membership_metas
	) {
		$interval = $membership_metas['subscription_duration']
			?? $membership_metas['subscription']['duration']
			?? null;

		$interval_count = (int) (
			$membership_metas['subscription_value']
			?? $membership_metas['subscription']['value']
			?? 1
		);

		// Create price if missing.
		if ( empty( $price_id ) ) {
			return $this->create_price_in_stripe(
				$type,
				$product_id,
				$amount,
				$currency,
				$interval,
				$interval_count
			);
		}

		// Try retrieving existing price.
		$existing_price = null;

		try {
			$existing_price = \Stripe\Price::retrieve( $price_id );
		} catch ( \Stripe\Exception\InvalidRequestException $e ) {
			return $this->create_price_in_stripe(
				$type,
				$product_id,
				$amount,
				$currency,
				$interval,
				$interval_count
			);
		}

		if ( ! $existing_price ) {
			return $this->create_price_in_stripe(
				$type,
				$product_id,
				$amount,
				$currency,
				$interval,
				$interval_count
			);
		}

		// Check if price amount has been updated.
		$needs_new = (
			$existing_price->unit_amount !== $amount
		);

		if ( empty( $existing_price->recurring ) && 'subscription' === $type ) {
			// If price is not recurring but type is subscription, we need to create a new price.
			$needs_new = true;
		} elseif ( ! empty( $existing_price->recurring ) && 'subscription' !== $type ) {
			// If price is recurring but type is not subscription, we need a new price.
			$needs_new = true;
		} elseif ( ! empty( $existing_price->recurring ) && 'subscription' === $type ) {
			// If price is recurring but interval or interval count has been updated, we need to create a new price.
			$needs_new = $needs_new || $existing_price->recurring->interval !== $interval ||
				$existing_price->recurring->interval_count !== $interval_count;
		}

		if ( $needs_new ) {
			return $this->create_price_in_stripe(
				$type,
				$product_id,
				$amount,
				$currency,
				$interval,
				$interval_count
			);
		}

		return $price_id;
	}

	/**
	 * Create a new price in Stripe for the given product with the specified details.
	 *
	 * @param string      $type           The type of the price, e.g., 'paid' or 'subscription'.
	 * @param string      $product_id     The Stripe product ID to which the price will be associated.
	 * @param int         $amount         The amount for the price in the smallest currency unit.
	 * @param string      $currency       The currency code (e.g., 'USD', 'JPY').
	 * @param string|null $interval       The interval for recurring prices (e.g., 'month', 'year').
	 * @param integer     $interval_count The number of intervals between billing cycles.
	 * @return string The ID of the newly created Stripe price.
	 */
	private function create_price_in_stripe(
		$type,
		$product_id,
		$amount,
		$currency,
		$interval = null,
		$interval_count = 1
	) {

		$data = array(
			'unit_amount' => $amount,
			'currency'    => $currency,
			'product'     => $product_id,
		);

		if ( 'subscription' === $type ) {
			$data['recurring'] = array(
				'interval'       => $interval,
				'interval_count' => $interval_count,
			);
		}

		$price = \Stripe\Price::create( $data );

		return $price->id;
	}

	/**
	 * Backfill missed subscription update record.
	 *
	 * @param string $last_synced Last synced timestamp to fetch events from.
	 * @return void
	 */
	public function run_missed_subscription_backfill( $last_synced ) {
		$events = \Stripe\Event::all(
			array(
				'types'   => array( 'customer.subscription.updated', 'customer.subscription.deleted' ),
				'created' => array(
					'gte' => $last_synced,
				),
				'limit'   => 100,
			)
		);

		foreach ( $events->autoPagingIterator() as $event ) {
			$subscription    = $event->data->object;
			$subscription_id = $subscription->id;

			if ( empty( $subscription_id ) ) {
				continue;
			}

			$membership_subscription = $this->members_subscription_repository->get_subscription_by_subscription_id_meta( $subscription_id );

			if ( empty( $membership_subscription ) ) {
				continue;
			}

			if ( $subscription->status !== $membership_subscription['status'] ) {

				if ( 'active' === $subscription->status && 'canceled' === $membership_subscription['status'] && $subscription->cancel_at_period_end ) {
					// If Stripe subscription is active but our record is canceled and Stripe subscription is set to cancel at period end, we should not update the status to active as it will cause confusion. We will wait for the next event to update the status.
					continue;
				}

				$update_data = array(
					'status' => $subscription->status,
				);

				$current_period_end = $subscription->current_period_end ?? null;
				if ( ! empty( $current_period_end ) ) {
					$next_billing_date                = gmdate( 'Y-m-d H:i:s', $current_period_end );
					$update_data['next_billing_date'] = $next_billing_date;
					$update_data['expiry_date']       = $next_billing_date;
				}

				$this->members_subscription_repository->update(
					$membership_subscription['ID'],
					$update_data
				);
			}
		}
	}

	/**
	 * Backfill missed payments events record.
	 *
	 * @param string $last_synced Last synced timestamp to fetch events from.
	 * @return void
	 */
	public function run_missed_payment_backfill( $last_synced ) {
		$events = \Stripe\Event::all(
			array(
				'types'   => array( 'invoice.payment_succeeded', 'invoice.paid' ),
				'created' => array(
					'gte' => $last_synced,
				),
				'limit'   => 100,
			)
		);

		foreach ( $events->autoPagingIterator() as $event ) {
			$invoice           = $event->data->object;
			$subscription_id   = $invoice->subscription ?? null;
			$payment_intent_id = $invoice->payment_intent ?? null;

			if ( empty( $subscription_id ) || empty( $payment_intent_id ) ) {
				continue;
			}

			$membership_subscription = $this->members_subscription_repository->get_subscription_by_subscription_id_meta( $subscription_id );

			if ( ! empty( $membership_subscription ) ) {
				$payment = $this->orders_repository->get_order_by_transaction_id( $payment_intent_id );

				if ( empty( $payment ) ) {
					$payment = $this->orders_repository->get_order_by_transaction_id( $subscription_id );
					$this->orders_repository->delete( $payment['ID'] );

					$subscription   = $this->members_subscription_repository->retrieve( $membership_subscription['ID'] );
					$payment_intent = \Stripe\PaymentIntent::retrieve( $payment_intent_id );
					$paid_amount    = $payment_intent->amount_received / 100; // Convert from cents to dollars
					$created_at     = gmdate( 'Y-m-d H:i:s', $invoice->created );

					$order_data = array(
						'orders_data'      => array(
							'user_id'         => absint( $subscription['user_id'] ),
							'item_id'         => $subscription['item_id'],
							'subscription_id' => $subscription['ID'],
							'created_by'      => $subscription['user_id'],
							'transaction_id'  => $payment_intent_id,
							'payment_method'  => 'stripe',
							'total_amount'    => $paid_amount,
							'status'          => 'completed',
							'order_type'      => 'subscription',
							'trial_status'    => 'off',
							'notes'           => 'Backfilled order for missed payment event',
							'created_at'      => $created_at,
						),
						'orders_meta_data' => array(
							array(
								'meta_key'   => 'is_admin_created',
								'meta_value' => false,
							),
						),
					);
					$this->orders_repository->create( $order_data );
				}
			}
		}
	}
}
