<?php
/**
 * Stripe Payment Gateway
 *
 * Handles Stripe payment processing via Payment Element.
 *
 * @package GGM_Commerce
 */

// If this file is called directly, abort.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter

// Load base class.
require_once dirname( __DIR__ ) . '/abstract-payment-gateway.php';

/**
 * Class GGM_Commerce_Gateway_Stripe
 *
 * Implements Stripe payment processing.
 */
class GGM_Commerce_Gateway_Stripe extends GGM_Commerce_Payment_Gateway {

	/**
	 * Stripe API version.
	 *
	 * @var string
	 */
	const API_VERSION = '2023-10-16';

	/**
	 * Stripe API base URL.
	 *
	 * @var string
	 */
	const API_BASE = 'https://api.stripe.com/v1';

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->id                     = 'stripe';
		$this->title                  = __( 'Stripe', 'ggm-commerce' );
		$this->description            = __( 'Pay securely with your credit or debit card.', 'ggm-commerce' );
		$this->premium_component_slug = 'stripe-gateway';
		$this->supports               = array( 'cards', 'apple_pay', 'google_pay', 'link' );

		parent::__construct();
	}

	/**
	 * Get gateway icon URL.
	 *
	 * @return string
	 */
	public function get_icon() {
		return plugins_url( 'assets/images/stripe-logo.svg', dirname( __DIR__, 3 ) . '/goose-commerce.php' );
	}

	/**
	 * Get settings fields for admin configuration.
	 *
	 * @return array
	 */
	public function get_settings_fields() {
		return array(
			'enabled'              => array(
				'title'   => __( 'Enable/Disable', 'ggm-commerce' ),
				'type'    => 'checkbox',
				'label'   => __( 'Enable Stripe payments', 'ggm-commerce' ),
				'default' => 'no',
			),
			'test_mode'            => array(
				'title'       => __( 'Test Mode', 'ggm-commerce' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable test mode', 'ggm-commerce' ),
				'description' => __( 'Use Stripe test API keys for development.', 'ggm-commerce' ),
				'default'     => 'yes',
			),
			'live_publishable_key' => array(
				'title'       => __( 'Live Publishable Key', 'ggm-commerce' ),
				'type'        => 'text',
				'description' => __( 'Get this from your Stripe Dashboard → Developers → API keys', 'ggm-commerce' ),
				'default'     => '',
			),
			'live_secret_key'      => array(
				'title'       => __( 'Live Secret Key', 'ggm-commerce' ),
				'type'        => 'password',
				'description' => __( 'Keep this secret! Never share it.', 'ggm-commerce' ),
				'default'     => '',
			),
			'test_publishable_key' => array(
				'title'       => __( 'Test Publishable Key', 'ggm-commerce' ),
				'type'        => 'text',
				'description' => __( 'Starts with pk_test_', 'ggm-commerce' ),
				'default'     => '',
			),
			'test_secret_key'      => array(
				'title'       => __( 'Test Secret Key', 'ggm-commerce' ),
				'type'        => 'password',
				'description' => __( 'Starts with sk_test_', 'ggm-commerce' ),
				'default'     => '',
			),
			'webhook_secret'       => array(
				'title'       => __( 'Webhook Secret', 'ggm-commerce' ),
				'type'        => 'password',
				'description' => sprintf(
					/* translators: %s: webhook URL */
					__( 'Configure your webhook at Stripe Dashboard. Endpoint URL: %s', 'ggm-commerce' ),
					'<code>' . rest_url( 'ggm-commerce/v1/webhooks/stripe' ) . '</code>'
				),
				'default'     => '',
			),
			'payment_methods'      => array(
				'title'   => __( 'Payment Methods', 'ggm-commerce' ),
				'type'    => 'section',
				'default' => '',
			),
			'enable_cards'         => array(
				'title'   => __( 'Credit/Debit Cards', 'ggm-commerce' ),
				'type'    => 'checkbox',
				'label'   => __( 'Accept card payments (always enabled)', 'ggm-commerce' ),
				'default' => 'yes',
				'custom_attributes' => array( 'disabled' => 'disabled' ),
			),
			'enable_apple_pay'     => array(
				'title'       => __( 'Apple Pay', 'ggm-commerce' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable Apple Pay', 'ggm-commerce' ),
				'description' => __( 'Requires HTTPS and domain verification.', 'ggm-commerce' ),
				'default'     => 'yes',
			),
			'enable_google_pay'    => array(
				'title'   => __( 'Google Pay', 'ggm-commerce' ),
				'type'    => 'checkbox',
				'label'   => __( 'Enable Google Pay', 'ggm-commerce' ),
				'default' => 'yes',
			),
			'enable_link'          => array(
				'title'       => __( 'Link by Stripe', 'ggm-commerce' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable Link (Stripe wallet)', 'ggm-commerce' ),
				'description' => __( 'One-click checkout for returning customers.', 'ggm-commerce' ),
				'default'     => 'yes',
			),
		);
	}

	/**
	 * Check if gateway is properly configured.
	 *
	 * @return bool
	 */
	public function is_configured() {
		$publishable_key = $this->get_publishable_key();
		$secret_key      = $this->get_secret_key();

		return ! empty( $publishable_key ) && ! empty( $secret_key );
	}

	/**
	 * Get publishable key based on mode.
	 *
	 * @return string
	 */
	public function get_publishable_key() {
		$prefix = $this->is_test_mode() ? 'test_' : 'live_';
		return $this->get_option( $prefix . 'publishable_key', '' );
	}

	/**
	 * Get secret key based on mode.
	 *
	 * @return string
	 */
	public function get_secret_key() {
		$prefix = $this->is_test_mode() ? 'test_' : 'live_';
		$key    = $this->get_option( $prefix . 'secret_key', '' );

		// Try to decrypt if it looks encrypted.
		if ( ! empty( $key ) && strpos( $key, 'sk_' ) !== 0 ) {
			$decrypted = $this->decrypt_key( $key );
			if ( strpos( $decrypted, 'sk_' ) === 0 ) {
				return $decrypted;
			}
		}

		return $key;
	}

	/**
	 * Get enabled payment method types for Stripe.
	 *
	 * @return array
	 */
	public function get_enabled_payment_methods() {
		$methods = array( 'card' ); // Card is always enabled.

		if ( 'yes' === $this->get_option( 'enable_apple_pay', 'yes' ) ) {
			$methods[] = 'apple_pay';
		}

		if ( 'yes' === $this->get_option( 'enable_google_pay', 'yes' ) ) {
			$methods[] = 'google_pay';
		}

		if ( 'yes' === $this->get_option( 'enable_link', 'yes' ) ) {
			$methods[] = 'link';
		}

		return $methods;
	}

	/**
	 * Create a PaymentIntent for an order.
	 *
	 * @param int $order_id Order ID.
	 * @return array Result with client_secret on success.
	 */
	public function create_payment_intent( $order_id ) {
		$order = $this->get_order( $order_id );

		if ( ! $order ) {
			return $this->error_response( __( 'Order not found.', 'ggm-commerce' ), 'order_not_found' );
		}

		$amount   = $this->get_amount_in_cents( $order->total_amount );
		$currency = strtolower( $this->get_currency() );

		$payment_method_types = array( 'card' );
		if ( 'yes' === $this->get_option( 'enable_link', 'yes' ) ) {
			$payment_method_types[] = 'link';
		}

		$intent_data = array(
			'amount'               => $amount,
			'currency'             => $currency,
			'payment_method_types' => $payment_method_types,
			'metadata'             => array(
				'order_id'     => $order_id,
				'order_number' => $order->order_number,
			),
			'description'          => sprintf(
				/* translators: %s: order number */
				__( 'Order %s', 'ggm-commerce' ),
				$order->order_number
			),
		);

		// Add customer email for receipts.
		if ( ! empty( $order->customer_email ) ) {
			$intent_data['receipt_email'] = $order->customer_email;
		}

		// For subscription orders, enable future usage so payment method can be reused for renewals.
		if ( ! empty( $order->subscription_id ) ) {
			$intent_data['setup_future_usage'] = 'off_session';
		}

		$response = $this->api_request( 'payment_intents', $intent_data, 'POST' );

		if ( isset( $response['error'] ) ) {
			$this->log_payment_event(
				$order_id,
				'',
				'payment_intent.create_failed',
				$order->total_amount,
				$currency,
				'failed',
				$response
			);

			return $this->error_response(
				$response['error']['message'] ?? __( 'Failed to create payment intent.', 'ggm-commerce' ),
				$response['error']['code'] ?? 'stripe_error'
			);
		}

		// Store payment intent ID on the order.
		global $wpdb;
		$wpdb->update(
			"{$wpdb->prefix}ggmc_orders",
			array(
				'payment_intent_id' => $response['id'],
				'payment_method'    => 'stripe',
			),
			array( 'id' => $order_id )
		);

		$this->log_payment_event(
			$order_id,
			$response['id'],
			'payment_intent.created',
			$order->total_amount,
			$currency,
			'requires_payment_method',
			$response
		);

		return $this->success_response(
			array(
				'client_secret'    => $response['client_secret'],
				'payment_intent'   => $response['id'],
				'publishable_key'  => $this->get_publishable_key(),
				'payment_methods'  => $this->get_enabled_payment_methods(),
			)
		);
	}

	/**
	 * Confirm a payment after frontend completion.
	 *
	 * @param string $payment_intent_id Payment Intent ID.
	 * @param int    $fallback_order_id Optional order ID from frontend (fallback if metadata missing).
	 * @return array
	 */
	public function confirm_payment( $payment_intent_id, $fallback_order_id = 0 ) {
		$response = $this->api_request( 'payment_intents/' . $payment_intent_id, array(), 'GET' );

		if ( isset( $response['error'] ) ) {
			return $this->error_response(
				$response['error']['message'] ?? __( 'Failed to confirm payment.', 'ggm-commerce' ),
				$response['error']['code'] ?? 'stripe_error'
			);
		}

		// Get order_id from Stripe metadata, or use fallback from frontend.
		$metadata_order_id = $response['metadata']['order_id'] ?? 0;
		$order_id = $metadata_order_id;
		if ( ! $order_id && $fallback_order_id > 0 ) {
			$order_id = $fallback_order_id;
		}

		if ( 'succeeded' === $response['status'] ) {
			$this->handle_successful_payment( $order_id, $response );

			return $this->success_response(
				array(
					'status'   => 'succeeded',
					'order_id' => $order_id,
				)
			);
		}

		return $this->success_response(
			array(
				'status'   => $response['status'],
				'order_id' => $order_id,
			)
		);
	}

	/**
	 * Create a PaymentIntent from cart total (no order required).
	 *
	 * This allows the Stripe Payment Element to be displayed immediately on checkout.
	 *
	 * @param float  $amount   Cart total amount.
	 * @param string $currency Currency code.
	 * @return array Result with client_secret on success.
	 */
	public function create_cart_payment_intent( $amount, $currency = 'GBP' ) {
		$amount_cents = $this->get_amount_in_cents( $amount );
		$currency     = strtolower( $currency );

		$payment_method_types = array( 'card' );
		if ( 'yes' === $this->get_option( 'enable_link', 'yes' ) ) {
			$payment_method_types[] = 'link';
		}

		$intent_data = array(
			'amount'               => $amount_cents,
			'currency'             => $currency,
			'payment_method_types' => $payment_method_types,
			'metadata'             => array(
				'source' => 'cart_checkout',
			),
			'description'          => __( 'Checkout payment', 'ggm-commerce' ),
		);

		$response = $this->api_request( 'payment_intents', $intent_data, 'POST' );

		if ( isset( $response['error'] ) ) {
			return $this->error_response(
				$response['error']['message'] ?? __( 'Failed to create payment intent.', 'ggm-commerce' ),
				$response['error']['code'] ?? 'stripe_error'
			);
		}

		return $this->success_response(
			array(
				'client_secret'     => $response['client_secret'],
				'payment_intent_id' => $response['id'],
				'publishable_key'   => $this->get_publishable_key(),
				'payment_methods'   => $this->get_enabled_payment_methods(),
			)
		);
	}

	/**
	 * Update a PaymentIntent amount.
	 *
	 * Called when cart total changes or when order is created.
	 *
	 * @param string $payment_intent_id Payment Intent ID.
	 * @param float  $amount            New amount.
	 * @param int    $order_id          Optional order ID to attach to metadata.
	 * @param string $order_number      Optional order number for description.
	 * @return array
	 */
	public function update_payment_intent( $payment_intent_id, $amount, $order_id = 0, $order_number = '' ) {
		$amount_cents = $this->get_amount_in_cents( $amount );

		$update_data = array(
			'amount' => $amount_cents,
		);

		// Add order info to metadata if provided.
		if ( $order_id > 0 ) {
			$update_data['metadata'] = array(
				'order_id'     => $order_id,
				'order_number' => $order_number,
				'source'       => 'order_checkout',
			);
			$update_data['description'] = sprintf(
				/* translators: %s: order number */
				__( 'Order %s', 'ggm-commerce' ),
				$order_number
			);
		}

		$response = $this->api_request( 'payment_intents/' . $payment_intent_id, $update_data, 'POST' );

		if ( isset( $response['error'] ) ) {
			return $this->error_response(
				$response['error']['message'] ?? __( 'Failed to update payment intent.', 'ggm-commerce' ),
				$response['error']['code'] ?? 'stripe_error'
			);
		}

		// If order ID provided, store payment intent on the order.
		if ( $order_id > 0 ) {
			global $wpdb;
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->update(
				"{$wpdb->prefix}ggmc_orders",
				array(
					'payment_intent_id' => $payment_intent_id,
					'payment_method'    => 'stripe',
				),
				array( 'id' => $order_id )
			);
		}

		return $this->success_response(
			array(
				'payment_intent_id' => $response['id'],
				'amount'            => $amount,
				'status'            => $response['status'],
			)
		);
	}

	/**
	 * Process payment for an order.
	 *
	 * @param int   $order_id     Order ID.
	 * @param array $payment_data Payment data from frontend.
	 * @return array
	 */
	public function process_payment( $order_id, $payment_data ) {
		// For Stripe, the actual payment is processed client-side.
		// This method creates the PaymentIntent if not already created.
		return $this->create_payment_intent( $order_id );
	}

	/**
	 * Handle successful payment.
	 *
	 * @param int   $order_id Order ID.
	 * @param array $intent   PaymentIntent data.
	 * @return void
	 */
	private function handle_successful_payment( $order_id, $intent ) {
		$transaction_id = $intent['latest_charge'] ?? $intent['id'];

		$this->update_order_payment_status( $order_id, 'paid', $transaction_id );

		$this->log_payment_event(
			$order_id,
			$transaction_id,
			'payment_intent.succeeded',
			$intent['amount'] / 100,
			$intent['currency'],
			'succeeded',
			$intent
		);

		// Save Stripe customer and payment method to linked subscriptions for renewals.
		$this->save_stripe_details_to_subscriptions( $order_id, $intent );

		/**
		 * Fires when a Stripe payment is completed.
		 *
		 * @param int   $order_id Order ID.
		 * @param array $intent   PaymentIntent data.
		 */
		do_action( 'ggm_commerce_stripe_payment_completed', $order_id, $intent );
	}

	/**
	 * Save Stripe customer ID and payment method to linked subscriptions.
	 *
	 * @param int   $order_id Order ID.
	 * @param array $intent   PaymentIntent data.
	 */
	private function save_stripe_details_to_subscriptions( $order_id, $intent ) {
		if ( ! class_exists( 'GGM_Commerce_Subscriptions' ) || ! GGM_Commerce_Subscriptions::is_activated() ) {
			return;
		}

		$customer_id    = $intent['customer'] ?? '';
		$payment_method = $intent['payment_method'] ?? '';

		// If no customer on the intent, try to create one for future charges.
		if ( empty( $customer_id ) && ! empty( $payment_method ) ) {
			$order = $this->get_order( $order_id );
			if ( $order && ! empty( $order->customer_email ) ) {
				$response = $this->api_request( 'customers', array(
					'email'          => $order->customer_email,
					'payment_method' => $payment_method,
					'metadata'       => array( 'order_id' => $order_id ),
				), 'POST' );

				if ( ! isset( $response['error'] ) && ! empty( $response['id'] ) ) {
					$customer_id = $response['id'];
				}
			}
		}

		if ( empty( $customer_id ) && empty( $payment_method ) ) {
			return;
		}

		global $wpdb;
		$sub_manager = GGM_Commerce_Subscriptions::instance();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$linked_subs = $wpdb->get_results( $wpdb->prepare(
			"SELECT id FROM {$wpdb->prefix}ggmc_subscriptions WHERE parent_order_id = %d",
			$order_id
		) );

		foreach ( $linked_subs as $sub ) {
			$update = array( 'payment_gateway' => 'stripe' );
			if ( ! empty( $customer_id ) ) {
				$update['gateway_customer_id'] = $customer_id;
			}
			if ( ! empty( $payment_method ) ) {
				$update['payment_method_token'] = $payment_method;
			}
			$sub_manager->update_subscription( $sub->id, $update );
		}
	}

	/**
	 * Handle webhook callback.
	 *
	 * @return array
	 */
	public function handle_webhook() {
		$payload   = file_get_contents( 'php://input' );
		$sig_header = isset( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) ) : '';

		if ( empty( $payload ) || empty( $sig_header ) ) {
			return $this->error_response( 'Missing payload or signature', 'webhook_error' );
		}

		$webhook_secret = $this->get_option( 'webhook_secret', '' );

		if ( ! empty( $webhook_secret ) ) {
			if ( ! $this->verify_webhook_signature( $payload, $sig_header ) ) {
				return $this->error_response( 'Invalid signature', 'webhook_signature_invalid' );
			}
		}

		$event = json_decode( $payload, true );

		if ( json_last_error() !== JSON_ERROR_NONE ) {
			return $this->error_response( 'Invalid JSON payload', 'webhook_invalid_json' );
		}

		$event_type = $event['type'] ?? '';
		$data       = $event['data']['object'] ?? array();

		switch ( $event_type ) {
			case 'payment_intent.succeeded':
				$this->webhook_payment_succeeded( $data );

				// Handle subscription renewal payment.
				$metadata = $data['metadata'] ?? array();
				if ( ! empty( $metadata['is_renewal'] ) && ! empty( $metadata['subscription_id'] ) ) {
					$this->webhook_renewal_succeeded( $data );
				}
				break;

			case 'payment_intent.payment_failed':
				$this->webhook_payment_failed( $data );

				// Handle subscription renewal failure.
				$metadata = $data['metadata'] ?? array();
				if ( ! empty( $metadata['is_renewal'] ) && ! empty( $metadata['subscription_id'] ) ) {
					$this->webhook_renewal_failed( $data );
				}
				break;

			case 'setup_intent.succeeded':
				$this->webhook_setup_intent_succeeded( $data );
				break;

			case 'charge.refunded':
				$this->webhook_charge_refunded( $data );
				break;

			default:
				// Log unhandled events for debugging.
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging for unhandled webhook events
					error_log( 'GGM Commerce Stripe: Unhandled webhook event: ' . $event_type );
				}
				break;
		}

		return $this->success_response( array( 'received' => true ) );
	}

	/**
	 * Verify Stripe webhook signature.
	 *
	 * @param string $payload   Raw payload.
	 * @param string $signature Stripe-Signature header.
	 * @return bool
	 */
	protected function verify_webhook_signature( $payload, $signature ) {
		$webhook_secret = $this->get_option( 'webhook_secret', '' );

		if ( empty( $webhook_secret ) ) {
			return true; // No verification if secret not set.
		}

		// Parse signature header.
		$parts = array();
		foreach ( explode( ',', $signature ) as $part ) {
			list( $key, $value ) = explode( '=', $part, 2 );
			$parts[ trim( $key ) ] = trim( $value );
		}

		$timestamp = $parts['t'] ?? '';
		$sig       = $parts['v1'] ?? '';

		if ( empty( $timestamp ) || empty( $sig ) ) {
			return false;
		}

		// Check timestamp is within 5 minutes.
		if ( abs( time() - intval( $timestamp ) ) > 300 ) {
			return false;
		}

		// Compute expected signature.
		$signed_payload    = $timestamp . '.' . $payload;
		$expected_sig      = hash_hmac( 'sha256', $signed_payload, $webhook_secret );

		return hash_equals( $expected_sig, $sig );
	}

	/**
	 * Handle payment_intent.succeeded webhook.
	 *
	 * @param array $intent PaymentIntent data.
	 * @return void
	 */
	private function webhook_payment_succeeded( $intent ) {
		$order_id = $intent['metadata']['order_id'] ?? 0;

		if ( ! $order_id ) {
			return;
		}

		$order = $this->get_order( $order_id );

		if ( ! $order || 'paid' === $order->payment_status ) {
			return; // Already processed.
		}

		$this->handle_successful_payment( $order_id, $intent );
	}

	/**
	 * Handle payment_intent.payment_failed webhook.
	 *
	 * @param array $intent PaymentIntent data.
	 * @return void
	 */
	private function webhook_payment_failed( $intent ) {
		$order_id = $intent['metadata']['order_id'] ?? 0;

		if ( ! $order_id ) {
			return;
		}

		$error_message = $intent['last_payment_error']['message'] ?? __( 'Payment failed.', 'ggm-commerce' );

		$this->update_order_payment_status( $order_id, 'failed', '', $error_message );

		$this->log_payment_event(
			$order_id,
			$intent['id'],
			'payment_intent.payment_failed',
			$intent['amount'] / 100,
			$intent['currency'],
			'failed',
			$intent
		);
	}

	/**
	 * Handle charge.refunded webhook.
	 *
	 * @param array $charge Charge data.
	 * @return void
	 */
	private function webhook_charge_refunded( $charge ) {
		// Get order from payment_intent.
		$payment_intent_id = $charge['payment_intent'] ?? '';

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

		global $wpdb;
		$order = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}ggmc_orders WHERE payment_intent_id = %s",
				$payment_intent_id
			)
		);

		if ( ! $order ) {
			return;
		}

		$refund_amount = ( $charge['amount_refunded'] ?? 0 ) / 100;

		$this->log_payment_event(
			$order->id,
			$charge['id'],
			'charge.refunded',
			$refund_amount,
			$charge['currency'],
			'refunded',
			$charge
		);

		// Update order status if fully refunded.
		if ( $charge['refunded'] ?? false ) {
			$wpdb->update(
				"{$wpdb->prefix}ggmc_orders",
				array(
					'status'         => 'refunded',
					'payment_status' => 'refunded',
					'updated_at'     => current_time( 'mysql' ),
				),
				array( 'id' => $order->id )
			);
		}
	}

	/**
	 * Handle setup_intent.succeeded webhook — saves payment method to subscription.
	 *
	 * @param array $data SetupIntent data from Stripe.
	 */
	private function webhook_setup_intent_succeeded( $data ) {
		if ( ! class_exists( 'GGM_Commerce_Subscriptions' ) ) {
			return;
		}

		$customer_id      = $data['customer'] ?? '';
		$payment_method_id = $data['payment_method'] ?? '';

		if ( empty( $customer_id ) || empty( $payment_method_id ) ) {
			return;
		}

		global $wpdb;
		$sub_manager = GGM_Commerce_Subscriptions::instance();

		// Find subscriptions linked to this Stripe customer that need a payment method.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$subscriptions = $wpdb->get_results( $wpdb->prepare(
			"SELECT id FROM {$wpdb->prefix}ggmc_subscriptions
			 WHERE gateway_customer_id = %s AND payment_gateway = 'stripe'
			 AND (payment_method_token IS NULL OR payment_method_token = '')
			 ORDER BY created_at DESC LIMIT 5",
			$customer_id
		) );

		foreach ( $subscriptions as $sub ) {
			$sub_manager->update_subscription( $sub->id, array(
				'payment_method_token' => $payment_method_id,
			) );

			$sub_manager->log_event(
				$sub->id,
				'payment_method_saved',
				'Payment method saved via Stripe SetupIntent.'
			);
		}
	}

	/**
	 * Handle successful subscription renewal payment webhook.
	 *
	 * @param array $data PaymentIntent data from Stripe.
	 */
	private function webhook_renewal_succeeded( $data ) {
		if ( ! class_exists( 'GGM_Commerce_Subscriptions' ) ) {
			return;
		}

		$metadata        = $data['metadata'] ?? array();
		$subscription_id = (int) ( $metadata['subscription_id'] ?? 0 );

		if ( ! $subscription_id ) {
			return;
		}

		$sub_manager  = GGM_Commerce_Subscriptions::instance();
		$subscription = $sub_manager->get_subscription( $subscription_id );

		if ( ! $subscription ) {
			return;
		}

		$next_payment = $sub_manager->calculate_next_payment_date(
			$subscription->billing_period,
			$subscription->billing_interval
		);

		$sub_manager->update_subscription( $subscription_id, array(
			'last_payment_date'  => current_time( 'mysql', true ),
			'next_payment_date'  => $next_payment,
			'status'             => 'active',
		) );

		$amount = ( $data['amount'] ?? 0 ) / 100;
		$sub_manager->log_event(
			$subscription_id,
			'renewal_payment_succeeded',
			sprintf( 'Renewal payment of %s succeeded.', ggm_commerce_format_price( $amount ) ),
			$metadata['order_id'] ?? null,
			$amount,
			'gateway'
		);

		do_action( 'ggmc_subscription_renewal_succeeded', $subscription_id, $data );
	}

	/**
	 * Handle failed subscription renewal payment webhook.
	 *
	 * @param array $data PaymentIntent data from Stripe.
	 */
	private function webhook_renewal_failed( $data ) {
		if ( ! class_exists( 'GGM_Commerce_Subscriptions' ) ) {
			return;
		}

		$metadata        = $data['metadata'] ?? array();
		$subscription_id = (int) ( $metadata['subscription_id'] ?? 0 );

		if ( ! $subscription_id ) {
			return;
		}

		$sub_manager = GGM_Commerce_Subscriptions::instance();
		$error_msg   = $data['last_payment_error']['message'] ?? 'Payment failed.';

		$sub_manager->log_event(
			$subscription_id,
			'renewal_payment_failed',
			'Renewal payment failed: ' . $error_msg,
			$metadata['order_id'] ?? null,
			null,
			'gateway'
		);

		// Schedule a retry.
		$settings    = GGM_Commerce_Subscriptions::get_settings();
		$max_retries = $settings['max_retries'];

		// Count existing retries.
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$retry_count = (int) $wpdb->get_var( $wpdb->prepare(
			"SELECT COUNT(*) FROM {$wpdb->prefix}ggmc_subscription_payment_retries WHERE subscription_id = %d",
			$subscription_id
		) );

		if ( $retry_count < $max_retries ) {
			$sub_manager->schedule_retry( $subscription_id, $retry_count + 1 );
			$sub_manager->update_subscription( $subscription_id, array( 'status' => 'failed_payment' ) );
		} else {
			// All retries exhausted — put on hold.
			$sub_manager->hold_subscription( $subscription_id, 'All payment retries exhausted.' );
		}

		do_action( 'ggmc_subscription_renewal_failed', $subscription_id, $data );
	}

	/**
	 * Process a refund.
	 *
	 * @param int   $order_id Order ID.
	 * @param float $amount   Amount to refund (null for full refund).
	 * @param string $reason  Refund reason.
	 * @return array
	 */
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
		$order = $this->get_order( $order_id );

		if ( ! $order || empty( $order->payment_intent_id ) ) {
			return $this->error_response( __( 'Order not found or has no payment.', 'ggm-commerce' ) );
		}

		$refund_data = array(
			'payment_intent' => $order->payment_intent_id,
		);

		if ( null !== $amount ) {
			$refund_data['amount'] = $this->get_amount_in_cents( $amount );
		}

		if ( ! empty( $reason ) ) {
			$refund_data['reason'] = 'requested_by_customer';
			$refund_data['metadata'] = array( 'reason' => $reason );
		}

		$response = $this->api_request( 'refunds', $refund_data, 'POST' );

		if ( isset( $response['error'] ) ) {
			return $this->error_response(
				$response['error']['message'] ?? __( 'Refund failed.', 'ggm-commerce' ),
				$response['error']['code'] ?? 'refund_error'
			);
		}

		$refund_amount = ( $response['amount'] ?? 0 ) / 100;

		$this->log_payment_event(
			$order_id,
			$response['id'],
			'refund.created',
			$refund_amount,
			$response['currency'],
			'succeeded',
			$response
		);

		return $this->success_response(
			array(
				'refund_id' => $response['id'],
				'amount'    => $refund_amount,
			)
		);
	}

	/**
	 * Make a request to the Stripe API.
	 *
	 * @param string $endpoint API endpoint.
	 * @param array  $data     Request data.
	 * @param string $method   HTTP method.
	 * @return array
	 */
	private function api_request( $endpoint, $data = array(), $method = 'POST' ) {
		$secret_key = $this->get_secret_key();

		if ( empty( $secret_key ) ) {
			return array(
				'error' => array(
					'message' => __( 'Stripe secret key not configured.', 'ggm-commerce' ),
					'code'    => 'missing_secret_key',
				),
			);
		}

		$url = self::API_BASE . '/' . $endpoint;

		$args = array(
			'method'  => $method,
			'headers' => array(
				'Authorization'  => 'Bearer ' . $secret_key,
				'Content-Type'   => 'application/x-www-form-urlencoded',
				'Stripe-Version' => self::API_VERSION,
			),
			'timeout' => 60,
		);

		if ( 'POST' === $method && ! empty( $data ) ) {
			$args['body'] = $this->build_form_data( $data );
		}

		$response = wp_remote_request( $url, $args );

		if ( is_wp_error( $response ) ) {
			return array(
				'error' => array(
					'message' => $response->get_error_message(),
					'code'    => 'wp_error',
				),
			);
		}

		$body = wp_remote_retrieve_body( $response );
		return json_decode( $body, true );
	}

	/**
	 * Build form-encoded data for Stripe API.
	 *
	 * @param array  $data   Data array.
	 * @param string $prefix Key prefix.
	 * @return string
	 */
	private function build_form_data( $data, $prefix = '' ) {
		$result = array();

		foreach ( $data as $key => $value ) {
			$new_key = $prefix ? "{$prefix}[{$key}]" : $key;

			if ( is_array( $value ) ) {
				$result[] = $this->build_form_data( $value, $new_key );
			} else {
				$result[] = rawurlencode( $new_key ) . '=' . rawurlencode( $value );
			}
		}

		return implode( '&', $result );
	}

	/**
	 * Render payment fields for checkout.
	 *
	 * @return void
	 */
	public function render_payment_fields() {
		?>
		<div id="ggmc-stripe-payment-element" class="ggmc-stripe-element-container">
			<div class="ggmc-stripe-placeholder">
				<p><?php esc_html_e( 'Enter your card details to complete payment.', 'ggm-commerce' ); ?></p>
				<div class="ggmc-stripe-placeholder-fields">
					<div class="ggmc-placeholder-field">
						<span class="ggmc-placeholder-label"><?php esc_html_e( 'Card number', 'ggm-commerce' ); ?></span>
						<span class="ggmc-placeholder-input"></span>
					</div>
					<div class="ggmc-placeholder-row">
						<div class="ggmc-placeholder-field">
							<span class="ggmc-placeholder-label"><?php esc_html_e( 'Expiry', 'ggm-commerce' ); ?></span>
							<span class="ggmc-placeholder-input"></span>
						</div>
						<div class="ggmc-placeholder-field">
							<span class="ggmc-placeholder-label"><?php esc_html_e( 'CVC', 'ggm-commerce' ); ?></span>
							<span class="ggmc-placeholder-input"></span>
						</div>
					</div>
				</div>
			</div>
		</div>
		<div id="ggmc-stripe-payment-message" class="ggmc-payment-message" style="display: none;"></div>
		<style>
		.ggmc-stripe-element-container {
			padding: 15px;
			background: #f8fafc;
			border-radius: 6px;
			min-height: 120px;
		}
		.ggmc-stripe-placeholder p {
			margin: 0 0 12px;
			color: #64748b;
			font-size: 13px;
		}
		.ggmc-stripe-placeholder-fields {
			display: flex;
			flex-direction: column;
			gap: 10px;
		}
		.ggmc-placeholder-row {
			display: flex;
			gap: 10px;
		}
		.ggmc-placeholder-row .ggmc-placeholder-field {
			flex: 1;
		}
		.ggmc-placeholder-field {
			display: flex;
			flex-direction: column;
			gap: 4px;
		}
		.ggmc-placeholder-label {
			font-size: 12px;
			color: #94a3b8;
			font-weight: 500;
		}
		.ggmc-placeholder-input {
			display: block;
			height: 36px;
			background: #e2e8f0;
			border-radius: 4px;
			animation: ggmc-pulse 1.5s ease-in-out infinite;
		}
		@keyframes ggmc-pulse {
			0%, 100% { opacity: 0.6; }
			50% { opacity: 1; }
		}
		.ggmc-stripe-element-container.ggmc-stripe-ready .ggmc-stripe-placeholder {
			display: none;
		}
		</style>
		<?php
	}

	/**
	 * Get JavaScript dependencies.
	 *
	 * @return array
	 */
	public function get_scripts() {
		return array(
			array(
				'handle' => 'stripe-js',
				'src'    => 'https://js.stripe.com/v3/',
				'deps'   => array(),
				'ver'    => null,
			),
			array(
				'handle' => 'ggmc-stripe-payment',
				'src'    => plugins_url( 'assets/js/stripe-payment.js', dirname( __DIR__, 3 ) . '/goose-commerce.php' ),
				'deps'   => array( 'jquery', 'stripe-js' ),
				'ver'    => '1.1.1-' . time(),
			),
		);
	}

	/**
	 * Get or create a Stripe Customer.
	 *
	 * @param string $email         Customer email.
	 * @param array  $customer_data Optional additional data (name, etc.).
	 * @return array|WP_Error Stripe Customer object or error.
	 */
	public function get_or_create_stripe_customer( $email, $customer_data = array() ) {
		// Search for existing customer by email.
		$search = $this->api_request( 'customers?email=' . rawurlencode( $email ) . '&limit=1', array(), 'GET' );

		if ( ! isset( $search['error'] ) && ! empty( $search['data'] ) ) {
			return $search['data'][0];
		}

		// Create new customer.
		$data = array(
			'email' => $email,
		);
		if ( ! empty( $customer_data['name'] ) ) {
			$data['name'] = $customer_data['name'];
		}

		$result = $this->api_request( 'customers', $data );

		if ( isset( $result['error'] ) ) {
			return new \WP_Error( 'stripe_customer_error', $result['error']['message'] ?? 'Failed to create Stripe customer.' );
		}

		return $result;
	}

	/**
	 * Create a SetupIntent to save a payment method for future off-session use.
	 *
	 * @param string $customer_id Stripe Customer ID.
	 * @return array|WP_Error SetupIntent data or error.
	 */
	public function create_setup_intent( $customer_id ) {
		$data = array(
			'customer'             => $customer_id,
			'usage'                => 'off_session',
			'payment_method_types' => array( 'card' ),
		);

		$result = $this->api_request( 'setup_intents', $data );

		if ( isset( $result['error'] ) ) {
			return new \WP_Error( 'stripe_setup_intent_error', $result['error']['message'] ?? 'Failed to create SetupIntent.' );
		}

		return $result;
	}

	/**
	 * Attach a payment method to a Stripe Customer.
	 *
	 * @param string $payment_method_id Stripe PaymentMethod ID.
	 * @param string $customer_id       Stripe Customer ID.
	 * @return array|WP_Error Result or error.
	 */
	public function attach_payment_method( $payment_method_id, $customer_id ) {
		$result = $this->api_request(
			'payment_methods/' . $payment_method_id . '/attach',
			array( 'customer' => $customer_id )
		);

		if ( isset( $result['error'] ) ) {
			return new \WP_Error( 'stripe_attach_error', $result['error']['message'] ?? 'Failed to attach payment method.' );
		}

		// Set as default payment method for the customer.
		$this->api_request(
			'customers/' . $customer_id,
			array(
				'invoice_settings' => array(
					'default_payment_method' => $payment_method_id,
				),
			)
		);

		return $result;
	}

	/**
	 * Create an off-session PaymentIntent for a subscription renewal.
	 *
	 * @param object $subscription Subscription DB row.
	 * @param int    $order_id     The renewal order ID.
	 * @return array API result with 'success' key.
	 */
	public function create_renewal_payment( $subscription, $order_id ) {
		if ( empty( $subscription->gateway_customer_id ) || empty( $subscription->payment_method_token ) ) {
			return $this->error_response(
				__( 'Missing payment method for subscription renewal.', 'ggm-commerce' ),
				'missing_payment_method'
			);
		}

		$amount   = $this->get_amount_in_cents( $subscription->recurring_total );
		$currency = strtolower( $subscription->currency ?: $this->get_currency() );

		$intent_data = array(
			'amount'               => $amount,
			'currency'             => $currency,
			'customer'             => $subscription->gateway_customer_id,
			'payment_method'       => $subscription->payment_method_token,
			'off_session'          => 'true',
			'confirm'              => 'true',
			'payment_method_types' => array( 'card' ),
			'metadata'             => array(
				'subscription_id'     => $subscription->id,
				'subscription_number' => $subscription->subscription_number,
				'order_id'            => $order_id,
				'is_renewal'          => '1',
			),
			'description' => sprintf(
				/* translators: %s: subscription number */
				__( 'Renewal for subscription %s', 'ggm-commerce' ),
				$subscription->subscription_number
			),
		);

		if ( ! empty( $subscription->customer_email ) ) {
			$intent_data['receipt_email'] = $subscription->customer_email;
		}

		$response = $this->api_request( 'payment_intents', $intent_data );

		if ( isset( $response['error'] ) ) {
			$this->log_payment_event(
				$order_id,
				'',
				'renewal_payment.failed',
				$subscription->recurring_total,
				$currency,
				'failed',
				$response
			);

			return $this->error_response(
				$response['error']['message'] ?? __( 'Renewal payment failed.', 'ggm-commerce' ),
				$response['error']['code'] ?? 'renewal_failed'
			);
		}

		// Store payment intent ID on the renewal order.
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->update(
			"{$wpdb->prefix}ggmc_orders",
			array(
				'payment_intent_id'    => $response['id'],
				'payment_method'       => 'stripe',
				'payment_transaction_id' => $response['id'],
			),
			array( 'id' => $order_id )
		);

		$status = $response['status'] ?? '';
		$success = 'succeeded' === $status;

		if ( $success ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->update(
				"{$wpdb->prefix}ggmc_orders",
				array(
					'payment_status' => 'paid',
					'status'         => 'completed',
					'updated_at'     => current_time( 'mysql' ),
				),
				array( 'id' => $order_id )
			);
		}

		$this->log_payment_event(
			$order_id,
			$response['id'],
			$success ? 'renewal_payment.succeeded' : 'renewal_payment.requires_action',
			$subscription->recurring_total,
			$currency,
			$status,
			$response
		);

		return $this->success_response(
			array(
				'payment_intent' => $response['id'],
				'status'         => $status,
				'paid'           => $success,
			)
		);
	}

	/**
	 * Retrieve a SetupIntent from Stripe.
	 *
	 * @param string $setup_intent_id SetupIntent ID.
	 * @return array Stripe SetupIntent data.
	 */
	public function retrieve_setup_intent( $setup_intent_id ) {
		return $this->api_request( 'setup_intents/' . $setup_intent_id, array(), 'GET' );
	}

	/**
	 * Get JavaScript localization data.
	 *
	 * @return array
	 */
	public function get_js_data() {
		// Get cart total if available.
		$cart_total = 0;
		if ( class_exists( 'GGM_Commerce_Cart' ) ) {
			$cart = GGM_Commerce_Cart::get_instance();
			$cart_total = $cart->get_cart_total();
		}

		// Generate a nonce for payment API calls.
		$nonce = wp_create_nonce( 'ggm_commerce_payment' );

		// Also provide the REST API nonce as a fallback.
		$rest_nonce = wp_create_nonce( 'wp_rest' );

		return array(
			'publishableKey'      => $this->get_publishable_key(),
			'paymentMethods'      => $this->get_enabled_payment_methods(),
			'testMode'            => $this->is_test_mode(),
			'locale'              => get_locale(),
			'currency'            => $this->get_currency(),
			'cartTotal'           => $cart_total,
			'nonce'               => $nonce,
			'restNonce'           => $rest_nonce,
			'createIntentUrl'     => rest_url( 'ggm-commerce/v1/stripe/create-intent' ),
			'createCartIntentUrl' => rest_url( 'ggm-commerce/v1/stripe/create-cart-intent' ),
			'updateIntentUrl'     => rest_url( 'ggm-commerce/v1/stripe/update-intent' ),
			'confirmUrl'          => rest_url( 'ggm-commerce/v1/stripe/confirm-payment' ),
			'returnUrl'           => add_query_arg( 'stripe_confirm', '1', home_url( '/checkout/' ) ),
			'i18n'                => array(
				'processing' => __( 'Processing payment...', 'ggm-commerce' ),
				'error'      => __( 'An error occurred. Please try again.', 'ggm-commerce' ),
			),
		);
	}
}
