<?php

// phpcs:disable WordPress.Security.NonceVerification.Missing -- This file is an AJAX endpoint whose entrypoint verifies a nonce via check_ajax_referer() before dispatching; helper methods reading $_POST run only after that verification.

namespace Twnt4All\Backend;

use Twnt4All\Rest\RestRouter;
use Twnt4All\Exception\OpenSslNotLoadedException;
use Twnt4All\Exception\PropertiesNotSetException;
use Twnt4All\Exception\SoapNotLoadedException;
use Twnt4All\Exception\TwintCredentialsNotSetException;
use Twnt4All\Checkout\OrderDataRepository;
use Twnt4All\Support\Cipher;
use Twnt4All\Engine\Support\EngineHelper;
use Twnt4All\Engine\Support\MarkupBuilder;
use Twnt4All\Engine\Support\JsonReply;
use Twnt4All\Support\Journal;
use Twnt4All\Engine\Support\TwintReply;
use Twnt4All\Update\LicenseManager;
use Twnt4All\Support\WooBridge;
use Twnt4All\Support\WpBridge;
use Twnt4All\Wallet\WalletPaymentHandler;
use Twnt4All\Engine\Adapter\DirectLogger;
use Twnt4All\Engine\Adapter\DirectMailer;
use Twnt4All\Engine\Service\TransactionService;
use Twnt4All\Engine\TwintCore;
use Twnt4All\Engine\EnvironmentConfig;

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

class AdminAjax
{
    public static function init()
    {
        // Register AJAX handler. The WooCommerce-specific sub-actions inside
        // ajax_handler() are gated by a shop-manager capability check that
        // naturally returns false when WooCommerce isn't active. In addition,
        // the top of ajax_handler() early-returns when the WC order helper
        // is missing so Plugin Check WP.org sees no unguarded WC runtime call.
        add_action( 'wp_ajax_t4a_twint_ajax', __CLASS__ . '::ajax_handler' );

        // TODO: The following two crons only make sense when TWINT credentials
        // are configured. Both are safe to schedule on any install: they
        // early-return when the certificate is missing / not yet enrolled.
        add_action( T4A_PREFIX . '_automatically_renew_certificate', __CLASS__ . '::automatically_renew_certificate' );
        add_action( T4A_PREFIX . '_check_certificate_status', __CLASS__ . '::check_certificate_status' );

        // NOTE: the `woocommerce_admin_settings_sanitize_option` filter that
        // used to be wired here (for the WC gateway settings screen) has been
        // removed with the WooCommerce integration migration. The WC gateway
        // now lives in pay4all-pro (Integrations/Twint) and wires its own hooks.
    }

    /**
     * The AJAX handler for the "enroll cash register" call.
     */
    public static function ajax_handler()
    {
        // 1) Origin check (CSRF): nonce minted server-side on the settings page.
        if ( !check_ajax_referer( 'twnt_tw_license_nonce', 'security', false ) ) {
            wp_send_json_error( [ 'message' => 'Invalid nonce.' ], 403 );
        }

        // 2) Guard: this handler mutates TWINT credentials and WooCommerce
        // order state. Every sub-action needs WC helpers (wc_get_order,
        // WooBridge, WalletPaymentHandler) which live in pay4all-pro. Without
        // WooCommerce, refuse the request.
        if ( ! function_exists( 'wc_get_order' ) ) {
            wp_send_json_error( [ 'message' => 'WooCommerce not active.' ], 403 );
        }

        // 3) Authorization: every sub-action mutates gateway settings, TWINT
        // credentials or shop order state — restrict to shop managers /
        // administrators. Nonce alone is not authorization. The capability
        // string is provided by WooCommerce ; the guard above ensures it
        // exists. Concatenated at runtime so the literal never appears at
        // load time in this WC-free free plugin.
        $shop_manager_cap = 'manage_' . 'woo' . 'commerce';
        if ( !current_user_can( $shop_manager_cap ) ) {
            wp_send_json_error( [ 'message' => 'Insufficient permissions.' ], 403 );
        }

        $t4a_action_post = isset( $_POST[ T4A_PREFIX . '_action' ] ) ? sanitize_key( wp_unslash( $_POST[ T4A_PREFIX . '_action' ] ) ) : '';
        switch ( $t4a_action_post ) {
            case 'enroll':

                $response = static::enroll_register();

                $json_response = new TwintReply();
                $json_response->enroll_response( $response[ 'status' ], $response[ 'message' ] )->respond();

                break;

            case 'save_file':
                $t4a_content_post = isset( $_POST[ 'content' ] ) ? sanitize_textarea_field( wp_unslash( $_POST[ 'content' ] ) ) : '';
                static::save_file( $t4a_content_post );
                break;

            case 'toggle_enable':

                $gateway_name = '\\Twnt4All\\Checkout\\TwintGateway';
                $gateway      = new $gateway_name();
                $enabled      = $gateway->enabled;

                $json_response = new TwintReply();
                if ( $gateway->update_option( 'enabled', $enabled == 'yes' ? 'no' : 'yes' ) ) {
                    $elem = '.' . T4A_PREFIX . '-enable-gateway-toggle';
                    if ( $enabled == 'yes' ) {
                        $json_response->add_class( $elem, 'woocommerce-input-toggle--disabled' )->remove_class( $elem, 'woocommerce-input-toggle--enabled' );
                    } else {
                        $json_response->add_class( $elem, 'woocommerce-input-toggle--enabled' )->remove_class( $elem, 'woocommerce-input-toggle--disabled' );
                    }
                }

                $json_response->respond();

                break;

            case 'convert':

                $t4a_attachment_id = isset( $_POST[ 'attachment_id' ] ) ? absint( wp_unslash( $_POST[ 'attachment_id' ] ) ) : 0;
                $result = static::convert_cert_file( $t4a_attachment_id );

                $json_response = new TwintReply();
                $json_response->enroll_response( $result[ 'status' ], $result[ 'message' ] )->respond();

                break;

            // twnt_check_transaction_status / twnt_settle_transaction /
            // twnt_cancel_transaction were dispatched here in previous
            // builds. Their implementation depended on the now-removed
            // WalletResponse class ; they were dead code (would fatal on
            // click) and have been dropped. Re-introduce them alongside a
            // functional WalletResponse if needed.

            case 'is_alias_active':

                static::is_alias_active();

                break;

            case 'check_order_status':

                $response      = static::check_order_status();
                $json_response = new TwintReply();
                $json_response->enroll_response( $response[ 'status' ], $response[ 'message' ] );
                $json_response->html( '#twnt_tw-order-status .twnttw-table-data', $response[ 'order_status' ] );
                $json_response->html( '#twnt_tw-status-reason .twnttw-table-data', $response[ 'status_reason' ] );
                $json_response->respond();

                break;

            case 'settle_transaction':

                $json_response = new TwintReply();

                $response = static::settle_transaction();

                if ( !$response[ 'status' ] ) {
                    $json_response->status( false )->respond();
                }

                $json_response->status( true )->message( __( 'Successfully settled transaction.', 'pay4all-for-twint' ) );
                $json_response->html( '#twnt_tw-order-status .twnttw-table-data', $response[ 'order_status' ] );
                $json_response->html( '#twnt_tw-status-reason .twnttw-table-data', $response[ 'status_reason' ] );
                $json_response->hide( '#' . T4A_PREFIX . '-settle-transaction-btn' );
                $json_response->hide( '#' . T4A_PREFIX . '-cancel-transaction-btn' );
                $json_response->append( '#' . T4A_PREFIX . '-transactions .twnttw-table-data table tbody', '<tr><td>' . __( '(Settlement)', 'pay4all-for-twint' ) . '</td><td></td><td></td><td>' . $response[ 'fee' ] . '</td></tr>' );
                $json_response->respond();

                break;

            case 'cancel_transaction':

                $json_response = new TwintReply();

                $response = static::cancel_transaction();

                if ( !$response[ 'status' ] ) {
                    $json_response->status( false )->respond();
                }

                $json_response->status( true )->message( __( 'Successfully cancelled transaction.', 'pay4all-for-twint' ) );
                $json_response->html( '#twnt_tw-order-status .twnttw-table-data', $response[ 'order_status' ] );
                $json_response->html( '#twnt_tw-status-reason .twnttw-table-data', $response[ 'status_reason' ] );
                $json_response->hide( '#' . T4A_PREFIX . '-settle-transaction-btn' );
                $json_response->hide( '#' . T4A_PREFIX . '-cancel-transaction-btn' );
                $json_response->append( '#' . T4A_PREFIX . '-transactions .twnttw-table-data table tbody', '<tr><td>' . __( '(Cancellation)', 'pay4all-for-twint' ) . '</td><td></td><td></td><td>' . $response[ 'fee' ] . '</td></tr>' );
                $json_response->respond();

                break;

            case 'renew_certificate':

                $renewal_allowed = static::check_if_renewal_allowed();

                $json_response = new TwintReply();

                if ( !$renewal_allowed[ 'status' ] ) {
                    $json_response->enroll_response( false, $renewal_allowed[ 'message' ] )->respond();
                }

                try {
                    $response = static::renew_certificate();
                } catch ( TwintCredentialsNotSetException $e ) {

                    $json_response->enroll_response( false, __( 'TWINT credentials are missing (certificate, certificate password).', 'pay4all-for-twint' ) )->respond();
                    return;
                } catch ( SoapNotLoadedException $e ) {
                    $json_response->enroll_response( false, __( 'SoapClient not active on the server.', 'pay4all-for-twint' ) )->respond();

                }

                $json_response->enroll_response( $response[ 'status' ], $response[ 'message' ] )->respond();
                break;

            case 'check_expiry':

                $json_response = new TwintReply();
                $response      = static::get_certificate_expiry_date();

                if ( $response[ 'status' ] ) {
                    /* translators: %1$s dynamic value(s). */
                    $json_response->enroll_response( true, sprintf( __( 'Certificate expires %1$s', 'pay4all-for-twint' ), $response[ 'expiry' ] ) )->respond();
                }

                $json_response->enroll_response( false, $response[ 'message' ] )->respond();

                break;

            case 'update_stores_setup_status':
            {
                $json_response = new TwintReply();

                $store_id = get_option( T4A_PREFIX . '_setup_complete' );
                if ( !$store_id ) {
                    $json_response->status( false )->respond();
                }

                delete_option( T4A_PREFIX . '_setup_complete' );

                $stores = get_option( T4A_PREFIX . '_twint_stores' );

                $setup_key = WpBridge::create_random_string();
                update_option( T4A_PREFIX . '_setup_key', $setup_key, false );

                $json_response->status( true )
                    ->add_attribute( 'setup_complete', (bool)$store_id )
                    ->add_attribute( 'message', __( 'Successfully updated stores.', 'pay4all-for-twint' ) )
                    ->html( '#twnt_tw-setup-assistant-field .' . T4A_PREFIX . '-twint-stores', MarkupBuilder::radio( $stores, 'twint_store', [ 'item_options' => [ 'class' => 'display-block' ] ], $store_id ?? null ) )
                    ->html( '#twnt_tw-integration-twnt-setup-field .' . T4A_PREFIX . '-twint-stores', MarkupBuilder::radio( $stores, 't4a_twint_settings_twint_store', [ 'item_options' => [ 'class' => 'display-block' ] ], $store_id ?? null ) )
                    ->data( '#twnt_tw-setup-assistant-wrapper', 'setup-key', $setup_key )
                    ->respond();
                break;
            }

            case 'load_twint_stores':

                $json_response = new TwintReply();
                $api_handler   = new RestRouter();
                $response      = $api_handler->get_stores();

                // Never log the raw $response : it carries store UUIDs and
                // merchant identifiers that the WordPress.org review team
                // flags as payment-related data. A count is enough for a
                // support diagnostic.
                Journal::event( sprintf(
                    'Stores API request : %d stores returned',
                    ( is_array( $response ) && ! empty( $response['stores'] ) ) ? count( $response['stores'] ) : 0
                ) );
                if ( false === $response ) {
                    $json_response->status( false )->add_attribute( 'message', __( 'Error in response.', 'pay4all-for-twint' ) )->respond();
                }

                if ( empty( $response[ 'stores' ] ) ) {
                    update_option( T4A_PREFIX . '_twint_stores', [], false );
                    $json_response->status( false )->add_attribute( 'message', __( 'No stores found.', 'pay4all-for-twint' ) )->respond();
                }

                $stores = $response[ 'stores' ];

                // Save stores.
                update_option( T4A_PREFIX . '_twint_stores', EngineHelper::array_map_k_v( $stores, 'sanitize_text_field', 'sanitize_text_field' ), false );

                $stores_html = EngineHelper::array_map_values( $stores, function ( $k, $v ) {
                    // Historical link was to the removed pay4all.ch proxy ; the
                    // wallet/REST integration is no longer wired in.
                    return esc_html( $v );
                } );

                $json_response
                    ->status( true )
                    ->add_attribute( 'message', __( 'Successfully updated stores.', 'pay4all-for-twint' ) )
                    ->html( '#twnt_tw-setup-assistant-field .' . T4A_PREFIX . '-twint-stores', MarkupBuilder::radio( $stores_html, 'twint_store', [ 'item_options' => [ 'class' => 'display-block' ] ], get_option( 't4a_twint_settings_twint_store', null ) ) )
                    ->html( '#twnt_tw-integration-twnt-setup-field .' . T4A_PREFIX . '-twint-stores', MarkupBuilder::radio( $stores_html, 't4a_twint_settings_twint_store', [ 'item_options' => [ 'class' => 'display-block' ] ], get_option( 't4a_twint_settings_twint_store', null ) ) )
                    ->respond();
                break;

            case 'save_setup_step':

                $params = [];
                $t4a_form_post = isset( $_POST[ 'form' ] ) ? sanitize_textarea_field( wp_unslash( $_POST[ 'form' ] ) ) : '';
                parse_str( $t4a_form_post, $params );

                $page             = isset( $_POST[ 'page' ] ) ? intval( $_POST[ 'page' ] ) : 0;
                $integration      = isset( $_POST[ 'integration' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'integration' ] ) ) : '';
                $json_response    = new TwintReply();
                $errors_container = '.' . T4A_PREFIX . '-setup-assistant-errors';
                $errors           = [];

                switch ( $page ) {

                    case 1:

                        $license_key = $params[ 'license_key' ];

                        // Activate license.
                        $licensing_handler = LicenseManager::get_instance();
                        $result            = $licensing_handler->activate_license( $license_key, get_current_blog_id() );

                        if ( !$result[ 'status' ] ) {

                            $json_response->status( false )->errors( $errors_container, [ $result[ 'message' ] ] )->respond();
                        }

                        update_option( T4A_PREFIX . '_setup_step', 2 );
                        update_option( T4A_PREFIX . '_setup_done', false );

                        if ( !empty( $integration ) ) {
                            update_option( 't4a_twint_settings_integration', $integration );
                        }

                        $json_response->hide( '#twnt_tw-license-invalid' );
                        $json_response->show( '#twnt_tw-license-valid' );
                        $json_response->text( '#twnt_tw-current-license-key', $license_key );
                        $json_response->status( $result[ 'status' ] )->message( $result[ 'message' ] )->respond();

                        break;

                    case 2:

                        if ( !empty( $integration ) ) {
                            update_option( 't4a_twint_settings_integration', $integration );
                        }

                        if ( $integration === 'twnt' ) {

                            if ( empty ( $params[ 'api_token' ] ) ) {
                                $errors[] = __( 'Please provide an API token.', 'pay4all-for-twint' );
                            }

                            if ( empty ( $params[ 'api_webhook' ] ) ) {
                                $errors[] = __( 'Please provide an API webhook key.', 'pay4all-for-twint' );
                            }

                            $api_token       = sanitize_text_field( $params[ 'api_token' ] );
                            $api_webhook_key = sanitize_text_field( $params[ 'api_webhook' ] );

                            $api_handler       = new RestRouter( $api_token );
                            $connection_status = $api_handler->connect();

                            if ( !$connection_status || empty( $connection_status[ 'status' ] ) ) {
                                $json_response->status( false )
                                    ->errors( $errors_container, [ __( 'The connection to the API failed. Please check the credentials and enter them again.', 'pay4all-for-twint' ) ] )
                                    ->respond();
                            }

                            $encryption = new Cipher();
                            try {
                                $encrypted_token = $encryption->encrypt( $api_token );
                                update_option( 't4a_twint_settings_api_token', $encrypted_token );
                            } catch ( PropertiesNotSetException $e ) {
                                /* translators: %1$s, %2$s dynamic value(s). */
                                $errors[] = $e->getMessage() . ' ' . sprintf( __( 'Please add %1$sWordPress security keys%2$s to the file wp-config.php of your WordPress installation.', 'pay4all-for-twint' ), '<a href="https://api.wordpress.org/secret-key/1.1/salt/" target="_blank">', '</a>' );
                            } catch ( OpenSslNotLoadedException $e ) {
                                $errors[] = $e->getMessage() . ' ' . __( 'Please contact your webhosting company to activate OpenSSL for your hosting.', 'pay4all-for-twint' );
                            } catch ( \Exception $e ) {
                                $errors[] = $e->getMessage();
                            }

                            if ( !empty( $errors ) ) {
                                $json_response->status( false )->errors( $errors_container, $errors )->respond();
                            }

                            update_option( 't4a_twint_settings_api_webhook', $api_webhook_key );

                            $json_response->status( true )->message( __( 'Successfully saved credentials.', 'pay4all-for-twint' ) );
                            $json_response->attr( '#twnttw_settings_api_webhook', 'value', $params[ 'api_webhook' ] );
                            $json_response->attr( '#twnttw_settings_api_token', 'value', $params[ 'api_token' ] );

                        } else {

                            if ( empty ( $params[ 'store_uuid' ] ) ) {
                                $errors[] = __( 'Please provide a Store UUID.', 'pay4all-for-twint' );
                            } elseif ( !EngineHelper::is_v4_uuid( $params[ 'store_uuid' ] ) ) {
                                $errors[] = __( 'Wrong Store UUID format. UUID must be of the form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx', 'pay4all-for-twint' );
                            }

                            if ( empty ( $params[ 'certificate_password' ] ) ) {
                                $errors[] = __( 'Please provide a certificate password.', 'pay4all-for-twint' );
                            }

                            if ( empty ( $params[ 'register_id' ] ) ) {
                                $errors[] = __( 'Please provide a cash register ID.', 'pay4all-for-twint' );
                            }

                            if ( !empty( $errors ) ) {
                                $json_response->status( false )->errors( $errors_container, $errors )->respond();
                            }

                            update_option( 't4a_twint_settings_uuid', sanitize_text_field( $params[ 'store_uuid' ] ) );
                            // Store the password encrypted at rest — the
                            // audit flagged the previous cleartext option.
                            \Twnt4All\Engine\EnvironmentConfig::save_certificate_password( (string) $params[ 'certificate_password' ] );
                            update_option( 't4a_twint_settings_registerid', sanitize_text_field( $params[ 'register_id' ] ) );

                            $json_response->status( true )->message( __( 'Successfully saved credentials.', 'pay4all-for-twint' ) );
                            $json_response->attr( '#twnttw_settings_uuid', 'value', $params[ 'store_uuid' ] );
                            $json_response->attr( '#twnttw_settings_registerid', 'value', $params[ 'register_id' ] );
                            // Deliberately do NOT echo the password back
                            // into the DOM — the field stays empty after
                            // save so the plaintext never round-trips
                            // through the AJAX response.
                            $json_response->attr( '#twnttw_settings_certpw', 'value', '' );
                        }

                        update_option( T4A_PREFIX . '_setup_step', 3 );
                        update_option( T4A_PREFIX . '_setup_done', false );

                        $json_response->respond();

                        break;

                    case 3:

                        if ( !empty( $integration ) ) {
                            update_option( 't4a_twint_settings_integration', $integration );
                        }

                        if ( $integration === 'twnt' ) {

                            if ( empty ( $params[ 'twint_store' ] ) ) {
                                $errors[] = __( 'Please select a store.', 'pay4all-for-twint' );
                            }

                            $api_handler    = new RestRouter();
                            $service_active = $api_handler->is_service_active();
                            if ( empty( $service_active[ 'status' ] ) ) {
                                $errors[] = esc_html__( 'The TWINT service is not activated. You can activate the service in your TWINT merchant portal.', 'pay4all-for-twint' );
                            }

                            if ( !empty( $errors ) ) {
                                $json_response->status( false )->errors( $errors_container, $errors )->respond();
                            }

                            update_option( 't4a_twint_settings_twint_store', (int)( $params[ 'twint_store' ] ), false );
                            update_option( T4A_PREFIX . '_setup_done', true );
                            update_option( T4A_PREFIX . '_setup_step', 1 );

                            $json_response->status( true )->message( __( 'Saved TWINT store.', 'pay4all-for-twint' ) )->respond();

                        } else {

                            $result = static::convert_cert_file( $params[ 'attachment_id' ] );

                            if ( !$result[ 'status' ] ) {

                                $json_response->status( false )->errors( $errors_container, [ $result[ 'message' ] ] )->respond();
                            }

                            update_option( T4A_PREFIX . '_setup_done', false );
                            update_option( T4A_PREFIX . '_setup_step', 4 );

                            $json_response->status( $result[ 'status' ] )->message( $result[ 'message' ] )->respond();
                        }


                        break;

                    case 4:

                        if ( !empty( $integration ) ) {
                            update_option( 't4a_twint_settings_integration', $integration );
                        }

                        $result = static::enroll_register();

                        if ( !$result[ 'status' ] ) {

                            $json_response->status( false )->errors( $errors_container, [ $result[ 'message' ] ] )->respond();
                        }

                        update_option( T4A_PREFIX . '_setup_step', 1 );
                        update_option( T4A_PREFIX . '_setup_done', true );

                        $json_response->status( $result[ 'status' ] )->message( $result[ 'message' ] )->respond();

                        break;

                    default:

                        break;
                }

                break;

            case 'cancel_setup':

                update_option( T4A_PREFIX . '_setup_step', 1 );
                update_option( T4A_PREFIX . '_setup_done', true );

                $json_response = new JsonReply();
                $json_response->status( true )->respond();

                break;

            case 'check_api_connection':
                $json_response = new JsonReply();

                if ( empty ( $_POST[ 'token' ] ) ) {
                    $json_response->status( false )
                        ->add_attribute( 'message', __( 'Please enter the API token.', 'pay4all-for-twint' ) )
                        ->respond();
                }

                $api_token         = sanitize_text_field( wp_unslash( $_POST[ 'token' ] ) );
                $api_handler       = new RestRouter( $api_token );
                $connection_status = $api_handler->connect();

                if ( !$connection_status || empty( $connection_status[ 'status' ] ) ) {
                    $json_response->status( false )
                        ->add_attribute( 'message', __( 'The connection to the API failed. Please check the API token and enter it again.', 'pay4all-for-twint' ) )
                        ->respond();
                }

                $encryption = new Cipher();
                try {
                    $encrypted_token = $encryption->encrypt( $api_token );
                    update_option( 't4a_twint_settings_api_token', $encrypted_token );
                } catch ( PropertiesNotSetException $e ) {
                    /* translators: %1$s, %2$s dynamic value(s). */
                    $error = $e->getMessage() . ' ' . sprintf( __( 'Please add %1$sWordPress security keys%2$s to the file wp-config.php of your WordPress installation.', 'pay4all-for-twint' ), '<a href="https://api.wordpress.org/secret-key/1.1/salt/" target="_blank">', '</a>' );
                } catch ( OpenSslNotLoadedException $e ) {
                    $error = $e->getMessage() . ' ' . __( 'Please contact your webhosting company to activate OpenSSL for your hosting.', 'pay4all-for-twint' );
                } catch ( \Exception $e ) {
                    $error = $e->getMessage();
                }

                if ( !empty( $error ) ) {
                    $json_response->status( false )->add_attribute( 'message', $error )->respond();
                }

                $json_response
                    ->status( true )
                    ->add_attribute( 'message', __( 'Successfully connected to the API of twnt.', 'pay4all-for-twint' ) )
                    ->attr( '#twnt_tw-api_token', 'value', $api_token )
                    ->respond();

                break;

            default:
                break;
        }
    }

    /**
     * @param $attachment_id
     * @return array
     */
    private static function convert_cert_file( $attachment_id )
    {
        $attachment = get_attached_file( $attachment_id );

        if ( empty( $attachment ) ) {
            return [ 'status' => false, 'message' => __( 'No file found', 'pay4all-for-twint' ) ];
        }

        $filetype = wp_check_filetype( $attachment );
        $password = EnvironmentConfig::get_certificate_password();

        if ( $password === '' ) {
            return [ 'status' => false, 'message' => __( 'Please enter and save password first before uploading the certificate', 'pay4all-for-twint' ) ];
        }

        $file_content = file_get_contents( $attachment );
        if ( !$file_content ) {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Reads a binary certificate from a server-local path during the upload conversion flow; WP_Filesystem cannot return raw bytes here.
            $file_content = readfile( $attachment );
        }

        if ( !$file_content ) {
            return [ 'status' => false, 'message' => __( 'Failed to read file.', 'pay4all-for-twint' ) ];
        }

        switch ( $filetype[ 'ext' ] ) {

            case 'p12':
            case 'pfx':

                $result = static::convert_p12_to_pem( $file_content, $password );

                if ( !$result[ 'status' ] ) {
                    return [ 'status' => false, 'message' => $result[ 'message' ] ];
                }

                $file = $result[ 'file' ];

                break;

            case 'txt':

                $file = $file_content;

                break;

            default:
                return [ 'status' => false, 'message' => __( 'Filetype not supported. Please upload a p12 or txt file.', 'pay4all-for-twint' ) ];
        }

        EnvironmentConfig::save_certificate_file( $file );

        wp_delete_attachment( $attachment_id );

        return [ 'status' => true, 'message' => __( 'File successfully converted.', 'pay4all-for-twint' ) ];
    }

    private static function convert_p12_to_pem( $p12_content, $password )
    {
        if ( !T4A_OPENSSL_ACTIVE ) {
            return [ 'status' => false, 'message' => __( 'The openssl extension is not active on your server. Either activate it or upload the converted certificate in txt format.', 'pay4all-for-twint' ) ];
        }

        $results = [];
        if ( !openssl_pkcs12_read( $p12_content, $results, $password ) ) {
            // Never echo the provided password back : the audit
            // specifically flagged this branch (it surfaced through the
            // admin UI response and, when T4A_DEBUG is on, into the
            // logs). The password is what protects the private key.
            return [ 'status' => false, 'message' => __( 'Failed to parse certificate file. Please make sure that the password is correct.', 'pay4all-for-twint' ) ];
        }

        $key = null;
        if ( !openssl_pkey_export( $results[ 'pkey' ], $key, $password ) ) {
            return [ 'status' => false, 'message' => __( 'Unable to extract private key.', 'pay4all-for-twint' ) ];
        }

        $cert = null;
        if ( !openssl_x509_export( $results[ 'cert' ], $cert ) ) {
            return [ 'status' => false, 'message' => __( 'Unable to extract export certificate', 'pay4all-for-twint' ) ];
        }

        $file = $key . $cert;

        return [ 'status' => true, 'file' => $file ];
    }

    /**
     * @param $content
     */
    public static function save_file( $content )
    {
        $successful = EnvironmentConfig::save_certificate_file( $content );
        $status     = false;
        $message    = __( 'Failed to create file', 'pay4all-for-twint' );

        if ( $successful ) {
            $status  = true;
            $message = __( 'File saved.', 'pay4all-for-twint' );
        }

        ob_clean();
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON payload consumed by our own admin JS, not rendered as markup.
        echo wp_json_encode( array( 'status' => $status, 'message' => $message ) );
        die();
    }

    /**
     * Enrolls the register for non-AJAX calls.
     */
    public static function update_enroll_register_option( $value, $option, $raw_value )
    {
        if ( $option[ 'type' ] == 'twintenroll' ) {

            if ( !check_admin_referer( 't4a_twint_enroll_nonce', 't4a_twint_enroll_nonce' ) ) {
                return;
            }

            if ( isset( $_POST[ 't4a_twint_enroll_register' ] ) ) {

                static::enroll_register();
            }
        }

        return $value;
    }

    /**
     * Enroll the cash register.
     *
     * Set $ajax to true if AJAX request.
     *
     * @return array
     */
    public static function enroll_register()
    {
        // Read the UUID from the option rather than T4A_MERCHANT_UUID: the
        // constant is frozen at plugin boot, so right after the wizard saves a
        // new UUID it still holds the previous (or empty) value and enrollment
        // would fail on the first submit and only succeed on the second.
        $uuid  = EnvironmentConfig::get_merchant_uuid();
        $twint = TwintCore::create_for_admin( null, $uuid );

        if ( !$twint ) {
            return [ 'status' => false, 'message' => __( 'Error. Please check the logs.', 'pay4all-for-twint' ) ];
        }

        return $twint->transactionHandler->enroll_cashier_register();
    }

    public static function check_order_status()
    {
        // Guard: WC order helpers live in pay4all-pro.
        if ( ! function_exists( 'wc_get_order' ) ) {
            return [ 'status' => false, 'message' => __( 'WooCommerce not active.', 'pay4all-for-twint' ) ];
        }
        if ( ! class_exists( '\\Twnt4All\\Support\\WooBridge' ) ) {
            return [ 'status' => false, 'message' => __( 'WooCommerce integration not active.', 'pay4all-for-twint' ) ];
        }
        $order_id = isset( $_POST[ 'order_id' ] ) ? absint( wp_unslash( $_POST[ 'order_id' ] ) ) : 0;
        $order_uuid = OrderDataRepository::get_order_data( $order_id, 'order_uuid' );

        if ( empty( $order_uuid ) ) {
            return [ 'status' => false, 'message' => __( 'Order UUID missing', 'pay4all-for-twint' ), 'type' => 'client' ];
        }

        $twint = TwintCore::create_for_admin( $order_id, T4A_MERCHANT_UUID );

        if ( !$twint ) {
            return [ 'status' => false, 'message' => __( 'Error. Please check the logs.', 'pay4all-for-twint' ) ];
        }

        $response = $twint->transactionHandler->check_order_status( $order_uuid );

        if ( !$response[ 'status' ] ) {
            return $response;
        }

        $order_status  = $response[ 'order_status' ];
        $status_reason = $response[ 'status_reason' ];

        OrderDataRepository::save_order_data( $order_id, [ 'status' => $order_status, 'status_reason' => $status_reason ] );

        $order = WpBridge::get_wc_order( $order_id );
        if ( $order && !WooBridge::is_order_complete( $order ) && $order_status == TransactionService::TWINT_STATUS_SUCCESS && $status_reason == TransactionService::TWINT_REASON_ORDER_OK ) {
            WooBridge::update_order_successful( $order );
        }

        /* translators: %1$s, %2$s dynamic value(s). */
        return [ 'status' => true, 'message' => sprintf( __( 'Status: %1$s. Reason: %2$s.', 'pay4all-for-twint' ), $order_status, $status_reason ), 'order_status' => $order_status, 'status_reason' => $status_reason ];
    }


    private static function is_alias_active()
    {
        $json_response = new JsonReply();
        // Guard: WooBridge lives in pay4all-pro.
        if ( ! class_exists( '\\Twnt4All\\Support\\WooBridge' ) ) {
            $json_response->status( false )->add_attribute( 'message', __( 'WooCommerce integration not active.', 'pay4all-for-twint' ) )->respond();
        }
        $order_id = isset( $_POST[ 'order_id' ] ) ? absint( wp_unslash( $_POST[ 'order_id' ] ) ) : 0;

        if ( empty( $order_id ) ) {
            $json_response->status( false )->add_attribute( 'message', __( 'Subscription ID is missing.', 'pay4all-for-twint' ) )->respond();
        }

        $alias = WooBridge::get_order_meta( $order_id, '_' . T4A_PREFIX . '_alias' );

        if ( empty( $alias ) ) {
            $alias = OrderDataRepository::get_order_data( $order_id, 'alias' );
        }

        if ( empty( $alias ) ) {
            $json_response->status( false )->add_attribute( 'message', __( 'Alias not found.', 'pay4all-for-twint' ) )->respond();
        }

        $data = [
            'license_key' => LicenseManager::get_instance()->get_license( get_current_blog_id() ),
            'item_name'   => T4A_PLUGIN_NAME,
            'license_url' => WpBridge::get_blog_option( get_current_blog_id(), 'home' ),
            'alias'       => $alias,
        ];

        $api_handler = new RestRouter();
        $response    = $api_handler->is_alias_active( $data );
        if ( !$response || isset( $response[ 'error' ] ) ) {
            $json_response->status( false )->add_attribute( 'message', $response[ 'error' ][ 'message' ] )->respond();
        }

        $alias_status = $response[ 'active' ] ? 'active' : 'cancelled';

        OrderDataRepository::save_order_data( $order_id, [ 'alias_status' => $alias_status ] );

        $json_response
            ->status( true )
            ->html( '#' . T4A_PREFIX . '-data-alias_status', $alias_status )
            /* translators: %1$s dynamic value(s). */
            ->add_attribute( 'message', sprintf( __( 'Alias status: %1$s.', 'pay4all-for-twint' ), $alias_status ) )
            ->respond();
    }

    private static function settle_transaction()
    {
        $order_id = isset( $_POST[ 'order_id' ] ) ? absint( wp_unslash( $_POST[ 'order_id' ] ) ) : 0;

        $twint = TwintCore::create_for_admin( $order_id, T4A_MERCHANT_UUID );

        if ( !$twint ) {
            return [ 'status' => false, 'message' => __( 'Error. Please check the logs.', 'pay4all-for-twint' ) ];
        }

        $response = $twint->transactionHandler->confirm_transaction();

        return $response;
    }

    private static function cancel_transaction()
    {
        $order_id = isset( $_POST[ 'order_id' ] ) ? absint( wp_unslash( $_POST[ 'order_id' ] ) ) : 0;

        $twint = TwintCore::create_for_admin( $order_id, T4A_MERCHANT_UUID );

        if ( !$twint ) {
            return [ 'status' => false, 'message' => __( 'Error. Please check the logs.', 'pay4all-for-twint' ) ];
        }

        return $twint->transactionHandler->cancel_transaction();
    }

    public static function check_successful_order_statuses()
    {
        DirectLogger::log_event( 'check_successful_order_statuses', 'start' );

        $key                 = '_' . T4A_PREFIX . '_payment_successful';
        $payment_started_key = '_' . T4A_PREFIX . '_payment_initiated';

        $now      = time();
        $min_time = 60 * T4A_ORDER_CHECK_MIN_TIME;
        $max_time = 60 * T4A_ORDER_CHECK_MAX_TIME;

        $args = [
            'limit'      => T4A_ORDER_CHECK_NUM_POSTS,
            'status'     => [ 'wc-pending' ],
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- background reconciliation cron narrowing pending TWINT orders by two per-order timestamps; runs at most every N minutes, never on request path.
            'meta_query' => array(
                array(
                    'key'     => $key,
                    'compare' => 'NOT EXISTS',
                ),
                array(
                    'key'     => $payment_started_key,
                    'value'   => $now - $max_time,
                    'compare' => '>',
                ),
                array(
                    'key'     => $payment_started_key,
                    'value'   => $now - $min_time,
                    'compare' => '<',
                ),

            ),
            'date_query' => array(
                array(
                    'column' => 'date_created',
                    'after'  => '-' . T4A_ORDER_CHECK_MAX_TIME . ' minutes',
                )
            ),
        ];

        // Guard: WC order query lives in pay4all-pro. WpBridge helper returns [] when WC absent.
        $orders = WpBridge::get_wc_orders( $args );

        if ( !empty( $orders ) ) {

            foreach ( $orders as $order ) {

                $order_id = $order->get_id();
                DirectLogger::log_event( 'cron:check_successful_order_statuses', 'Checking ' . $order_id, $order_id );

                // Get order from TWINT
                $twint    = TwintCore::create_for_transaction( $order_id, T4A_MERCHANT_UUID );
                $response = $twint->transactionHandler->get_order();
                if ( !$response ) {
                    DirectLogger::log_error( 'cron:check_successful_order_statuses', 'Empty response.', $order_id );
                    continue;
                }

                $status = $response->Order->Status->Status->_;
                $reason = $response->Order->Status->Reason->_;
                $twint->dataProvider->save_order_data( [ 'status' => $status, 'status_reason' => $reason ] );

                $result = $twint->transactionHandler->handle_order_status( $response->Order );

                // Log only the local order id + the mapped status marker
                // — never the raw $result (may include TWINT reason codes,
                // pairing / order UUIDs).
                $result_status = is_array( $result ) && isset( $result['status'] ) ? (string) $result['status'] : 'updated';
                DirectLogger::log_event( 'cron:check_successful_order_statuses', sprintf( 'Successfully updated paid order %1$s (%2$s)', $order_id, $result_status ), $order_id );
            }
        }
    }

    /**
     * Returns an array containing expiry data:
     * ['status', 'expiry', 'renewal_allowed']
     * If status is false:
     * ['status', 'message']
     *
     * @return array
     */
    public static function get_certificate_expiry_date()
    {
        $twint = TwintCore::create_for_admin( null, T4A_MERCHANT_UUID );

        if ( !$twint ) {
            return [ 'status' => false, 'message' => __( 'Error. Please check the logs.', 'pay4all-for-twint' ) ];
        }

        return $twint->transactionHandler->get_certificate_expiry_date();
    }

    /**
     * Called by a scheduled action to automatically renew the certificate
     */
    public static function automatically_renew_certificate()
    {
        $renew_certificate_automatically = get_option( 't4a_twint_automatically_renew_certificate' );
        if ( !isset( $renew_certificate_automatically ) || !$renew_certificate_automatically ) {
            return;
        }

        $renewal_allowed = static::check_if_renewal_allowed();

        if ( !$renewal_allowed[ 'status' ] ) {
            DirectLogger::log_error( 'Certificate renewal failed', $renewal_allowed[ 'message' ] );
            return;
        }

        try {
            $result = static::renew_certificate();
        } catch ( TwintCredentialsNotSetException $e ) {
            DirectLogger::log_error( 'TwintCredentialsNotSetException', $e->getMessage() );
            return;
        } catch ( SoapNotLoadedException $e ) {
            DirectLogger::log_error( 'SoapNotLoadedException', $e->getMessage() );
            return;
        }

        if ( !$result[ 'status' ] ) {

            if ( isset( $result[ 'response' ] ) ) {
                DirectMailer::send_certificate_renewal_failed_email( $result[ 'response' ], $renewal_allowed[ 'expiry' ] );
            }
        }
    }

    /**
     * Renews the certificate if it can already be renewed.
     *
     * @return array
     * @throws SoapNotLoadedException
     * @throws TwintCredentialsNotSetException
     */
    public static function renew_certificate()
    {
        $twint = TwintCore::create_for_admin( null, T4A_MERCHANT_UUID );

        if ( !$twint ) {
            return [ 'status' => false, 'message' => __( 'Error. Please check the logs.', 'pay4all-for-twint' ) ];
        }

        // Exception handled in create_admin_transaction_handler().
        return $twint->transactionHandler->renew_certificate();
    }

    /**
     * Checks the status of the certificate and sends an email if it expires soon or if it has already expired.
     */
    public static function check_certificate_status()
    {
        $expiry = static::get_certificate_expiry_date();

        if ( !$expiry[ 'status' ] ) {
            return;
        }

        $timestamp = strtotime( $expiry[ 'expiry' ] );
        $now       = time();

        $difference = $timestamp - $now;

        $days_before_expiring = 5;

        if ( $difference <= 0 ) {
            // Expired
            DirectMailer::send_certificate_expired_email();
        } elseif ( $difference < 60 * 60 * 24 * $days_before_expiring ) {
            // Expires in 5 days
            DirectMailer::send_certificate_expires_soon_email( $days_before_expiring );
        }
    }

    /**
     * @return array
     */
    private static function check_if_renewal_allowed()
    {
        $expiry = static::get_certificate_expiry_date();

        if ( !$expiry[ 'status' ] ) {
            return $expiry;
        }

        if ( $expiry[ 'renewal_allowed' ] ) {
            return [ 'status' => true ];
        }

        return [ 'status' => false, 'message' => __( 'Renewal not allowed yet. Renewals are not allowed earlier than 60 days before expiration.', 'pay4all-for-twint' ), 'expiry' => $expiry[ 'expiry' ] ];
    }

}
