<?php
/**
 * PayPal PSP Connector
 *
 * Implements the PspConnectorInterface so AmbassadorHub can use PayPal as
 * the active payment service provider.  A single connector type is registered
 * because PayPal is PSP-only — CRM and marketing automation remain separate.
 *
 * @package AmbassadorHub\Connectors\PayPal
 */

declare( strict_types=1 );

namespace AmbassadorHub\Connectors\PayPal;

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

use AmbassadorHub\Admin\InlineAssets;
use AmbassadorHub\Connectors\AbstractPspConnector;
use AmbassadorHub\Connectors\ConnectorResult;

/**
 * PayPal PSP connector.
 *
 * Registered once with the ConnectorRegistry under the 'psp' slot.
 * Payment initiation (phase 1) uses the PayPal Orders API v2 via direct HTTP.
 * Webhook callbacks (phase 2) are handled by WebhookHandler.
 */
final class PayPalPspConnector extends AbstractPspConnector {

	// -------------------------------------------------------------------------
	// ConnectorInterface — core identity
	// -------------------------------------------------------------------------

	public const ID   = 'paypal_psp';
	public const TYPE = 'psp';
	public const LOGO = AMBASSADOR_HUB_URL . 'src/Connectors/PayPal/assets/logo.svg';

	// -------------------------------------------------------------------------
	// Option key constants
	// -------------------------------------------------------------------------

	/** PayPal REST API client ID. */
	public const OPTION_CLIENT_ID = 'ambh_paypal_client_id';

	/** PayPal REST API client secret. */
	public const OPTION_CLIENT_SECRET = 'ambh_paypal_client_secret';

	/** API mode: 'sandbox' or 'live'. */
	public const OPTION_MODE = 'ambh_paypal_mode';

	/** Transient key for the cached connection-test result. */
	private const CONN_RESULT_TRANSIENT = 'ambh_paypal_psp_conn_result';

	// -------------------------------------------------------------------------
	// API helpers
	// -------------------------------------------------------------------------

	/**
	 * Return the PayPal API base URL for the configured mode.
	 *
	 * @return string Base URL without trailing slash.
	 */
	public static function get_api_base(): string {
		return 'live' === get_option( self::OPTION_MODE, 'sandbox' )
			? 'https://api-m.paypal.com'
			: 'https://api-m.sandbox.paypal.com';
	}

	/**
	 * {@inheritdoc}
	 */
	public static function get_label(): string {
		return esc_html__( 'PayPal', 'ambassadorhub' );
	}

	/**
	 * {@inheritdoc}
	 */
	public static function get_description(): string {
		return esc_html__( 'Accept PayPal, Venmo, and card payments via PayPal Orders API.', 'ambassadorhub' );
	}

	// -------------------------------------------------------------------------
	// ConnectorInterface — availability
	// -------------------------------------------------------------------------

	/**
	 * {@inheritdoc}
	 *
	 * Available only when both credentials are configured AND a connection test has
	 * succeeded. In the admin the test runs eagerly so the UI is always accurate.
	 * On the frontend the credentials alone are trusted so payment processing is
	 * never blocked by an untested state.
	 */
	public static function is_available(): bool {
		$client_id     = (string) get_option( self::OPTION_CLIENT_ID, '' );
		$client_secret = (string) get_option( self::OPTION_CLIENT_SECRET, '' );

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

		// If a connection test has been cached, use its result.
		$result = get_transient( self::CONN_RESULT_TRANSIENT );
		if ( false !== $result ) {
			return (bool) ( $result['success'] ?? false );
		}

		// Admin: run the test now so the UI reflects the actual connection state.
		if ( is_admin() ) {
			$result = Actions::test_connection();
			set_transient( self::CONN_RESULT_TRANSIENT, $result, DAY_IN_SECONDS );
			return (bool) ( $result['success'] ?? false );
		}

		// Frontend / cron / REST: credentials present — trust them until an admin test is cached.
		return true;
	}

	// -------------------------------------------------------------------------
	// ConnectorInterface — settings fields
	// -------------------------------------------------------------------------

	/**
	 * {@inheritdoc}
	 */
	public static function get_settings_fields(): array {
		return array(
			array(
				'type' => 'description',
				'text' => esc_html__( 'Configure your PayPal REST API credentials. Create a REST app in the PayPal Developer Dashboard to obtain a client ID and secret.', 'ambassadorhub' ),
			),
			array(
				'id'      => self::OPTION_MODE,
				'type'    => 'select',
				'title'   => esc_html__( 'Mode', 'ambassadorhub' ),
				'options' => array(
					'sandbox' => esc_html__( 'Sandbox (testing)', 'ambassadorhub' ),
					'live'    => esc_html__( 'Live', 'ambassadorhub' ),
				),
				'default' => 'sandbox',
			),
			array(
				'id'          => self::OPTION_CLIENT_ID,
				'type'        => 'text',
				'title'       => esc_html__( 'Client ID', 'ambassadorhub' ),
				'description' => esc_html__( 'Your PayPal REST app client ID. Found in the PayPal Developer Dashboard under Apps & Credentials.', 'ambassadorhub' ),
			),
			array(
				'id'          => self::OPTION_CLIENT_SECRET,
				'type'        => 'password',
				'title'       => esc_html__( 'Client secret', 'ambassadorhub' ),
				'description' => esc_html__( 'Your PayPal REST app client secret. Keep this private.', 'ambassadorhub' ),
			),
		);
	}

	// -------------------------------------------------------------------------
	// ConnectorInterface — registration callback
	// -------------------------------------------------------------------------

	/**
	 * {@inheritdoc}
	 *
	 * Registers the webhook REST route so it can receive payment status updates from PayPal.
	 */
	public static function on_registration(): void {

		// Load webhook endpoint handler so it can register its REST route.
		add_action( 'ambh_register_rest_routes', array( WebhookHandler::class, 'register_route' ) );

		// Flush the connection-test cache whenever credentials or mode change.
		add_action( 'update_option_' . self::OPTION_CLIENT_ID, array( self::class, 'on_creds_updated' ) );
		add_action( 'update_option_' . self::OPTION_CLIENT_SECRET, array( self::class, 'on_creds_updated' ) );
		add_action( 'update_option_' . self::OPTION_MODE, array( self::class, 'on_creds_updated' ) );
	}

	/**
	 * Called when PayPal credentials or mode are updated.
	 *
	 * Flushes the OAuth token cache, clears the cached connection-test result,
	 * and marks that settings were just saved so the panel auto-expands on the
	 * next page render.
	 *
	 * @return void
	 */
	public static function on_creds_updated(): void {
		Actions::flush_token();
		delete_transient( self::CONN_RESULT_TRANSIENT );
	}

	// -------------------------------------------------------------------------
	// Settings rendering
	// -------------------------------------------------------------------------

	/**
	 * {@inheritdoc}
	 *
	 * Renders the settings form and, below it, the live connection status.
	 */
	public static function render_settings(): void {
		parent::render_settings();

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only flag from WP options.php redirect.
		$just_saved = ! empty( $_GET['settings-updated'] );
		// Transient is cleared by on_creds_updated() when credentials actually change —
		// no need to clear it here (which would otherwise affect all connectors on the page).

		if ( ! self::is_available() ) {
			return;
		}

		$result = get_transient( self::CONN_RESULT_TRANSIENT );
		if ( false === $result ) {
			$result = Actions::test_connection();
			set_transient( self::CONN_RESULT_TRANSIENT, $result, DAY_IN_SECONDS );
		}

		$success        = (bool) ( $result['success'] ?? false );
		$mode           = (string) ( $result['mode'] ?? '' );
		$client_id_hint = (string) ( $result['client_id_hint'] ?? '' );
		$message        = (string) ( $result['message'] ?? '' );

		$mode_label = 'live' === $mode
			? esc_html__( 'Live', 'ambassadorhub' )
			: esc_html__( 'Sandbox', 'ambassadorhub' );

		?>
		<div class="ambh-conn-status ambh-conn-status--<?php echo esc_attr( $success ? 'ok' : 'error' ); ?>">
			<?php if ( $success ) : ?>
				<span class="ambh-conn-status__icon" aria-hidden="true">&#10003;</span>
				<span class="ambh-conn-status__text">
					<?php
					echo esc_html(
						sprintf(
							/* translators: %s: Sandbox or Live */
							__( 'Connected · %s', 'ambassadorhub' ),
							$mode_label
						)
					);
					if ( '' !== $client_id_hint ) {
						echo ' <span class="ambh-conn-status__sub">(' . esc_html( $client_id_hint ) . ')</span>';
					}
					?>
				</span>
			<?php else : ?>
				<span class="ambh-conn-status__icon" aria-hidden="true">&#10007;</span>
				<span class="ambh-conn-status__text">
					<?php echo esc_html( $message ? $message : __( 'Connection failed.', 'ambassadorhub' ) ); ?>
				</span>
			<?php endif; ?>
		</div>
		<?php

		// Auto-expand this settings panel when the credentials were just saved.
		if ( $just_saved ) {
			$panel_id = 'ambh-psp-' . sanitize_html_class( self::ID ) . '-settings';

			InlineAssets::script(
				'(function () {
					var panelId = ' . wp_json_encode( $panel_id ) . ';
					var panel   = document.getElementById( panelId );
					var btn     = document.querySelector( \'[aria-controls="\' + panelId + \'"]\' );
					if ( panel ) { panel.removeAttribute( \'hidden\' ); }
					if ( btn )   { btn.setAttribute( \'aria-expanded\', \'true\' ); }
				})();'
			);
		}
	}

	// -------------------------------------------------------------------------
	// PspConnectorInterface
	// -------------------------------------------------------------------------

	/**
	 * {@inheritdoc}
	 *
	 * Creates a PayPal order and returns the buyer-approval redirect URL.
	 *
	 * @param array<string, mixed> $donation_data Normalised donation payload.
	 * @return ConnectorResult
	 */
	public static function initiate_payment( array $donation_data ): ConnectorResult {
		return Actions::execute_payment_initiation( $donation_data );
	}
}
