<?php

/**
 * The API module.
 *
 * @package WooCommerce\PayPalCommerce\ApiClient
 */
declare (strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient;

use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\FactoryModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\Vendor\Psr\Log\LoggerInterface;
/**
 * Class ApiModule
 */
class ApiModule implements ServiceModule, FactoryModule, ExecutableModule
{
    use ModuleClassNameIdTrait;
    /**
     * {@inheritDoc}
     */
    public function services(): array
    {
        return require __DIR__ . '/../services.php';
    }
    /**
     * {@inheritDoc}
     */
    public function factories(): array
    {
        return require __DIR__ . '/../factories.php';
    }
    /**
     * {@inheritDoc}
     */
    public function run(ContainerInterface $c): bool
    {
        add_action('woocommerce_after_calculate_totals', function (\WC_Cart $cart) {
            $fees = $cart->fees_api()->get_fees();
            WC()->session->set('ppcp_fees', $fees);
        });
        add_filter('ppcp_create_order_request_body_data', function (array $data) {
            foreach ($data['purchase_units'] ?? array() as $purchase_unit_index => $purchase_unit) {
                foreach ($purchase_unit['items'] ?? array() as $item_index => $item) {
                    $data['purchase_units'][$purchase_unit_index]['items'][$item_index]['name'] = apply_filters('woocommerce_paypal_payments_cart_line_item_name', $item['name'], $item['cart_item_key'] ?? null);
                }
            }
            return $data;
        });
        add_action('woocommerce_paypal_payments_paypal_order_created', function (Order $order) use ($c) {
            $transient = $c->has('api.helper.order-transient') ? $c->get('api.helper.order-transient') : null;
            if ($transient instanceof OrderTransient) {
                $transient->on_order_created($order);
            }
        }, 10, 1);
        add_action('woocommerce_paypal_payments_woocommerce_order_created', function (WC_Order $wc_order, Order $order) use ($c) {
            $transient = $c->has('api.helper.order-transient') ? $c->get('api.helper.order-transient') : null;
            if ($transient instanceof OrderTransient) {
                $transient->on_woocommerce_order_created($wc_order, $order);
            }
        }, 10, 2);
        add_action('woocommerce_paypal_payments_clear_apm_product_status', static function () use ($c) {
            $failure_registry = $c->has('api.helper.failure-registry') ? $c->get('api.helper.failure-registry') : null;
            if ($failure_registry instanceof FailureRegistry) {
                $failure_registry->clear_failures(FailureRegistry::SELLER_STATUS_KEY);
            }
            $partners_endpoint = $c->has('api.endpoint.partners') ? $c->get('api.endpoint.partners') : null;
            if ($partners_endpoint instanceof PartnersEndpoint) {
                $partners_endpoint->clear_seller_status_cache();
            }
        }, 10, 2);
        /**
         * Flushes the API client caches.
         */
        add_action('woocommerce_paypal_payments_flush_api_cache', static function () use ($c) {
            $caches = array('api.paypal-bearer-cache', 'api.client-credentials-cache', 'api.token-rate-limiter-cache', 'settings.service.signup-link-cache');
            $logger = $c->get('woocommerce.logger.woocommerce');
            assert($logger instanceof LoggerInterface);
            $logger->info('Flushing API caches...');
            foreach ($caches as $cache_id) {
                $cache = $c->get($cache_id);
                assert($cache instanceof Cache);
                $cache->flush();
            }
        });
        /**
         * Refreshes the access token before a request is retried after an
         * authentication failure (see {@see RequestTrait::should_retry_after_auth_failure()}).
         *
         * A cached bearer token can outlive a change in the merchant's granted
         * scopes (e.g. vaulting enabled after the token was issued), causing
         * Vault and other calls to fail with 401 / 403 NOT_AUTHORIZED for up to
         * the token's lifetime. Discarding the cached token here forces a fresh
         * one to be issued with the current scopes for the retry.
         */
        add_filter('ppcp_retry_request_args', static function (array $args) use ($c): array {
            $authorization = $args['headers']['Authorization'] ?? '';
            if (!is_string($authorization) || 0 !== strpos($authorization, 'Bearer ')) {
                return $args;
            }
            $bearer_cache = $c->get('api.paypal-bearer-cache');
            assert($bearer_cache instanceof Cache);
            $bearer_cache->delete(PayPalBearer::CACHE_KEY);
            try {
                $bearer = $c->get('api.bearer');
                $args['headers']['Authorization'] = 'Bearer ' . $bearer->bearer()->token();
            } catch (RuntimeException $exception) {
                $logger = $c->get('woocommerce.logger.woocommerce');
                assert($logger instanceof LoggerInterface);
                $logger->warning('Could not refresh the access token for request retry: ' . $exception->getMessage());
            }
            return $args;
        });
        /**
         * Filters the request arguments to add the `'PayPal-Partner-Attribution-Id'` header.
         *
         * This ensures that all API requests include the appropriate BN code retrieved
         * from the `PartnerAttribution` helper. Using this approach avoids the need
         * for extensive refactoring of existing classes that use the `RequestTrait`.
         *
         * The filter is applied in {@see RequestTrait::request()} before making an API request.
         *
         * @see PartnerAttribution::get_bn_code() Retrieves the BN code dynamically.
         * @see RequestTrait::request() Where the filter `ppcp_request_args` is applied.
         */
        add_filter('ppcp_request_args', static function (array $args) use ($c) {
            if (isset($args['headers']['PayPal-Partner-Attribution-Id'])) {
                return $args;
            }
            $partner_attribution = $c->get('api.helper.partner-attribution');
            assert($partner_attribution instanceof PartnerAttribution);
            if (!isset($args['headers']) || !is_array($args['headers'])) {
                $args['headers'] = array();
            }
            $args['headers']['PayPal-Partner-Attribution-Id'] = $partner_attribution->get_bn_code();
            return $args;
        });
        return \true;
    }
}
