<?php
/**
 * PayPal payment gateway (invoice-based).
 *
 * Uses PayPal Orders API v2 (Checkout). Creates an order and returns the
 * buyer approval URL; payment is captured on return and/or via webhook.
 *
 * @package Lucrativeit_Subscription_Recurring_Billing
 */

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

/**
 * PayPal gateway. Invoice-based: a Checkout link is emailed each cycle.
 */
class LUCRSURE_PayPal_Gateway extends LUCRSURE_Abstract_Gateway {

	/**
	 * Cached OAuth access token for this request.
	 *
	 * @var string
	 */
	protected $access_token = '';

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->id                  = 'paypal';
		$this->title               = __( 'PayPal', 'lucrativeit-subscription-recurring-billing' );
		$this->automatic_recurring = false;
	}

	/**
	 * Whether credentials are present when enabled.
	 *
	 * @return bool
	 */
	public function is_enabled() {
		$config = $this->config();
		if ( empty( $config['enabled'] ) ) {
			return false;
		}

		$client_id     = isset( $config['client_id'] ) ? trim( (string) $config['client_id'] ) : '';
		$client_secret = isset( $config['client_secret'] ) ? trim( (string) $config['client_secret'] ) : '';

		return '' !== $client_id && '' !== $client_secret;
	}

	/**
	 * Create a PayPal Checkout order for the invoice.
	 *
	 * @param array $invoice Invoice row.
	 * @return array
	 */
	public function create_payment( array $invoice ) {
		$token = $this->get_access_token();
		if ( '' === $token ) {
			return array(
				'success'            => false,
				'payment_url'        => '',
				'gateway_payment_id' => '',
				'message'            => __( 'PayPal API credentials are not configured or authentication failed.', 'lucrativeit-subscription-recurring-billing' ),
			);
		}

		$amount   = number_format( (float) $invoice['amount'], 2, '.', '' );
		$currency = strtoupper( (string) $invoice['currency'] );
		$invoice_number = (string) $invoice['invoice_number'];

		$payload = array(
			'intent'         => 'CAPTURE',
			'purchase_units' => array(
				array(
					'reference_id' => substr( $invoice_number, 0, 127 ),
					'custom_id'    => substr( $invoice_number, 0, 127 ),
					'description'  => substr( 'Invoice ' . $invoice_number, 0, 127 ),
					'amount'       => array(
						'currency_code' => $currency,
						'value'         => $amount,
					),
				),
			),
			'application_context' => array(
				'brand_name'          => substr( get_bloginfo( 'name' ), 0, 127 ),
				'landing_page'        => 'NO_PREFERENCE',
				'user_action'         => 'PAY_NOW',
				'return_url'          => home_url( '/?lucrsure_payment=success&invoice=' . rawurlencode( $invoice_number ) . '&lucrsure_gateway=paypal' ),
				'cancel_url'          => home_url( '/?lucrsure_payment=cancel&invoice=' . rawurlencode( $invoice_number ) . '&lucrsure_gateway=paypal' ),
				'shipping_preference' => 'NO_SHIPPING',
			),
		);

		$response = wp_remote_post(
			$this->api_base() . '/v2/checkout/orders',
			array(
				'timeout' => 30,
				'headers' => array(
					'Authorization'     => 'Bearer ' . $token,
					'Content-Type'      => 'application/json',
					'PayPal-Request-Id' => 'lucrsure-' . md5( $invoice_number . '|' . $amount . '|' . $currency ),
				),
				'body'    => wp_json_encode( $payload ),
			)
		);

		if ( is_wp_error( $response ) ) {
			return array(
				'success'            => false,
				'payment_url'        => '',
				'gateway_payment_id' => '',
				'message'            => $response->get_error_message(),
			);
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		$url  = $this->approve_link( $body );

		if ( '' === $url ) {
			$message = __( 'PayPal Checkout order could not be created.', 'lucrativeit-subscription-recurring-billing' );
			if ( ! empty( $body['message'] ) ) {
				$message = (string) $body['message'];
			} elseif ( ! empty( $body['details'][0]['description'] ) ) {
				$message = (string) $body['details'][0]['description'];
			}

			return array(
				'success'            => false,
				'payment_url'        => '',
				'gateway_payment_id' => '',
				'message'            => $message,
			);
		}

		return array(
			'success'            => true,
			'payment_url'        => esc_url_raw( $url ),
			'gateway_payment_id' => isset( $body['id'] ) ? sanitize_text_field( (string) $body['id'] ) : '',
			'message'            => __( 'PayPal Checkout order created.', 'lucrativeit-subscription-recurring-billing' ),
		);
	}

	/**
	 * Capture an approved PayPal order and mark the matching invoice paid.
	 *
	 * @param string $order_id PayPal order id (returned as `token` on return URL).
	 * @return bool
	 */
	public function capture_order( $order_id ) {
		$order_id = sanitize_text_field( (string) $order_id );
		if ( '' === $order_id || ! preg_match( '/^[A-Za-z0-9_-]{1,64}$/', $order_id ) ) {
			return false;
		}

		$token = $this->get_access_token();
		if ( '' === $token ) {
			return false;
		}

		$response = wp_remote_post(
			$this->api_base() . '/v2/checkout/orders/' . rawurlencode( $order_id ) . '/capture',
			array(
				'timeout' => 30,
				'headers' => array(
					'Authorization' => 'Bearer ' . $token,
					'Content-Type'  => 'application/json',
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			return false;
		}

		$code = (int) wp_remote_retrieve_response_code( $response );
		$body = json_decode( wp_remote_retrieve_body( $response ), true );

		// Already captured orders often return 422; still try to resolve the invoice.
		$status = isset( $body['status'] ) ? sanitize_text_field( (string) $body['status'] ) : '';
		if ( $code >= 400 && 'COMPLETED' !== $status ) {
			$existing = $this->get_order( $order_id );
			if ( ! $existing ) {
				return false;
			}
			$body   = $existing;
			$status = isset( $body['status'] ) ? sanitize_text_field( (string) $body['status'] ) : '';
		}

		if ( 'COMPLETED' !== $status ) {
			return false;
		}

		$invoice_number = '';
		if ( ! empty( $body['purchase_units'][0]['custom_id'] ) ) {
			$invoice_number = sanitize_text_field( (string) $body['purchase_units'][0]['custom_id'] );
		} elseif ( ! empty( $body['purchase_units'][0]['reference_id'] ) ) {
			$invoice_number = sanitize_text_field( (string) $body['purchase_units'][0]['reference_id'] );
		}

		if ( '' === $invoice_number || ! class_exists( 'LUCRSURE_Invoice_Repository' ) ) {
			return false;
		}

		$invoices = new LUCRSURE_Invoice_Repository();
		$invoice  = $invoices->find_by_number( $invoice_number );
		if ( ! $invoice ) {
			return false;
		}

		$capture_id = '';
		if ( ! empty( $body['purchase_units'][0]['payments']['captures'][0]['id'] ) ) {
			$capture_id = sanitize_text_field( (string) $body['purchase_units'][0]['payments']['captures'][0]['id'] );
		}

		$update = array( 'gateway_payment_id' => $capture_id ? $capture_id : $order_id );
		$invoices->update( (int) $invoice['id'], $update );

		if ( class_exists( 'LUCRSURE_Billing_Engine' ) ) {
			LUCRSURE_Billing_Engine::instance()->mark_invoice_paid( (int) $invoice['id'] );
		}

		return true;
	}

	/**
	 * Fetch an order from PayPal.
	 *
	 * @param string $order_id Order id.
	 * @return array|null
	 */
	public function get_order( $order_id ) {
		$order_id = sanitize_text_field( (string) $order_id );
		if ( '' === $order_id || ! preg_match( '/^[A-Za-z0-9_-]{1,64}$/', $order_id ) ) {
			return null;
		}

		$token = $this->get_access_token();
		if ( '' === $token ) {
			return null;
		}

		$response = wp_remote_get(
			$this->api_base() . '/v2/checkout/orders/' . rawurlencode( $order_id ),
			array(
				'timeout' => 30,
				'headers' => array(
					'Authorization' => 'Bearer ' . $token,
					'Content-Type'  => 'application/json',
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			return null;
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		return is_array( $body ) ? $body : null;
	}

	/**
	 * API host for the configured environment.
	 *
	 * @return string
	 */
	public function api_base() {
		$config = $this->config();
		return ! empty( $config['sandbox'] )
			? 'https://api-m.sandbox.paypal.com'
			: 'https://api-m.paypal.com';
	}

	/**
	 * Obtain (and cache) an OAuth2 access token.
	 *
	 * @return string
	 */
	public function get_access_token() {
		if ( '' !== $this->access_token ) {
			return $this->access_token;
		}

		$config        = $this->config();
		$client_id     = isset( $config['client_id'] ) ? trim( (string) $config['client_id'] ) : '';
		$client_secret = isset( $config['client_secret'] ) ? trim( (string) $config['client_secret'] ) : '';

		if ( '' === $client_id || '' === $client_secret ) {
			return '';
		}

		$cache_key = 'lucrsure_paypal_token_' . md5( $client_id . ( ! empty( $config['sandbox'] ) ? '1' : '0' ) );
		$cached    = get_transient( $cache_key );
		if ( is_string( $cached ) && '' !== $cached ) {
			$this->access_token = $cached;
			return $this->access_token;
		}

		$response = wp_remote_post(
			$this->api_base() . '/v1/oauth2/token',
			array(
				'timeout' => 30,
				'headers' => array(
					'Authorization' => 'Basic ' . base64_encode( $client_id . ':' . $client_secret ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
					'Accept'        => 'application/json',
				),
				'body'    => array(
					'grant_type' => 'client_credentials',
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			return '';
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( empty( $body['access_token'] ) ) {
			return '';
		}

		$this->access_token = (string) $body['access_token'];
		$ttl                = isset( $body['expires_in'] ) ? max( 60, (int) $body['expires_in'] - 60 ) : 3000;
		set_transient( $cache_key, $this->access_token, $ttl );

		return $this->access_token;
	}

	/**
	 * Extract the buyer approval URL from an Orders API response.
	 *
	 * @param array|null $body Response body.
	 * @return string
	 */
	protected function approve_link( $body ) {
		if ( empty( $body['links'] ) || ! is_array( $body['links'] ) ) {
			return '';
		}

		foreach ( $body['links'] as $link ) {
			if ( isset( $link['rel'], $link['href'] ) && 'approve' === $link['rel'] ) {
				return (string) $link['href'];
			}
			// Newer responses may use payer-action.
			if ( isset( $link['rel'], $link['href'] ) && 'payer-action' === $link['rel'] ) {
				return (string) $link['href'];
			}
		}

		return '';
	}
}
