<?php
/**
 * PayPal Webhook Listener Class.
 *
 * Handles incoming webhook events from PayPal and processes payment updates.
 * This class:
 * - Registers REST API endpoints for test and live modes
 * - Verifies webhook signatures to ensure requests are from PayPal
 * - Routes webhook events to appropriate handlers
 * - Creates subscription payment records for recurring payments (enables refunds)
 * - Updates payment statuses based on webhook events
 *
 * @package sureforms-pro
 * @since 2.4.0
 */

namespace SRFM_Pro\Inc\Business\Payments\PayPal;

use SRFM\Inc\Database\Tables\Payments;
use SRFM\Inc\Helper;
use SRFM\Inc\Traits\Get_Instance;
use SRFM_Pro\Inc\Business\Payments\PayPal\Helper as PayPal_Helper;
use WP_REST_Request;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * PayPal Webhook Listener.
 *
 * Listens for webhook events from PayPal and processes them accordingly.
 * CRITICAL: Creates child payment records for subscription payments with capture_id,
 * which enables refunds on individual subscription payments.
 *
 * @since 2.4.0
 */
class Webhook_Listener {
	use Get_Instance;

	/**
	 * Payment mode for current webhook request.
	 *
	 * @var string
	 * @since 2.4.0
	 */
	private $mode = 'test';

	/**
	 * Constructor.
	 *
	 * Registers webhook REST API endpoints.
	 *
	 * @since 2.4.0
	 */
	public function __construct() {
		add_action( 'rest_api_init', [ $this, 'register_endpoints' ] );
	}

	/**
	 * Registers REST API endpoints for PayPal webhooks.
	 *
	 * Creates two endpoints:
	 * - /sureforms-pro/paypal-test-webhook (for sandbox mode)
	 * - /sureforms-pro/paypal-live-webhook (for live mode)
	 *
	 * @since 2.4.0
	 * @return void
	 */
	public function register_endpoints() {
		// Test mode webhook endpoint.
		register_rest_route(
			'sureforms-pro',
			'/paypal-test-webhook',
			[
				'methods'             => 'POST',
				'callback'            => [ $this, 'webhook_listener_test' ],
				'permission_callback' => '__return_true',
			]
		);

		// Live mode webhook endpoint.
		register_rest_route(
			'sureforms-pro',
			'/paypal-live-webhook',
			[
				'methods'             => 'POST',
				'callback'            => [ $this, 'webhook_listener_live' ],
				'permission_callback' => '__return_true',
			]
		);
	}

	/**
	 * Webhook listener for test mode.
	 *
	 * @param WP_REST_Request $request Webhook request.
	 * @since 2.4.0
	 * @return void
	 */
	public function webhook_listener_test( WP_REST_Request $request ) {
		$this->webhook_listener( $request, 'test' );
	}

	/**
	 * Webhook listener for live mode.
	 *
	 * @param WP_REST_Request $request Webhook request.
	 * @since 2.4.0
	 * @return void
	 */
	public function webhook_listener_live( WP_REST_Request $request ) {
		$this->webhook_listener( $request, 'live' );
	}

	/**
	 * Main webhook listener.
	 *
	 * Validates webhook signature and routes events to appropriate handlers.
	 *
	 * @param WP_REST_Request $request Webhook request.
	 * @param string          $mode    Payment mode ('test' or 'live').
	 * @since 2.4.0
	 * @return void
	 */
	public function webhook_listener( WP_REST_Request $request, $mode ) {
		$this->mode = $mode;

		// Debug: Log webhook received.
		Helper::srfm_log( '=== PayPal Webhook Received ===' );
		Helper::srfm_log( 'Mode: ' . $mode );
		Helper::srfm_log( 'Request Body: ' . $request->get_body() );

		// Validate webhook signature.
		$validation_result = $this->validate_webhook_signature( $request, $mode );

		if ( ! $validation_result['success'] ) {
			Helper::srfm_log( 'PayPal webhook signature validation failed: ' . ( $validation_result['message'] ?? 'Unknown error' ) );
			http_response_code( 400 );
			echo wp_json_encode( [ 'error' => 'Signature validation failed' ] );
			return;
		}

		Helper::srfm_log( 'Webhook signature validated successfully' );

		// Get webhook event data.
		$event_data = $request->get_params();

		if ( empty( $event_data['event_type'] ) ) {
			Helper::srfm_log( 'PayPal webhook: Missing event_type in webhook data.' );
			http_response_code( 400 );
			echo wp_json_encode( [ 'error' => 'Missing event_type' ] );
			return;
		}

		$event_type = sanitize_text_field( $event_data['event_type'] );
		Helper::srfm_log( 'Processing PayPal webhook event: ' . $event_type );

		// Route event to appropriate handler.
		$this->handle_webhook_event( $event_type, $event_data );

		// Send success response to PayPal.
		http_response_code( 200 );
		echo wp_json_encode( [ 'success' => true ] );
	}

	/**
	 * Validate webhook signature.
	 *
	 * Verifies that the webhook request came from PayPal by validating
	 * the signature using PayPal's verification API.
	 *
	 * @param WP_REST_Request $request Webhook request.
	 * @param string          $mode    Payment mode ('test' or 'live').
	 * @since 2.4.0
	 * @return array Validation result with success status.
	 */
	private function validate_webhook_signature( WP_REST_Request $request, $mode ) {
		Helper::srfm_log( 'Starting webhook signature validation for mode: ' . $mode );

		// Verify required HTTP headers are present.
		if ( ! $this->verify_http_headers() ) {
			Helper::srfm_log( 'Missing required PayPal webhook headers' );
			return [
				'success' => false,
				'message' => 'Missing required PayPal webhook headers.',
			];
		}

		Helper::srfm_log( 'All required headers present' );

		// Get webhook ID from settings.
		$settings   = PayPal_Helper::get_all_paypal_settings();
		$webhook_id = 'live' === $mode
			? ( $settings['webhook_live_id'] ?? '' )
			: ( $settings['webhook_test_id'] ?? '' );

		Helper::srfm_log( 'Webhook ID from settings: ' . ( $webhook_id ? $webhook_id : 'EMPTY' ) );

		if ( empty( $webhook_id ) ) {
			return [
				'success' => false,
				'message' => 'PayPal webhook not configured for mode: ' . $mode,
			];
		}

		// Prepare verification data.
		$webhook_event = json_decode( $request->get_body(), true );

		if ( ! is_array( $webhook_event ) ) {
			Helper::srfm_log( 'Failed to parse webhook event JSON' );
			return [
				'success' => false,
				'message' => 'Invalid webhook event JSON',
			];
		}

		// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PayPal webhook requires raw header values and no sanitization here.
		$verification_data = [
			'auth_algo'         => isset( $_SERVER['HTTP_PAYPAL_AUTH_ALGO'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_PAYPAL_AUTH_ALGO'] ) ) : '',
			'cert_url'          => isset( $_SERVER['HTTP_PAYPAL_CERT_URL'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_PAYPAL_CERT_URL'] ) ) : '',
			'transmission_id'   => isset( $_SERVER['HTTP_PAYPAL_TRANSMISSION_ID'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_PAYPAL_TRANSMISSION_ID'] ) ) : '',
			'transmission_sig'  => isset( $_SERVER['HTTP_PAYPAL_TRANSMISSION_SIG'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_PAYPAL_TRANSMISSION_SIG'] ) ) : '',
			'transmission_time' => isset( $_SERVER['HTTP_PAYPAL_TRANSMISSION_TIME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_PAYPAL_TRANSMISSION_TIME'] ) ) : '',
			'webhook_id'        => $webhook_id,
			'webhook_event'     => $webhook_event,
		];
		// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PayPal webhook requires raw header values and no sanitization here.

		Helper::srfm_log( 'Verification data prepared. Transmission ID: ' . $verification_data['transmission_id'] );

		// Verify signature with PayPal.
		$result = Webhook::verify_signature( $verification_data, $mode );

		Helper::srfm_log( 'Signature verification result: ' . wp_json_encode( $result ) );

		return $result;
	}

	/**
	 * Verify required PayPal webhook HTTP headers are present.
	 *
	 * @since 2.4.0
	 * @return bool True if all headers present, false otherwise.
	 */
	private function verify_http_headers() {
		$required_headers = [
			'HTTP_PAYPAL_TRANSMISSION_SIG',
			'HTTP_PAYPAL_AUTH_ALGO',
			'HTTP_PAYPAL_CERT_URL',
			'HTTP_PAYPAL_TRANSMISSION_ID',
			'HTTP_PAYPAL_TRANSMISSION_TIME',
		];

		$missing_headers = [];

		foreach ( $required_headers as $header ) {
			if ( empty( $_SERVER[ $header ] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
				$missing_headers[] = $header;
			}
		}

		if ( ! empty( $missing_headers ) ) {
			Helper::srfm_log( 'Missing PayPal webhook headers: ' . implode( ', ', $missing_headers ) );
			return false;
		}

		return true;
	}

	/**
	 * Handle webhook event based on event type.
	 *
	 * Routes events to specific handlers:
	 * - PAYMENT.CAPTURE.COMPLETED: Payment completed (one-time & subscription)
	 * - PAYMENT.CAPTURE.REFUNDED: Payment refunded
	 * - PAYMENT.CAPTURE.DENIED: Payment denied
	 * - BILLING.SUBSCRIPTION.*: Subscription events
	 *
	 * @param string $event_type Event type from PayPal.
	 * @param array  $event_data Full event data from PayPal.
	 * @since 2.4.0
	 * @return void
	 */
	private function handle_webhook_event( $event_type, $event_data ) {
		// Extract resource data.
		$resource = $event_data['resource'] ?? [];

		if ( empty( $resource ) ) {
			Helper::srfm_log( 'PayPal webhook: Missing resource data in event.' );
			return;
		}

		// Route based on event type.
		switch ( $event_type ) {
			case 'PAYMENT.CAPTURE.REFUNDED':
				$this->handle_payment_capture_refunded( $resource );
				break;
			case 'PAYMENT.SALE.COMPLETED':
				// Subscription recurring payment completed.
				$this->handle_payment_sale_completed( $resource );
				break;

			case 'PAYMENT.SALE.REFUNDED':
				// Subscription payment refunded.
				$this->handle_payment_sale_refunded( $resource );
				break;

			case 'BILLING.SUBSCRIPTION.ACTIVATED':
				$this->handle_subscription_activated( $resource );
				break;

			case 'BILLING.SUBSCRIPTION.CANCELLED':
				$this->handle_subscription_cancelled( $resource );
				break;

			case 'BILLING.SUBSCRIPTION.EXPIRED':
				$this->handle_subscription_expired( $resource );
				break;

			case 'BILLING.SUBSCRIPTION.SUSPENDED':
				$this->handle_subscription_suspended( $resource );
				break;

			case 'BILLING.SUBSCRIPTION.UPDATED':
				$this->handle_subscription_updated( $resource );
				break;

			default:
				Helper::srfm_log( 'PayPal webhook: Unhandled event type: ' . $event_type );
				break;
		}
	}

	/**
	 * Process initial subscription payment.
	 *
	 * Updates the parent subscription record with the initial payment transaction ID.
	 * This is called when PAYMENT.SALE.COMPLETED is received and the parent subscription
	 * does not have a transaction_id yet.
	 *
	 * @param array  $parent_subscription  Parent subscription record from database.
	 * @param array  $resource             Resource data from webhook.
	 * @param string $billing_agreement_id PayPal billing agreement ID.
	 * @since 2.4.0
	 * @return void
	 */
	private function process_initial_subscription_payment( $parent_subscription, $resource, $billing_agreement_id ) {
		Helper::srfm_log( 'Processing initial subscription payment for billing_agreement_id: ' . $billing_agreement_id );

		// Extract sale details.
		$sale_id  = $resource['id'] ?? '';
		$amount   = $resource['amount']['total'] ?? '0.00';
		$currency = $resource['amount']['currency'] ?? 'USD';

		// Get parent subscription ID.
		$parent_id = ! empty( $parent_subscription['id'] ) && is_numeric( $parent_subscription['id'] ) ? intval( $parent_subscription['id'] ) : 0;

		if ( ! $parent_id ) {
			Helper::srfm_log( 'Invalid parent subscription ID for initial payment.' );
			return;
		}

		// Prepare update data.
		$update_data = [
			'transaction_id' => $sale_id,
			'status'         => 'succeeded',
		];

		// Generate srfm_txn_id if not already set.
		$current_srfm_txn_id = ! empty( $parent_subscription['srfm_txn_id'] ) && is_string( $parent_subscription['srfm_txn_id'] ) ? sanitize_text_field( $parent_subscription['srfm_txn_id'] ) : '';
		if ( empty( $current_srfm_txn_id ) ) {
			$unique_payment_id          = PayPal_Helper::generate_unique_payment_id( $parent_id );
			$update_data['srfm_txn_id'] = $unique_payment_id;
			Helper::srfm_log( 'Generated srfm_txn_id for initial subscription payment: ' . $unique_payment_id );
		}

		// Add log entry for initial payment success.
		$current_logs = Helper::get_array_value( $parent_subscription['log'] ?? [] );
		$new_log      = [
			'title'      => __( 'Initial Subscription Payment Succeeded', 'sureforms-pro' ),
			'created_at' => current_time( 'mysql' ),
			'messages'   => [
				sprintf(
					/* translators: %s: Sale ID */
					__( 'Sale ID: %s', 'sureforms-pro' ),
					$sale_id
				),
				sprintf(
					/* translators: 1: Amount, 2: Currency */
					__( 'Amount: %1$s %2$s', 'sureforms-pro' ),
					$amount,
					strtoupper( $currency )
				),
				__( 'Payment Status: Succeeded', 'sureforms-pro' ),
				__( 'Subscription Status: Active', 'sureforms-pro' ),
			],
		];

		$current_logs[]     = $new_log;
		$update_data['log'] = $current_logs;

		// Update parent subscription record.
		$result = Payments::update( $parent_id, $update_data );

		if ( false === $result ) {
			Helper::srfm_log( 'Failed to update parent subscription for initial payment. Subscription ID: ' . $parent_id );
		} else {
			Helper::srfm_log( 'Initial subscription payment processed successfully. Subscription ID: ' . $parent_id . ', Sale ID: ' . $sale_id );
		}
	}

	/**
	 * Process subscription renewal payment.
	 *
	 * Creates a child payment record for subscription renewal billing cycles.
	 * The child record has:
	 * - type: 'renewal'
	 * - transaction_id: sale_id (from webhook)
	 * - parent_subscription_id: database ID of parent subscription
	 *
	 * This enables refunds because we now have the sale_id required by PayPal's refund API.
	 *
	 * @param array  $resource             Resource data from webhook.
	 * @param string $billing_agreement_id PayPal billing agreement ID.
	 * @since 2.4.0
	 * @return void
	 */
	private function process_subscription_renewal_payment( $resource, $billing_agreement_id ) {
		Helper::srfm_log( 'Processing subscription renewal payment for billing_agreement_id: ' . $billing_agreement_id );

		// Find parent subscription record by subscription_id field.
		$parent_subscription = Payments::get_main_subscription_record( $billing_agreement_id );

		if ( ! $parent_subscription ) {
			Helper::srfm_log( 'PayPal webhook: Parent subscription not found for billing_agreement_id: ' . $billing_agreement_id );
			return;
		}

		Helper::srfm_log( 'Found parent subscription record with ID: ' . ( $parent_subscription['id'] ?? 'unknown' ) );

		$parent_subscription_id = ! empty( $parent_subscription['id'] ) && is_numeric( $parent_subscription['id'] ) ? intval( $parent_subscription['id'] ) : 0;

		// Extract sale/capture ID.
		$sale_id = $resource['id'] ?? '';

		// Check if payment already exists.
		$existing_payment = Payments::get_by_transaction_id( $sale_id );

		if ( $existing_payment ) {
			Helper::srfm_log( 'Subscription renewal payment already processed for sale_id: ' . $sale_id );
			return;
		}

		// Extract payment details from resource.
		// For PAYMENT.SALE.COMPLETED, amount is in $resource['amount']['total'].
		// For PAYMENT.CAPTURE.COMPLETED, amount is in $resource['amount']['value'].
		$amount   = $resource['amount']['total'] ?? $resource['amount']['value'] ?? '0.00';
		$currency = $resource['amount']['currency'] ?? $resource['amount']['currency_code'] ?? 'USD';
		$status   = $resource['state'] ?? $resource['status'] ?? 'completed';

		// Normalize status.
		$normalized_status = 'completed' === strtolower( $status ) || 'COMPLETED' === $status ? 'succeeded' : strtolower( $status );

		// Prepare renewal payment data.
		$payment_data = [
			'form_id'                => ! empty( $parent_subscription['form_id'] ) && is_numeric( $parent_subscription['form_id'] ) ? intval( $parent_subscription['form_id'] ) : 0,
			'block_id'               => ! empty( $parent_subscription['block_id'] ) && is_string( $parent_subscription['block_id'] ) ? sanitize_text_field( $parent_subscription['block_id'] ) : '',
			'status'                 => $normalized_status,
			'total_amount'           => floatval( $amount ),
			'currency'               => strtolower( $currency ),
			'entry_id'               => ! empty( $parent_subscription['entry_id'] ) && is_numeric( $parent_subscription['entry_id'] ) ? intval( $parent_subscription['entry_id'] ) : 0,
			'type'                   => 'renewal', // CHANGED from 'subscription_payment'.
			'transaction_id'         => $sale_id, // CRITICAL: This enables refunds!
			'gateway'                => 'paypal',
			'mode'                   => $this->mode,
			'subscription_id'        => $billing_agreement_id,
			'parent_subscription_id' => $parent_subscription_id,
			'customer_email'         => ! empty( $parent_subscription['customer_email'] ) && is_string( $parent_subscription['customer_email'] ) ? sanitize_email( $parent_subscription['customer_email'] ) : '',
			'customer_name'          => ! empty( $parent_subscription['customer_name'] ) && is_string( $parent_subscription['customer_name'] ) ? sanitize_text_field( $parent_subscription['customer_name'] ) : '',
			'customer_id'            => ! empty( $parent_subscription['customer_id'] ) && is_string( $parent_subscription['customer_id'] ) ? sanitize_text_field( $parent_subscription['customer_id'] ) : '',
			'payment_data'           => [
				'sale_id'              => $sale_id,
				'billing_agreement_id' => $billing_agreement_id,
				'resource'             => $resource,
			],
			'log'                    => [
				[
					'title'      => __( 'Subscription Renewal Payment', 'sureforms-pro' ), // UPDATED title.
					'created_at' => current_time( 'mysql' ),
					'messages'   => [
						sprintf(
							/* translators: %s: Transaction ID */
							__( 'Transaction ID: %s', 'sureforms-pro' ),
							$sale_id
						),
						sprintf(
							/* translators: %s: Payment Gateway */
							__( 'Payment Gateway: %s', 'sureforms-pro' ),
							'PayPal'
						),
						sprintf(
							/* translators: 1: Amount, 2: Currency */
							__( 'Amount: %1$s %2$s', 'sureforms-pro' ),
							$amount,
							strtoupper( $currency )
						),
						sprintf(
							/* translators: %s: Status */
							__( 'Status: %s', 'sureforms-pro' ),
							ucfirst( $normalized_status )
						),
						sprintf(
							/* translators: %s: Subscription ID */
							__( 'Subscription ID: %s', 'sureforms-pro' ),
							$billing_agreement_id
						),
						__( 'Created via PayPal webhook (subscription renewal)', 'sureforms-pro' ),
					],
				],
			],
		];

		// Create renewal payment record.
		$payment_id = Payments::add( $payment_data );

		if ( $payment_id ) {
			// Generate unique payment ID.
			$unique_payment_id = PayPal_Helper::generate_unique_payment_id( $payment_id );
			Payments::update( $payment_id, [ 'srfm_txn_id' => $unique_payment_id ] );

			Helper::srfm_log(
				sprintf(
					'Renewal payment record created successfully. Payment ID: %d, Sale ID: %s, Amount: %s %s',
					$payment_id,
					$sale_id,
					$amount,
					$currency
				)
			);
		} else {
			Helper::srfm_log( 'Failed to create renewal payment record for sale_id: ' . $sale_id );
		}
	}

	/**
	 * Handle PAYMENT.CAPTURE.REFUNDED event.
	 *
	 * Processes refunds initiated from PayPal dashboard or via API.
	 * Updates payment status, refunded amount, and adds refund details to payment data.
	 *
	 * @param array $resource Resource data from webhook.
	 * @since 2.4.0
	 * @return void
	 */
	private function handle_payment_capture_refunded( $resource ) {
		$refund_id = $resource['id'] ?? '';
		Helper::srfm_log( 'Processing PAYMENT.CAPTURE.REFUNDED for refund_id: ' . $refund_id );

		// Get capture_id from the 'up' link (parent capture).
		$capture_id = $this->extract_capture_id_from_refund( $resource );

		if ( empty( $capture_id ) ) {
			Helper::srfm_log( 'PayPal webhook: Missing capture_id in PAYMENT.CAPTURE.REFUNDED event.' );
			return;
		}

		Helper::srfm_log(
			sprintf(
				'Refund lookup info - Refund ID: %s, Capture ID: %s',
				$refund_id,
				$capture_id
			)
		);

		// Find payment by capture_id (transaction_id).
		$payment = Payments::get_by_transaction_id( $capture_id );

		if ( ! $payment ) {
			Helper::srfm_log(
				sprintf(
					'REFUND FAILED: Could not find payment entry. Refund ID: %s, Capture ID: %s',
					$refund_id,
					$capture_id
				)
			);
			return;
		}

		$payment_id = ! empty( $payment['id'] ) && is_numeric( $payment['id'] ) ? intval( $payment['id'] ) : 0;

		// Extract refund details.
		$refund_amount = isset( $resource['amount']['value'] ) && is_numeric( $resource['amount']['value'] ) ? floatval( $resource['amount']['value'] ) : 0;
		$currency      = ! empty( $resource['amount']['currency_code'] ) && is_string( $resource['amount']['currency_code'] ) ? sanitize_text_field( strtolower( $resource['amount']['currency_code'] ) ) : 'usd';
		$refund_status = ! empty( $resource['status'] ) && is_string( $resource['status'] ) ? sanitize_text_field( $resource['status'] ) : 'unknown';

		Helper::srfm_log(
			sprintf(
				'Processing refund for payment entry ID: %d, Amount: %s %s, Status: %s',
				$payment_id,
				$refund_amount,
				strtoupper( $currency ),
				$refund_status
			)
		);

		// Only process completed refunds.
		if ( 'COMPLETED' !== $refund_status ) {
			Helper::srfm_log( 'Unexpected refund status: ' . $refund_status . '. Skipping processing.' );
			return;
		}

		// Update refund data in database.
		$update_refund_data = $this->update_refund_data( $payment_id, $resource, $refund_amount, $currency );

		if ( ! $update_refund_data ) {
			Helper::srfm_log( 'REFUND FAILED: Failed to update refund data for payment entry ID: ' . $payment_id );
			return;
		}

		Helper::srfm_log(
			sprintf(
				'REFUND SUCCESS: Payment refunded successfully. Refund ID: %s, Amount: %s %s, Payment Entry ID: %d',
				$refund_id,
				$refund_amount,
				strtoupper( $currency ),
				$payment_id
			)
		);
	}

	/**
	 * Extract capture ID from refund resource.
	 *
	 * PayPal refund webhook includes a link to the parent capture in the 'up' rel link.
	 *
	 * @param array $resource Refund resource data from webhook.
	 * @since 2.4.0
	 * @return string Capture ID or empty string if not found.
	 */
	private function extract_capture_id_from_refund( $resource ) {
		$links = $resource['links'] ?? [];

		if ( ! is_array( $links ) ) {
			return '';
		}

		// Find the 'up' link which points to the parent capture.
		foreach ( $links as $link ) {
			if ( ! is_array( $link ) ) {
				continue;
			}

			if ( isset( $link['rel'] ) && 'up' === $link['rel'] && ! empty( $link['href'] ) ) {
				// Extract capture ID from URL like: https://api.sandbox.paypal.com/v2/payments/captures/7U6446858J068822P.
				$href       = $link['href'];
				$parts      = explode( '/', $href );
				$capture_id = end( $parts );

				Helper::srfm_log( 'Extracted capture_id from refund links: ' . $capture_id );
				return $capture_id;
			}
		}

		Helper::srfm_log( 'Could not extract capture_id from refund links' );
		return '';
	}

	/**
	 * Update refund data for a payment.
	 *
	 * @param int    $payment_id Payment ID.
	 * @param array  $refund_response Refund response data from PayPal.
	 * @param float  $refund_amount Refund amount.
	 * @param string $currency Currency code.
	 * @since 2.4.0
	 * @return bool Whether the update was successful.
	 */
	private function update_refund_data( $payment_id, $refund_response, $refund_amount, $currency ) {
		if ( empty( $payment_id ) || empty( $refund_response ) ) {
			return false;
		}

		// Get payment record.
		$payment = Payments::get( $payment_id );
		if ( ! $payment ) {
			Helper::srfm_log( 'Payment record not found for ID: ' . $payment_id );
			return false;
		}

		// Check if refund already exists.
		$refund_id = ! empty( $refund_response['id'] ) && is_string( $refund_response['id'] ) ? sanitize_text_field( $refund_response['id'] ) : '';
		if ( $this->check_if_refund_already_exists( $payment, $refund_id ) ) {
			Helper::srfm_log( 'Refund already exists for refund_id: ' . $refund_id );
			return true;
		}

		// Prepare refund data for payment_data column.
		$refund_data = [
			'refund_id'   => $refund_id,
			'amount'      => floatval( $refund_amount ),
			'currency'    => sanitize_text_field( strtoupper( $currency ) ),
			'status'      => ! empty( $refund_response['status'] ) && is_string( $refund_response['status'] ) ? sanitize_text_field( $refund_response['status'] ) : 'COMPLETED',
			'created'     => time(),
			'refunded_by' => 'paypal_webhook',
			'refunded_at' => gmdate( 'Y-m-d H:i:s' ),
		];

		// Validate refund amount to prevent over-refunding.
		$original_amount    = floatval( $payment['total_amount'] );
		$existing_refunds   = floatval( $payment['refunded_amount'] ?? 0 );
		$new_refund_amount  = floatval( $refund_amount );
		$total_after_refund = $existing_refunds + $new_refund_amount;

		if ( $total_after_refund > $original_amount ) {
			Helper::srfm_log(
				sprintf(
					'Over-refund attempt blocked. Payment ID: %d, Original: %s, Existing refunds: %s, New refund: %s',
					$payment_id,
					number_format( $original_amount, 2 ),
					number_format( $existing_refunds, 2 ),
					number_format( $new_refund_amount, 2 )
				)
			);
			return false;
		}

		// Add refund data to payment_data column (for audit trail).
		$payment_data_result = Payments::add_refund_to_payment_data( $payment_id, $refund_data );

		// Update the refunded_amount column.
		$refund_amount_result = Payments::add_refund_amount( $payment_id, $new_refund_amount );

		// Calculate appropriate payment status.
		$payment_status = 'succeeded'; // Default.
		if ( $total_after_refund >= $original_amount ) {
			$payment_status = 'refunded'; // Fully refunded.
		} elseif ( $total_after_refund > 0 ) {
			$payment_status = 'partially_refunded'; // Partially refunded.
		}

		// Update payment status and log.
		$current_logs = Helper::get_array_value( $payment['log'] );
		$refund_type  = $total_after_refund >= $original_amount ? __( 'Full', 'sureforms-pro' ) : __( 'Partial', 'sureforms-pro' );
		$new_log      = [
			'title'      => sprintf(
				/* translators: %s: Refund type (Full or Partial) */
				__( '%s Payment Refund', 'sureforms-pro' ),
				$refund_type
			),
			'created_at' => current_time( 'mysql' ),
			'messages'   => [
				sprintf(
					/* translators: %s: Refund ID */
					__( 'Refund ID: %s', 'sureforms-pro' ),
					$refund_id
				),
				sprintf(
					/* translators: %s: Payment Gateway */
					__( 'Payment Gateway: %s', 'sureforms-pro' ),
					'PayPal'
				),
				sprintf(
					/* translators: 1: Refund amount, 2: Currency */
					__( 'Refund Amount: %1$s %2$s', 'sureforms-pro' ),
					number_format( $new_refund_amount, 2 ),
					strtoupper( $currency )
				),
				sprintf(
					/* translators: 1: Total refunded amount, 2: Currency, 3: Original amount, 4: Currency */
					__( 'Total Refunded: %1$s %2$s of %3$s %4$s', 'sureforms-pro' ),
					number_format( $total_after_refund, 2 ),
					strtoupper( $currency ),
					number_format( $original_amount, 2 ),
					strtoupper( $currency )
				),
				sprintf(
					/* translators: %s: Refund status */
					__( 'Refund Status: %s', 'sureforms-pro' ),
					'COMPLETED'
				),
				sprintf(
					/* translators: %s: Payment status */
					__( 'Payment Status: %s', 'sureforms-pro' ),
					ucfirst( str_replace( '_', ' ', $payment_status ) )
				),
				sprintf(
					/* translators: %s: Refunded by method */
					__( 'Refunded by: %s', 'sureforms-pro' ),
					__( 'PayPal Webhook', 'sureforms-pro' )
				),
			],
		];

		$current_logs[] = $new_log;

		$update_data = [
			'status' => $payment_status,
			'log'    => $current_logs,
		];

		// Update payment record with status and log.
		$payment_update_result = Payments::update( $payment_id, $update_data );

		// Check if all operations succeeded.
		if ( false === $payment_data_result ) {
			Helper::srfm_log( 'Failed to store refund data in payment_data for payment ID: ' . $payment_id );
		}

		if ( false === $refund_amount_result ) {
			Helper::srfm_log( 'Failed to update refunded_amount column for payment ID: ' . $payment_id );
			return false;
		}

		if ( false === $payment_update_result ) {
			Helper::srfm_log( 'Failed to update payment status and log for payment ID: ' . $payment_id );
			return false;
		}

		Helper::srfm_log(
			sprintf(
				'Refund processed successfully. Payment ID: %d, Refund ID: %s, Amount: %s %s',
				$payment_id,
				$refund_id,
				number_format( $new_refund_amount, 2 ),
				strtoupper( $currency )
			)
		);

		return true;
	}

	/**
	 * Check if refund already exists in payment data.
	 *
	 * @param array  $payment Payment record data.
	 * @param string $refund_id Refund ID from PayPal.
	 * @since 2.4.0
	 * @return bool True if refund already exists, false otherwise.
	 */
	private function check_if_refund_already_exists( $payment, $refund_id ) {
		if ( empty( $refund_id ) ) {
			return false;
		}

		// Use Helper::get_array_value() to handle stdClass objects.
		$payment_data = Helper::get_array_value( $payment['payment_data'] ?? [] );

		if ( empty( $payment_data['refunds'] ) ) {
			return false;
		}

		// O(1) lookup using refund ID as array key.
		return is_array( $payment_data['refunds'] ) && isset( $payment_data['refunds'][ $refund_id ] );
	}

	/**
	 * Handle PAYMENT.SALE.COMPLETED event.
	 *
	 * CRITICAL: This handles subscription payment completion (both initial and renewal).
	 * PayPal sends PAYMENT.SALE.COMPLETED for subscription billing cycles.
	 *
	 * Logic:
	 * - If parent subscription has no transaction_id → Initial payment → Update parent
	 * - If parent subscription has transaction_id → Renewal payment → Create child record
	 *
	 * @param array $resource Resource data from webhook.
	 * @since 2.4.0
	 * @return void
	 */
	private function handle_payment_sale_completed( $resource ) {
		$sale_id = $resource['id'] ?? '';

		if ( empty( $sale_id ) ) {
			Helper::srfm_log( 'PayPal webhook: Missing sale_id in PAYMENT.SALE.COMPLETED event.' );
			return;
		}

		Helper::srfm_log( 'Processing PAYMENT.SALE.COMPLETED for sale_id: ' . $sale_id );

		// PAYMENT.SALE.COMPLETED is always for subscriptions.
		$billing_agreement_id = $resource['billing_agreement_id'] ?? '';

		if ( empty( $billing_agreement_id ) ) {
			Helper::srfm_log( 'PayPal webhook: Missing billing_agreement_id in PAYMENT.SALE.COMPLETED event.' );
			return;
		}

		// Find parent subscription record.
		$parent_subscription = Payments::get_main_subscription_record( $billing_agreement_id );

		if ( ! $parent_subscription ) {
			Helper::srfm_log( 'PayPal webhook: Parent subscription not found for billing_agreement_id: ' . $billing_agreement_id );
			return;
		}

		Helper::srfm_log( 'Found parent subscription record with ID: ' . ( $parent_subscription['id'] ?? 'unknown' ) );

		// Check if this is initial or renewal payment.
		$is_initial_payment = empty( $parent_subscription['transaction_id'] ?? '' );

		Helper::srfm_log(
			sprintf(
				'Payment type detected: %s. Subscription record has transaction_id: %s',
				$is_initial_payment ? 'Initial Payment' : 'Renewal Payment',
				! empty( $parent_subscription['transaction_id'] ?? '' ) ? 'YES' : 'NO'
			)
		);

		if ( $is_initial_payment ) {
			Helper::srfm_log( 'Processing as initial subscription payment...' );
			$this->process_initial_subscription_payment( $parent_subscription, $resource, $billing_agreement_id );
		} else {
			Helper::srfm_log( 'Processing as subscription renewal payment...' );
			$this->process_subscription_renewal_payment( $resource, $billing_agreement_id );
		}

		Helper::srfm_log(
			sprintf(
				'Subscription payment processed successfully. Type: %s, Subscription ID: %s, Sale ID: %s',
				$is_initial_payment ? 'Initial' : 'Renewal',
				$billing_agreement_id,
				$sale_id
			)
		);
	}

	/**
	 * Handle PAYMENT.SALE.REFUNDED event.
	 *
	 * Processes refunds for subscription payments (initial and renewal).
	 * Updates payment status, refunded amount, and adds refund details to payment data.
	 *
	 * @param array $resource Resource data from webhook.
	 * @since 2.4.0
	 * @return void
	 */
	private function handle_payment_sale_refunded( $resource ) {
		$refund_id = $resource['id'] ?? '';
		Helper::srfm_log( 'Processing PAYMENT.SALE.REFUNDED for refund_id: ' . $refund_id );

		// Extract sale_id from resource (the parent sale being refunded).
		$sale_id = $this->extract_sale_id_from_sale_refund( $resource );

		if ( empty( $sale_id ) ) {
			Helper::srfm_log( 'PayPal webhook: Missing sale_id in PAYMENT.SALE.REFUNDED event.' );
			return;
		}

		Helper::srfm_log(
			sprintf(
				'Refund lookup info - Refund ID: %s, Sale ID: %s',
				$refund_id,
				$sale_id
			)
		);

		// Find payment by sale_id (transaction_id).
		$payment = Payments::get_by_transaction_id( $sale_id );

		if ( ! $payment ) {
			Helper::srfm_log(
				sprintf(
					'REFUND FAILED: Could not find payment entry. Refund ID: %s, Sale ID: %s',
					$refund_id,
					$sale_id
				)
			);
			return;
		}

		$payment_id = ! empty( $payment['id'] ) && is_numeric( $payment['id'] ) ? intval( $payment['id'] ) : 0;

		// Extract refund details.
		$refund_amount = isset( $resource['amount']['total'] ) && is_numeric( $resource['amount']['total'] ) ? floatval( $resource['amount']['total'] ) : 0;
		$currency      = ! empty( $resource['amount']['currency'] ) && is_string( $resource['amount']['currency'] ) ? sanitize_text_field( strtolower( $resource['amount']['currency'] ) ) : 'usd';
		$refund_status = ! empty( $resource['state'] ) && is_string( $resource['state'] ) ? sanitize_text_field( $resource['state'] ) : 'unknown';

		Helper::srfm_log(
			sprintf(
				'Processing refund for payment entry ID: %d, Amount: %s %s, Status: %s',
				$payment_id,
				$refund_amount,
				strtoupper( $currency ),
				$refund_status
			)
		);

		// Only process completed refunds.
		if ( 'completed' !== strtolower( $refund_status ) ) {
			Helper::srfm_log( 'Unexpected refund status: ' . $refund_status . '. Skipping processing.' );
			return;
		}

		// Update refund data in database.
		$update_refund_data = $this->update_refund_data( $payment_id, $resource, $refund_amount, $currency );

		if ( ! $update_refund_data ) {
			Helper::srfm_log( 'REFUND FAILED: Failed to update refund data for payment entry ID: ' . $payment_id );
			return;
		}

		Helper::srfm_log(
			sprintf(
				'REFUND SUCCESS: Payment refunded successfully. Refund ID: %s, Amount: %s %s, Payment Entry ID: %d',
				$refund_id,
				$refund_amount,
				strtoupper( $currency ),
				$payment_id
			)
		);
	}

	/**
	 * Extract sale ID from sale refund resource.
	 *
	 * PayPal sale refund webhook includes a link to the parent sale in the 'sale' rel link.
	 *
	 * @param array $resource Sale refund resource data from webhook.
	 * @since 2.4.0
	 * @return string Sale ID or empty string if not found.
	 */
	private function extract_sale_id_from_sale_refund( $resource ) {
		$links = $resource['links'] ?? [];

		if ( ! is_array( $links ) ) {
			return '';
		}

		// Find the 'sale' link which points to the parent sale.
		foreach ( $links as $link ) {
			if ( ! is_array( $link ) ) {
				continue;
			}

			if ( isset( $link['rel'] ) && 'sale' === $link['rel'] && ! empty( $link['href'] ) ) {
				// Extract sale ID from URL like: https://api.sandbox.paypal.com/v1/payments/sale/99M24651226093908.
				$href    = $link['href'];
				$parts   = explode( '/', $href );
				$sale_id = end( $parts );

				Helper::srfm_log( 'Extracted sale_id from sale refund links: ' . $sale_id );
				return $sale_id;
			}
		}

		Helper::srfm_log( 'Could not extract sale_id from sale refund links' );
		return '';
	}

	/**
	 * Handle BILLING.SUBSCRIPTION.ACTIVATED event.
	 *
	 * @param array $resource Resource data from webhook.
	 * @since 2.4.0
	 * @return void
	 */
	private function handle_subscription_activated( $resource ) {
		$subscription_id = $resource['id'] ?? '';
		$this->update_subscription_status( $subscription_id, 'active', 'Subscription Activated' );
	}

	/**
	 * Handle BILLING.SUBSCRIPTION.CANCELLED event.
	 *
	 * @param array $resource Resource data from webhook.
	 * @since 2.4.0
	 * @return void
	 */
	private function handle_subscription_cancelled( $resource ) {
		$subscription_id = $resource['id'] ?? '';
		$this->update_subscription_status( $subscription_id, 'canceled', 'Subscription Cancelled' );
	}

	/**
	 * Handle BILLING.SUBSCRIPTION.EXPIRED event.
	 *
	 * @param array $resource Resource data from webhook.
	 * @since 2.4.0
	 * @return void
	 */
	private function handle_subscription_expired( $resource ) {
		$subscription_id = $resource['id'] ?? '';
		$this->update_subscription_status( $subscription_id, 'canceled', 'Subscription Expired' );
	}

	/**
	 * Handle BILLING.SUBSCRIPTION.SUSPENDED event.
	 *
	 * @param array $resource Resource data from webhook.
	 * @since 2.4.0
	 * @return void
	 */
	private function handle_subscription_suspended( $resource ) {
		$subscription_id = $resource['id'] ?? '';
		$this->update_subscription_status( $subscription_id, 'paused', 'Subscription Suspended' );
	}

	/**
	 * Handle BILLING.SUBSCRIPTION.UPDATED event.
	 *
	 * @param array $resource Resource data from webhook.
	 * @since 2.4.0
	 * @return void
	 */
	private function handle_subscription_updated( $resource ) {
		$subscription_id = $resource['id'] ?? '';
		Helper::srfm_log( 'PayPal webhook: Subscription updated for ID: ' . $subscription_id );
		// Additional update logic can be added here if needed.
	}

	/**
	 * Update subscription status.
	 *
	 * @param string $subscription_id Subscription ID from PayPal.
	 * @param string $status          New status.
	 * @param string $log_title       Log entry title.
	 * @since 2.4.0
	 * @return void
	 */
	private function update_subscription_status( $subscription_id, $status, $log_title ) {
		if ( empty( $subscription_id ) ) {
			Helper::srfm_log( 'PayPal webhook: Missing subscription_id for status update.' );
			return;
		}

		$subscription = Payments::get_by_transaction_id( $subscription_id );

		if ( ! $subscription ) {
			Helper::srfm_log( 'PayPal webhook: Subscription not found for ID: ' . $subscription_id );
			return;
		}

		$payment_id = ! empty( $subscription['id'] ) && is_numeric( $subscription['id'] ) ? intval( $subscription['id'] ) : 0;

		// Prepare log entry.
		$current_logs = Helper::get_array_value( $subscription['log'] );
		$new_log      = [
			'title'      => $log_title,
			'created_at' => current_time( 'mysql' ),
			'messages'   => [
				sprintf(
					/* translators: %s: Subscription ID */
					__( 'Subscription ID: %s', 'sureforms-pro' ),
					$subscription_id
				),
				sprintf(
					/* translators: %s: Payment Gateway */
					__( 'Payment Gateway: %s', 'sureforms-pro' ),
					'PayPal'
				),
				sprintf(
					/* translators: %s: Status */
					__( 'Status: %s', 'sureforms-pro' ),
					ucfirst( $status )
				),
				__( 'Updated via PayPal webhook', 'sureforms-pro' ),
			],
		];

		$current_logs[] = $new_log;

		// Update subscription.
		Payments::update(
			$payment_id,
			[
				'subscription_status' => $status,
				'log'                 => $current_logs,
			]
		);

		Helper::srfm_log( 'Updated subscription status to ' . $status . ' for ID: ' . $subscription_id );
	}
}
