<?php

/**
 * The local alternative payment methods module.
 *
 * @package WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods
 */
declare (strict_types=1);
namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;

use WC_Order;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\Assets\AssetGetter;
use WooCommerce\PayPalCommerce\Settings\Data\Definition\FeaturesDefinition;
use WooCommerce\PayPalCommerce\Settings\Data\SettingsProvider;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
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\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\FeesUpdater;
/**
 * Class LocalAlternativePaymentMethodsModule
 */
class LocalAlternativePaymentMethodsModule implements ServiceModule, ExecutableModule
{
    use ModuleClassNameIdTrait;
    /**
     * Payment methods configuration.
     *
     * @var array
     */
    private array $payment_methods = array();
    /**
     * {@inheritDoc}
     */
    public function services(): array
    {
        return require __DIR__ . '/../services.php';
    }
    /**
     * {@inheritDoc}
     */
    public function run(ContainerInterface $c): bool
    {
        add_action('after_setup_theme', fn() => $this->run_with_translations($c));
        return \true;
    }
    /**
     * Set up WP hooks that depend on translation features.
     * Runs after the theme setup, when translations are available, which is fired
     * before the `init` hook, which usually contains most of the logic.
     *
     * @param ContainerInterface $c The DI container.
     * @return void
     */
    private function run_with_translations(ContainerInterface $c): void
    {
        $this->payment_methods = $c->get('ppcp-local-apms.payment-methods');
        $this->register_pwc_feature_flag_filters();
        $c->get('ppcp-local-apms.oxxo.integration')->init();
        if ('DE' === $c->get('api.shop.country')) {
            $c->get('ppcp-local-apms.pui.integration')->init();
        }
        add_action('woocommerce_paypal_payments_check_pui_payment_captured', function (int $wc_order_id, string $order_id) use ($c) {
            $order_endpoint = $c->get('api.endpoint.order');
            $logger = $c->get('woocommerce.logger.woocommerce');
            $order = $order_endpoint->order($order_id);
            $order_status = $order->status();
            $logger->info("Checking payment captured webhook for WC order #{$wc_order_id}, PayPal order status: " . $order_status->name());
            $wc_order = wc_get_order($wc_order_id);
            if (!$wc_order instanceof WC_Order || $wc_order->get_status() !== 'on-hold') {
                return;
            }
            if ($order_status->name() !== OrderStatus::COMPLETED) {
                $message = __('Could not process WC order because PAYMENT.CAPTURE.COMPLETED webhook not received.', 'woocommerce-paypal-payments');
                $logger->error($message);
                $wc_order->update_status('failed', $message);
            }
        }, 10, 2);
        // When Local APMs are disabled, none of the following hooks are needed.
        if (!$this->should_add_local_apm_gateways($c)) {
            return;
        }
        add_action('wp_enqueue_scripts', function () use ($c) {
            if (!is_checkout() && !is_cart() && !is_wc_endpoint_url('order-pay')) {
                return;
            }
            $asset_getter = $c->get('ppcp-local-apms.asset_getter');
            assert($asset_getter instanceof AssetGetter);
            $asset_version = $c->get('ppcp.asset-version');
            wp_enqueue_style('ppcp-local-apms-gateway', $asset_getter->get_asset_url('gateway.css'), array(), $asset_version);
        });
        /**
         * The "woocommerce_payment_gateways" filter is responsible for ADDING
         * custom payment gateways to WooCommerce. Here, we add all the local
         * APM gateways to the filtered list, so they become available later on.
         */
        add_filter(
            'woocommerce_payment_gateways',
            /**
             * Param types removed to avoid third-party issues.
             *
             * @psalm-suppress MissingClosureParamType
             */
            function ($methods) use ($c) {
                if (!is_array($methods)) {
                    return $methods;
                }
                $payment_methods = $c->get('ppcp-local-apms.payment-methods');
                $payment_methods = apply_filters('woocommerce_paypal_payments_local_apm_payment_methods', $payment_methods);
                // Only register eligible gateways when the merchant is connected.
                $is_connected = $c->get('settings.flag.is-connected');
                $eligibility_checks = array();
                if ($is_connected) {
                    $eligibility_service = $c->get('settings.service.payment_methods_eligibilities');
                    $eligibility_checks = $eligibility_service->get_eligibility_checks();
                }
                foreach ($payment_methods as $key => $value) {
                    $gateway_id = $value['id'];
                    if (isset($eligibility_checks[$gateway_id]) && !$eligibility_checks[$gateway_id]()) {
                        continue;
                    }
                    $methods[] = $c->get('ppcp-local-apms.' . $key . '.wc-gateway');
                }
                return $methods;
            }
        );
        /**
         * Filters the "available gateways" list by REMOVING gateways that
         * are not available for the current customer.
         */
        add_filter(
            'woocommerce_available_payment_gateways',
            /**
             * Param types removed to avoid third-party issues.
             *
             * @psalm-suppress MissingClosureParamType
             */
            function ($methods) use ($c) {
                // @phpstan-ignore empty.property
                if (!is_array($methods) || is_admin() || empty(WC()->customer)) {
                    // Don't restrict the gateway list on wp-admin or when no customer is known.
                    return $methods;
                }
                $payment_methods = $c->get('ppcp-local-apms.payment-methods');
                /**
                 * Filter the payment methods array before checking availability.
                 *
                 * @param array $payment_methods The payment methods configuration array.
                 * @return array The filtered payment methods array.
                 */
                $payment_methods = apply_filters('woocommerce_paypal_payments_local_apm_payment_methods', $payment_methods);
                $customer_country = WC()->customer->get_billing_country() ?: WC()->customer->get_shipping_country();
                $site_currency = get_woocommerce_currency();
                // Remove unsupported gateways from the customer's payment options.
                foreach ($payment_methods as $payment_method) {
                    // Empty arrays mean "allow all" - skip restriction checks.
                    $is_currency_supported = empty($payment_method['currencies']) || in_array($site_currency, $payment_method['currencies'], \true);
                    $is_country_supported = empty($payment_method['countries']) || in_array($customer_country, $payment_method['countries'], \true);
                    if (!$is_currency_supported || !$is_country_supported) {
                        unset($methods[$payment_method['id']]);
                    }
                }
                return $methods;
            }
        );
        /**
         * Adds all local APM gateways in the "payment_method_type" block registry
         * to make the payment methods available in the Block Checkout.
         *
         * @see IntegrationRegistry::initialize
         */
        add_action('woocommerce_blocks_payment_method_type_registration', function (PaymentMethodRegistry $payment_method_registry) use ($c): void {
            $payment_methods = $c->get('ppcp-local-apms.payment-methods');
            /**
             * Filter the payment methods array before registering block payment methods.
             *
             * @param array $payment_methods The payment methods configuration array.
             * @return array The filtered payment methods array.
             */
            $payment_methods = apply_filters('woocommerce_paypal_payments_local_apm_payment_methods', $payment_methods);
            foreach ($payment_methods as $key => $value) {
                $payment_method_registry->register($c->get('ppcp-local-apms.' . $key . '.payment-method'));
            }
        });
        add_filter('woocommerce_paypal_payments_localized_script_data', function (array $data) use ($c) {
            $payment_methods = $c->get('ppcp-local-apms.payment-methods');
            /**
             * Filter the payment methods array before adding to disable-funding.
             *
             * @param array $payment_methods The payment methods configuration array.
             * @return array The filtered payment methods array.
             */
            $payment_methods = apply_filters('woocommerce_paypal_payments_local_apm_payment_methods', $payment_methods);
            $default_disable_funding = $data['url_params']['disable-funding'] ?? '';
            $funding_keys = array_keys(array_filter($payment_methods, fn(array $m) => ($m['disable_funding'] ?? \true) !== \false));
            $disable_funding = array_merge($funding_keys, array_filter(explode(',', $default_disable_funding)));
            $data['url_params']['disable-funding'] = implode(',', array_unique($disable_funding));
            return $data;
        });
        add_action('woocommerce_before_thankyou', array($this, 'handle_cancelled_local_apm'));
        add_action('template_redirect', array($this, 'handle_pwc_order_received_redirect'));
        add_action('woocommerce_paypal_payments_payment_capture_completed_webhook_handler', function (WC_Order $wc_order, string $order_id) use ($c) {
            $payment_methods = $c->get('ppcp-local-apms.payment-methods');
            if (!$this->is_local_apm($wc_order->get_payment_method(), $payment_methods)) {
                return;
            }
            $fees_updater = $c->get('wcgateway.helper.fees-updater');
            assert($fees_updater instanceof FeesUpdater);
            $fees_updater->update($order_id, $wc_order);
        }, 10, 2);
    }
    /**
     * Handle cancelled local APM payments on the thank you page.
     *
     * @param int $order_id The order ID.
     * @return void
     */
    public function handle_cancelled_local_apm($order_id): void
    {
        $order = wc_get_order($order_id);
        if (!$order instanceof WC_Order) {
            return;
        }
        $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash((string) $_SERVER['REQUEST_URI'])) : '';
        $query_string = (string) wp_parse_url($request_uri, \PHP_URL_QUERY);
        $params = array();
        wp_parse_str(str_replace('?', '&', $query_string), $params);
        $params = array_map('sanitize_text_field', $params);
        $cancelled = $params['cancelled'] ?? '';
        $order_key = $params['key'] ?? '';
        if (!$this->is_local_apm($order->get_payment_method(), $this->payment_methods) || !$cancelled || $order->get_order_key() !== $order_key) {
            return;
        }
        if ($order->get_payment_method() === \WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\PWCGateway::ID) {
            $this->handle_cancelled_crypto_payment($order, $cancelled);
            return;
        }
        $this->handle_cancelled_standard_apm($order, $params);
    }
    /**
     * Handle cancelled crypto payments.
     *
     * @param WC_Order $order The WooCommerce order.
     * @param string   $cancelled The cancelled parameter value.
     * @return void
     */
    private function handle_cancelled_crypto_payment(WC_Order $order, string $cancelled): void
    {
        if (!$cancelled) {
            return;
        }
        if ($order->get_status() === 'on-hold') {
            $order->update_status('failed', __('Pay with Crypto payment was cancelled or failed.', 'woocommerce-paypal-payments'));
            $order->add_order_note(__('Payment was cancelled or failed during the Pay with Crypto payment process.', 'woocommerce-paypal-payments'), 1);
        }
        add_filter('woocommerce_order_has_status', '__return_true');
        if (!wp_doing_ajax() && !is_admin()) {
            $clean_url = isset($_SERVER['REQUEST_URI']) ? remove_query_arg('cancelled', esc_url_raw(wp_unslash($_SERVER['REQUEST_URI']))) : '';
            wp_safe_redirect(home_url($clean_url));
            exit;
        }
    }
    /**
     * Handle cancelled standard APM payments (non-crypto).
     *
     * @param WC_Order $order The WooCommerce order.
     * @param array    $params Parsed query parameters from the return URL.
     * @return void
     */
    private function handle_cancelled_standard_apm(WC_Order $order, array $params = array()): void
    {
        $error_code = $params['errorcode'] ?? '';
        if ($error_code === 'processing_error' || $error_code === 'payment_error') {
            $order->update_status('failed', __("The payment can't be processed because of an error.", 'woocommerce-paypal-payments'));
            add_filter('woocommerce_order_has_status', '__return_true');
        }
    }
    /**
     * Check if given payment method is a local APM.
     *
     * @param string $selected_payment_method Selected payment method.
     * @param array  $payment_methods Available local APMs.
     * @return bool
     */
    private function is_local_apm(string $selected_payment_method, array $payment_methods): bool
    {
        foreach ($payment_methods as $payment_method) {
            if ($payment_method['id'] === $selected_payment_method) {
                return \true;
            }
        }
        return \false;
    }
    /**
     * Check if the local APMs should be added to the available payment gateways.
     *
     * @param ContainerInterface $container Container.
     * @return bool
     */
    private function should_add_local_apm_gateways(ContainerInterface $container): bool
    {
        // APMs are only available after merchant onboarding is completed.
        $is_connected = $container->get('settings.flag.is-connected');
        if (!$is_connected) {
            /**
             * When the merchant is _not_ connected yet, we still need to
             * register the APM gateways in one case:
             *
             * During the authentication process (which happens via a REST call)
             * the gateways need to be present, so they can be correctly
             * pre-configured for new merchants.
             */
            return $this->is_rest_request();
        }
        $settings_provider = $container->get('settings.settings-provider');
        assert($settings_provider instanceof SettingsProvider);
        return $settings_provider->is_method_enabled(PayPalGateway::ID);
    }
    /**
     * Register PWC feature flag filters.
     *
     * @return void
     */
    private function register_pwc_feature_flag_filters(): void
    {
        /**
         * Filter payment methods array to exclude PWC when feature flag is disabled.
         */
        add_filter('woocommerce_paypal_payments_local_apm_payment_methods', function (array $methods) {
            if ($this->is_pwc_feature_enabled()) {
                return $methods;
            }
            // Remove PWC from payment methods array when feature flag is disabled.
            return array_filter($methods, function ($method) {
                return $method['id'] !== 'ppcp-pwc';
            });
        });
        /**
         * Filter APM gateway list to conditionally exclude PWC.
         */
        add_filter('woocommerce_paypal_payments_gateway_group_apm', function (array $group): array {
            if (!$this->is_pwc_feature_enabled()) {
                $group = array_filter($group, function ($method) {
                    return $method['id'] !== 'ppcp-pwc';
                });
            }
            return $group;
        });
        /**
         * Filter todos list to conditionally exclude PWC-related todos.
         */
        add_filter('woocommerce_paypal_payments_todos_list', function (array $todos): array {
            if (!$this->is_pwc_feature_enabled()) {
                unset($todos['enable_pwc']);
                unset($todos['apply_for_pwc']);
            }
            return $todos;
        });
        /**
         * Filter features list to conditionally exclude PWC feature.
         */
        add_filter('woocommerce_paypal_payments_features_list', function (array $features): array {
            if (!$this->is_pwc_feature_enabled()) {
                unset($features[FeaturesDefinition::FEATURE_PAY_WITH_CRYPTO]);
            }
            return $features;
        });
        /**
         * Filter localized script data to exclude PWC from disable-funding list.
         */
        add_filter('woocommerce_paypal_payments_localized_script_data', function (array $data) {
            if (!$this->is_pwc_feature_enabled()) {
                return $data;
            }
            $current_disable_funding = $data['url_params']['disable-funding'] ?? '';
            $funding_sources = array_filter(explode(',', $current_disable_funding));
            $funding_sources = array_filter($funding_sources, function ($source) {
                return $source !== 'pwc';
            });
            $data['url_params']['disable-funding'] = implode(',', array_unique($funding_sources));
            return $data;
        }, 11);
    }
    /**
     * Handle PWC order received page redirect to strip token parameter.
     *
     * PayPal automatically appends a 'token' parameter to return URLs after crypto payments.
     * When the 'token' parameter is present on the order-received page, WooCommerce displays
     * a minimal page instead of the full order details.
     *
     * This intercepts PWC orders on 'template_redirect' (before any output buffering)
     * and redirects to a clean URL without the token, allowing WooCommerce to display
     * the full order-received page.
     *
     * Note: PWC uses ORDER_COMPLETE_ON_PAYMENT_APPROVAL, so the token serves no purpose
     * but we can't prevent PayPal from appending it.
     *
     * @return void
     */
    public function handle_pwc_order_received_redirect(): void
    {
        // Only run on order-received endpoint.
        if (!is_wc_endpoint_url('order-received')) {
            return;
        }
        $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash((string) $_SERVER['REQUEST_URI'])) : '';
        $query_string = (string) wp_parse_url($request_uri, \PHP_URL_QUERY);
        $params = array();
        wp_parse_str(str_replace('?', '&', $query_string), $params);
        $params = array_map('sanitize_text_field', $params);
        if (empty($params['token']) || !empty($params['cancelled'])) {
            return;
        }
        // Get order ID from the URL endpoint.
        global $wp;
        $order_id = isset($wp->query_vars['order-received']) ? absint($wp->query_vars['order-received']) : 0;
        if (!$order_id) {
            return;
        }
        $order = wc_get_order($order_id);
        if (!$order instanceof WC_Order) {
            return;
        }
        // Only handle PWC - other payment methods may use 'token' legitimately (e.g., 3DS).
        if ($order->get_payment_method() !== \WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\PWCGateway::ID) {
            return;
        }
        wp_safe_redirect($order->get_checkout_order_received_url());
        exit;
    }
    /**
     * Checks, whether the current request is trying to access a WooCommerce REST endpoint.
     *
     * @return bool True, if the request path matches the WC-Rest namespace.
     */
    private function is_rest_request(): bool
    {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
        $request_uri = wp_unslash($_SERVER['REQUEST_URI'] ?? '');
        return is_string($request_uri) && strpos($request_uri, '/wp-json/wc/') !== \false;
    }
    /**
     * Check if PWC (Pay with Crypto) feature flag is enabled.
     *
     * @return bool True if PWC is enabled, false otherwise.
     */
    private function is_pwc_feature_enabled(): bool
    {
        return apply_filters(
            // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
            'woocommerce.feature-flags.woocommerce_paypal_payments.pwc_enabled',
            getenv('PCP_PWC_ENABLED') !== '0'
        );
    }
}
