<?php

namespace Pay4AllForMypos\Checkout;

use Pay4AllForMypos\Api\IpcClient;
use Pay4AllForMypos\Backend\ConfigPage;
use Pay4AllForMypos\Support\Journal;

defined( 'ABSPATH' ) || exit;

/**
 * WooCommerce gateway that redirects the customer to a myPOS-hosted
 * checkout page via a signed IPCPurchase POST.
 *
 * Unlike the Stripe gateway (which calls a JSON API and returns a
 * `redirect` URL from `process_payment`), myPOS requires a real HTML
 * `<form method="post">` submitted from the customer's browser so the
 * customer's session cookies + fingerprint are attached to the request.
 * That's why `process_payment()` sends the customer to
 * `get_checkout_payment_url()` (WC's built-in "Pay for order" page) and
 * `receipt_page()` renders the auto-submitting form.
 */
class MyposGateway extends \WC_Payment_Gateway
{
    public const SLUG = 'pay4all_for_mypos';

    /**
     * myPOS accepts payments in these 15 ISO 4217 currencies only.
     * We block the gateway from appearing at checkout in any other currency.
     */
    private const SUPPORTED_CURRENCIES = [
        'BGN', 'USD', 'EUR', 'GBP', 'CHF', 'JPY', 'RON', 'HRK',
        'NOK', 'ISK', 'SEK', 'DKK', 'CZK', 'HUF', 'PLN',
    ];

    public function __construct()
    {
        $this->id                 = self::SLUG;
        $this->method_title       = __( 'Pay4All for myPOS', 'pay4all-for-mypos' );
        $this->method_description = __( 'Payment via myPOS Checkout (card, iDEAL, TWINT, Satispay depending on your configuration). The customer is redirected to a secure page hosted by myPOS and returns to the WooCommerce thank-you page.', 'pay4all-for-mypos' );
        $this->has_fields         = false;
        $this->supports           = [
            'products',
            'refunds',
        ];

        $this->init_form_fields();
        $this->init_settings();

        $this->title       = $this->get_option( 'title', __( 'Card / TWINT (myPOS)', 'pay4all-for-mypos' ) );
        $this->description = $this->get_option( 'description', __( 'Pay securely.', 'pay4all-for-mypos' ) );
        $this->enabled     = $this->get_option( 'enabled', 'no' );

        add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, [ $this, 'process_admin_options' ] );

        // WC calls this on the "Pay for order" page. It's what renders the
        // auto-submitting IPCPurchase form.
        add_action( 'woocommerce_receipt_' . $this->id, [ $this, 'receipt_page' ] );
    }

    /**
     * The real settings live in the WizardPage/ConfigPage. This gateway
     * settings screen is intentionally minimal - just an enable toggle plus
     * the visible title/description shown at checkout.
     */
    public function init_form_fields() : void
    {
        $this->form_fields = [
            'enabled' => [
                'title'   => __( 'Enable / Disable', 'pay4all-for-mypos' ),
                'type'    => 'checkbox',
                'label'   => __( 'Enable Pay4All for myPOS at checkout', 'pay4all-for-mypos' ),
                'default' => 'no',
            ],
            'title' => [
                'title'       => __( 'Title at checkout', 'pay4all-for-mypos' ),
                'type'        => 'text',
                'description' => __( 'The title the customer sees at checkout.', 'pay4all-for-mypos' ),
                'default'     => __( 'Card / TWINT (myPOS)', 'pay4all-for-mypos' ),
                'desc_tip'    => true,
            ],
            'description' => [
                'title'       => __( 'Description', 'pay4all-for-mypos' ),
                'type'        => 'textarea',
                'description' => __( 'Text displayed below the title at checkout.', 'pay4all-for-mypos' ),
                'default'     => __( 'Pay securely.', 'pay4all-for-mypos' ),
                'desc_tip'    => true,
            ],
        ];
    }

    /**
     * Hide the gateway at checkout when:
     *   - the merchant disabled it in WC settings ; OR
     *   - no myPOS Configuration Pack is set for the active mode ; OR
     *   - the store currency isn't one of the 15 myPOS supports.
     */
    public function is_available()
    {
        if ( 'yes' !== $this->enabled ) {
            return false;
        }
        if ( ! ConfigPage::has_credentials() ) {
            return false;
        }
        $currency = strtoupper( (string) get_woocommerce_currency() );
        if ( ! in_array( $currency, self::SUPPORTED_CURRENCIES, true ) ) {
            return false;
        }
        return true;
    }

    /**
     * Renders the description + logo shown under the radio button on the
     * classic (non-Blocks) checkout.
     */
    public function payment_fields()
    {
        if ( $this->description ) {
            // wp_kses_post() must be the OUTER call so Plugin Check
            // recognises the escape ; wpautop() only adds <p>, it doesn't
            // sanitize. Both KSES passes are idempotent when the input is
            // already safe, so the net behaviour is unchanged.
            echo wp_kses_post( wpautop( wptexturize( $this->description ) ) );
        }
        $logo = PAY4ALL_FOR_MYPOS_PLUGIN_URL . 'assets/img/mypos-logo.svg';
        printf(
            '<p><img src="%s" alt="%s" style="max-height:24px;vertical-align:middle;"></p>',
            esc_url( $logo ),
            esc_attr__( 'myPOS', 'pay4all-for-mypos' )
        );
    }

    /**
     * Send the customer to the "Pay for order" page - that's the page WC
     * calls `receipt_page()` on. From there our form auto-submits to myPOS.
     *
     * @param int $order_id
     * @return array
     */
    public function process_payment( $order_id )
    {
        $order = wc_get_order( $order_id );
        if ( ! $order instanceof \WC_Order ) {
            wc_add_notice( __( 'Order not found.', 'pay4all-for-mypos' ), 'error' );
            return [ 'result' => 'failure' ];
        }

        $order->update_status( 'pending', __( 'Awaiting myPOS payment (redirect pending).', 'pay4all-for-mypos' ) );
        $order->save();

        // Track this pending payment so the cron sweeper can reconcile it
        // if the customer never comes back and myPOS never notifies us.
        if ( class_exists( PendingPaymentsManager::class ) ) {
            PendingPaymentsManager::track_pending( (int) $order_id, ConfigPage::get_mode() );
        }

        return [
            'result'   => 'success',
            'redirect' => $order->get_checkout_payment_url( true ),
        ];
    }

    /**
     * Renders a full-viewport page that auto-submits the signed IPCPurchase
     * form to myPOS. Runs on WC's `woocommerce_receipt_{$this->id}` hook.
     *
     * @param int $order_id
     */
    public function receipt_page( $order_id ) : void
    {
        $order = wc_get_order( (int) $order_id );
        if ( ! $order instanceof \WC_Order ) {
            echo '<p>' . esc_html__( 'Order not found.', 'pay4all-for-mypos' ) . '</p>';
            return;
        }

        $mode    = ConfigPage::get_mode();
        $methods = ConfigPage::enabled_methods();

        $built = IpcFormBuilder::build_purchase( $order, $mode, $methods );
        if ( empty( $built['url'] ) || empty( $built['fields'] ) ) {
            echo '<p>' . esc_html__( 'Unable to prepare the myPOS payment. Please contact the shop owner.', 'pay4all-for-mypos' ) . '</p>';
            return;
        }

        // Empty the cart before redirect so a browser Back button doesn't
        // strand the customer with a filled cart pointing at a paid order.
        if ( function_exists( 'WC' ) && WC()->cart ) {
            WC()->cart->empty_cart();
        }

        // Enqueue the loading overlay CSS + auto-submit JS instead of
        // inlining <style>/<script> tags (WP.org guideline 7 forbids
        // inline scripts/styles on admin+frontend pages served by WP).
        wp_enqueue_style(
            'pay4all-for-mypos-receipt',
            PAY4ALL_FOR_MYPOS_PLUGIN_URL . 'assets/css/receipt.css',
            [],
            PAY4ALL_FOR_MYPOS_PLUGIN_VERSION
        );
        wp_enqueue_script(
            'pay4all-for-mypos-receipt',
            PAY4ALL_FOR_MYPOS_PLUGIN_URL . 'assets/js/receipt.js',
            [],
            PAY4ALL_FOR_MYPOS_PLUGIN_VERSION,
            true
        );

        $redirect_msg = esc_html__( 'Redirecting to myPOS...', 'pay4all-for-mypos' );
        $submit_label = esc_html__( 'Continue to myPOS', 'pay4all-for-mypos' );
        ?>
        <div class="pay4all-mypos-loading">
            <p class="pay4all-mypos-loading__msg"><?php echo esc_html( $redirect_msg ); ?></p>
            <div class="pay4all-mypos-loading__spinner"></div>
        </div>
        <form id="pay4all_for_mypos_form"
              class="pay4all-mypos-autosubmit"
              data-pay4all-mypos-autosubmit
              action="<?php echo esc_url( $built['url'] ); ?>"
              method="post">
            <?php foreach ( $built['fields'] as $name => $value ) : ?>
                <input type="hidden" name="<?php echo esc_attr( (string) $name ); ?>" value="<?php echo esc_attr( (string) $value ); ?>">
            <?php endforeach; ?>
            <noscript>
                <p class="pay4all-mypos-autosubmit__noscript"><?php esc_html_e( 'JavaScript is disabled - please click the button below to continue.', 'pay4all-for-mypos' ); ?></p>
                <p class="pay4all-mypos-autosubmit__noscript"><button type="submit"><?php echo esc_html( $submit_label ); ?></button></p>
            </noscript>
        </form>
        <?php
    }

    /**
     * WooCommerce native refund - returns true on success or WP_Error on
     * failure. WC records the refund via WC_Order_Refund on our behalf,
     * so we don't keep our own refund meta history.
     *
     * @param int         $order_id
     * @param float|null  $amount   Amount in the order's currency (decimal).
     * @param string      $reason   Free-text merchant note.
     * @return bool|\WP_Error
     */
    public function process_refund( $order_id, $amount = null, $reason = '' )
    {
        $order = wc_get_order( (int) $order_id );
        if ( ! $order instanceof \WC_Order ) {
            return new \WP_Error( 'pay4all_for_mypos_refund_no_order', __( 'Order not found.', 'pay4all-for-mypos' ) );
        }

        $trnref = (string) $order->get_meta( '_pay4all_for_mypos_trnref' );
        if ( $trnref === '' ) {
            // Fall back to WC's built-in transaction id, which we set on the
            // order during IPCPurchaseNotify.
            $trnref = (string) $order->get_transaction_id();
        }
        if ( $trnref === '' ) {
            return new \WP_Error(
                'pay4all_for_mypos_refund_no_trnref',
                __( 'No myPOS transaction reference on this order. The refund must be issued from the myPOS dashboard.', 'pay4all-for-mypos' )
            );
        }

        if ( $amount === null || (float) $amount <= 0 ) {
            return new \WP_Error( 'pay4all_for_mypos_refund_invalid_amount', __( 'Invalid refund amount.', 'pay4all-for-mypos' ) );
        }

        $mode   = ConfigPage::get_mode();
        $client = new IpcClient( $mode );
        $result = $client->refund( $trnref, (float) $amount, (string) $order->get_currency() );

        if ( ! empty( $result['ok'] ) ) {
            $refund_ref = $result['trnref'] ?? '';
            $note       = $reason !== ''
                ? sprintf(
                    /* translators: 1: refund amount, 2: currency, 3: myPOS refund reference, 4: merchant reason */
                    __( 'Pay4All for myPOS: refund %1$s %2$s processed (ref %3$s) - reason: %4$s', 'pay4all-for-mypos' ),
                    number_format( (float) $amount, 2, '.', '' ),
                    $order->get_currency(),
                    $refund_ref !== '' ? $refund_ref : '-',
                    $reason
                )
                : sprintf(
                    /* translators: 1: refund amount, 2: currency, 3: myPOS refund reference */
                    __( 'Pay4All for myPOS: refund %1$s %2$s processed (ref %3$s).', 'pay4all-for-mypos' ),
                    number_format( (float) $amount, 2, '.', '' ),
                    $order->get_currency(),
                    $refund_ref !== '' ? $refund_ref : '-'
                );
            $order->add_order_note( $note );
            $order->save();
            Journal::event( sprintf( 'Refund OK order #%d, amount %s %s', (int) $order_id, $amount, $order->get_currency() ) );
            return true;
        }

        $err = (string) ( $result['error'] ?? __( 'Unknown error', 'pay4all-for-mypos' ) );
        $order->add_order_note( sprintf(
            /* translators: %s = error message returned by myPOS */
            __( 'Pay4All for myPOS: refund failed (%s).', 'pay4all-for-mypos' ),
            $err
        ) );
        $order->save();
        Journal::error( sprintf( 'Refund FAILED order #%d: %s', (int) $order_id, $err ) );
        return new \WP_Error( 'pay4all_for_mypos_refund_failed', $err );
    }
}
