<?php
/**
 * PayPal API Client Trait.
 *
 * Provides reusable HTTP communication methods for PayPal API operations.
 * This trait handles authentication, request formatting, response parsing,
 * and error handling for all PayPal API endpoints.
 *
 * @package sureforms-pro
 * @since 2.4.0
 */

namespace SRFM_Pro\Inc\Business\Payments\PayPal;

use SRFM\Inc\Payments\Payment_Helper;

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

/**
 * PayPal API Client Trait.
 *
 * Usage:
 * - Use this trait in any class that needs to communicate with PayPal API
 * - Provides centralized authentication and request handling
 * - Ensures consistent error handling across all PayPal operations
 *
 * @since 2.4.0
 */
trait Client {
	/**
	 * PayPal success status codes.
	 *
	 * HTTP status codes that indicate successful PayPal API responses:
	 * - 200: Success (GET requests, webhook verification)
	 * - 201: Created (POST requests - orders, subscriptions, refunds)
	 * - 204: No Content (DELETE requests - webhook removal)
	 *
	 * Reference:
	 * - Create order: https://developer.paypal.com/docs/api/orders/v2/#orders_create
	 * - Capture order: https://developer.paypal.com/docs/api/orders/v2/#orders_capture
	 * - Refund: https://developer.paypal.com/docs/api/payments/v2/#captures_refund
	 * - Webhooks: https://developer.paypal.com/docs/api/webhooks/v1/
	 *
	 * @var array
	 * @since 2.4.0
	 */
	public static $paypal_success_codes = [ 200, 201, 204 ];

	/**
	 * Get PayPal OAuth bearer token.
	 *
	 * Authenticates with PayPal API using client credentials flow
	 * and returns an access token for subsequent API requests.
	 *
	 * @param string|null $mode Payment mode ('test' or 'live'). If null, uses current mode.
	 * @since 2.4.0
	 * @return string|false Access token on success, false on failure.
	 */
	public static function get_bearer( $mode = null ) {
		// Determine payment mode.
		if ( null === $mode ) {
			$mode = Payment_Helper::get_payment_mode();
		}

		// Get credentials for the specified mode.
		$client_id     = Helper::get_paypal_client_id( $mode );
		$client_secret = Helper::get_paypal_client_secret( $mode );

		if ( empty( $client_id ) || empty( $client_secret ) ) {
			return false;
		}

		// Build OAuth token request.
		$base_url      = Helper::get_api_base_url( $mode );
		$token_url     = $base_url . 'v1/oauth2/token';
		$authorization = base64_encode( $client_id . ':' . $client_secret ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Required for PayPal Basic Auth header

		$args = [
			'headers' => [
				'Authorization'                 => 'Basic ' . $authorization,
				'Content-Type'                  => 'application/x-www-form-urlencoded',
				'PayPal-Partner-Attribution-Id' => Helper::paypal_bn_code(),
			],
			'body'    => [ 'grant_type' => 'client_credentials' ],
			'timeout' => 30,
		];

		// Make OAuth request.
		$response = wp_remote_post( $token_url, $args );

		// Handle errors.
		if ( is_wp_error( $response ) ) {
			return false;
		}

		// Parse response.
		$status_code   = wp_remote_retrieve_response_code( $response );
		$response_body = wp_remote_retrieve_body( $response );
		$decoded       = json_decode( $response_body, true );

		if ( ! is_array( $decoded ) ) {
			return false;
		}

		if ( 200 !== $status_code || ! isset( $decoded['access_token'] ) || ! is_string( $decoded['access_token'] ) ) {
			return false;
		}

		return isset( $decoded['access_token'] ) && is_string( $decoded['access_token'] ) ? $decoded['access_token'] : '';
	}

	/**
	 * Make a request to PayPal API.
	 *
	 * Generic request handler that works for all PayPal API endpoints.
	 * Automatically handles:
	 * - Bearer token authentication
	 * - Request formatting (JSON encoding)
	 * - HTTP method selection (POST, GET, DELETE, PATCH)
	 * - Response parsing
	 * - Error extraction
	 * - PayPal debug ID capture
	 *
	 * @param string      $endpoint PayPal API endpoint (e.g., 'v2/checkout/orders').
	 * @param array       $body     Request body data (will be JSON encoded).
	 * @param string      $method   HTTP method ('POST', 'GET', 'DELETE', 'PATCH'). Default 'POST'.
	 * @param string|null $mode     Payment mode ('test' or 'live'). If null, uses current mode.
	 * @since 2.4.0
	 * @return array Response array with data or error information.
	 */
	public static function request( $endpoint, $body = [], $method = 'POST', $mode = null ) {
		// Determine payment mode.
		if ( null === $mode ) {
			$mode = Payment_Helper::get_payment_mode();
		}

		// Get bearer token.
		$access_token = self::get_bearer( $mode );
		if ( ! $access_token ) {
			return [
				'success' => false,
				'message' => __( 'Failed to authenticate with PayPal.', 'sureforms-pro' ),
			];
		}

		// Build request URL.
		$base_url    = Helper::get_api_base_url( $mode );
		$request_url = $base_url . $endpoint;

		// Build request arguments.
		$args = [
			'headers' => [
				'Authorization'                 => 'Bearer ' . $access_token,
				'Content-Type'                  => 'application/json',
				'PayPal-Partner-Attribution-Id' => Helper::paypal_bn_code(),
			],
			'method'  => strtoupper( $method ),
			'timeout' => 30,
		];

		// Add body for non-GET requests; ensure body is always a string or unset.
		if ( ! empty( $body ) && 'GET' !== strtoupper( $method ) ) {
			// WordPress expects (string) for JSON body.
			$encoded_body = wp_json_encode( $body );
			if ( ! is_string( $encoded_body ) ) {
				return [
					'success' => false,
					'message' => __( 'Failed to encode body for PayPal API request.', 'sureforms-pro' ),
				];
			}
			$args['body'] = $encoded_body;
		}

		// Make API request.
		$response = wp_remote_request( $request_url, $args );

		// Parse and return response.
		return self::parse_response( $response );
	}

	/**
	 * Parse PayPal API response.
	 *
	 * Extracts data from HTTP response, handles errors, and returns
	 * a consistent response format.
	 *
	 * @param array|\WP_Error $response HTTP response or WP_Error.
	 * @since 2.4.0
	 * @return array Parsed response with success status and data/error.
	 */
	private static function parse_response( $response ) {
		// Handle WordPress HTTP errors.
		if ( is_wp_error( $response ) ) {
			$error_message = $response->get_error_message();
			return [
				'success' => false,
				'message' => $error_message,
			];
		}

		// Get response details.
		$status_code   = wp_remote_retrieve_response_code( $response );
		$response_body = wp_remote_retrieve_body( $response );

		// Handle HTTP 204 No Content responses (e.g., subscription activation, webhook deletion).
		// These are successful operations but return no body.
		if ( 204 === $status_code ) {
			$debug_id = wp_remote_retrieve_header( $response, 'paypal-debug-id' );
			$result   = [
				'success' => true,
				'status'  => 'completed',
			];

			if ( ! empty( $debug_id ) ) {
				$result['paypal_debug_id'] = $debug_id;
			}

			return $result;
		}

		// Decode JSON response body.
		$decoded = json_decode( $response_body, true );

		// Handle JSON decode errors.
		if ( ! is_array( $decoded ) ) {
			return [
				'success' => false,
				'message' => __( 'Invalid response from PayPal API.', 'sureforms-pro' ),
			];
		}

		// Extract PayPal debug ID (useful for support).
		$debug_id = wp_remote_retrieve_header( $response, 'paypal-debug-id' );
		if ( ! empty( $debug_id ) ) {
			$decoded['paypal_debug_id'] = $debug_id;
		}

		// Check if request was successful.
		if ( ! in_array( $status_code, self::$paypal_success_codes, true ) ) {
			$error_message = self::extract_error_message( $decoded );

			return array_merge(
				$decoded,
				[
					'success' => false,
					'message' => $error_message,
				]
			);
		}

		// Success - return data.
		return array_merge(
			$decoded,
			[
				'success' => true,
			]
		);
	}

	/**
	 * Extract error message from PayPal API error response.
	 *
	 * PayPal API returns errors in different formats depending on the endpoint.
	 * This method handles all known error response formats.
	 *
	 * Error formats:
	 * 1. name + message + details (most common)
	 * 2. error + error_description (OAuth errors)
	 * 3. message only (simple errors)
	 *
	 * @param array $response_data Decoded error response from PayPal.
	 * @since 2.4.0
	 * @return string User-friendly error message.
	 */
	private static function extract_error_message( $response_data ) {
		// Format 1: name + message + details (most API endpoints).
		if ( ! empty( $response_data['name'] ) && ! empty( $response_data['message'] ) ) {
			$error_message = $response_data['name'] . ': ' . $response_data['message'];

			// Check for detailed error information.
			if ( ! empty( $response_data['details'] ) && is_array( $response_data['details'] ) ) {
				$details = [];
				foreach ( $response_data['details'] as $detail ) {
					if ( ! empty( $detail['issue'] ) ) {
						$issue_text = $detail['issue'];
						if ( ! empty( $detail['description'] ) ) {
							$issue_text .= ': ' . $detail['description'];
						}
						$details[] = $issue_text;
					}
				}
				if ( ! empty( $details ) ) {
					$error_message .= ' (' . implode( '; ', $details ) . ')';
				}
			}

			return $error_message;
		}

		// Format 2: error + error_description (OAuth errors).
		if ( ! empty( $response_data['error'] ) && ! empty( $response_data['error_description'] ) ) {
			return $response_data['error'] . ': ' . $response_data['error_description'];
		}

		// Format 3: message only.
		if ( ! empty( $response_data['message'] ) ) {
			return $response_data['message'];
		}

		// Fallback.
		return __( 'An unknown error occurred with PayPal API.', 'sureforms-pro' );
	}
}
