<?php
/**
 * Renders the TWINT refund panel inline on the Pay4All Form order detail
 * screen - hooked to `p4all_order_after_items` (fired right after the
 * "Ordered items" card) so the refund UI sits next to the order recap.
 * The panel is only emitted when the order's payment_method is 'twint_auto'
 * - non-TWINT orders see nothing at all.
 *
 *   POST /wp-admin/admin-post.php?action=p4all_twint_form_refund
 *     Body: order_id, amount (optional decimal in the order currency),
 *           reason (optional free-text), _wpnonce
 *
 * Refunds are executed via FormTwintCore::create_for_admin() +
 * BackofficeTransactionService::create_refund() - the SOAP path already used
 * by the WooCommerce TWINT gateway in "direct" (SOAP) mode. The equivalent
 * REST path (WalletRequest::refund_transaction) is left unused because form
 * orders in this codebase are always processed in SOAP mode via FormTwintCore.
 */

namespace Twnt4All\Pay4All;

use Twnt4All\Checkout\OrderDataRepository;

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

final class FormOrderRefund
{
    public const ACTION_SLUG = 'p4all_twint_form_refund';
    public const NONCE_KEY   = 'p4all_twint_form_refund_nonce';

    /** Method key used by Pay4All Form for TWINT-paid orders. */
    private const METHOD_KEY = 'twint_auto';

    /**
     * Post meta storing the refund history. Shape:
     *   [ [ id => 'tx_xxx', amount_cents => 500, reason => '...', created_at => 1719800000 ], ... ]
     */
    public const META_REFUNDS_HISTORY = '_p4all_twint_refunds';

    public static function register() : void
    {
        add_action( 'p4all_order_after_items',         [ self::class, 'render_after_items' ], 10, 3 );
        add_action( 'admin_post_' . self::ACTION_SLUG, [ self::class, 'handle_refund' ] );
        add_action( 'admin_notices',                   [ self::class, 'maybe_print_notice' ] );
        // Inline JS: builds a real <form> at click time and attaches it to
        // <body> - see the pay4all-stripe FormOrderRefund for the rationale
        // (WP wraps the detail view in <form id="post"> and nested forms are
        // silently stripped by the HTML5 parser).
        add_action( 'admin_footer', [ self::class, 'print_footer_js' ] );
    }

    /**
     * @param array<string,mixed> $totals
     * @param array<string,mixed> $payment
     */
    public static function render_after_items( \WP_Post $post, array $totals = [], array $payment = [] ) : void
    {
        $order_id = (int) $post->ID;
        if ( $payment === [] ) {
            $payment = (array) get_post_meta( $order_id, '_p4all_payment', true );
        }
        $method = (string) ( $payment['method'] ?? '' );

        if ( $method !== self::METHOD_KEY ) {
            return;
        }

        if ( $totals === [] ) {
            $totals = (array) get_post_meta( $order_id, '_p4all_totals', true );
        }

        $payment_state = (string) ( $payment['status'] ?? '' );
        $total         = (float) ( $totals['total'] ?? 0 );
        $currency_raw  = $totals['currency'] ?? 'CHF';
        if ( is_array( $currency_raw ) ) { $currency_raw = (string) ( $currency_raw['code'] ?? 'CHF' ); }
        $currency      = strtoupper( (string) $currency_raw );

        $refunds        = self::get_refunds( $order_id );
        $total_cents    = (int) round( $total * 100 );
        $refunded_cents = self::sum_refunded_cents( $refunds );
        $remaining_cents = max( 0, $total_cents - $refunded_cents );

        $shim     = new FormOrderShim( $order_id );
        $order_uuid = self::resolve_order_uuid( $order_id, $shim );

        echo '<div class="p4all-card" style="margin-top:16px;">';
        echo '<h3>' . esc_html__( 'TWINT refund', 'pay4all-for-twint' ) . '</h3>';

        if ( ! empty( $refunds ) ) {
            self::render_refund_history( $refunds, $currency, $total_cents, $refunded_cents, $remaining_cents );
        }

        if ( $payment_state === 'refunded' ) {
            echo '<p style="color:#6e6e6e;margin-top:0;"><strong>&#10004;</strong> '
                . esc_html__( 'This order has been fully refunded.', 'pay4all-for-twint' )
                . '</p>';
            echo '</div>';
            return;
        }

        if ( ! in_array( $payment_state, [ 'paid', 'partially_refunded' ], true ) ) {
            echo '<p class="description">'
                . esc_html__( 'A TWINT refund can only be issued on an order whose payment status is "paid" or "partially refunded".', 'pay4all-for-twint' )
                . '</p>';
            echo '</div>';
            return;
        }

        if ( $order_uuid === '' ) {
            echo '<p class="description" style="color:#a00;">'
                . esc_html__( 'Cannot refund: no TWINT transaction ID is stored on this order. Issue the refund from the merchant TWINT app.', 'pay4all-for-twint' )
                . '</p>';
            echo '</div>';
            return;
        }

        if ( $remaining_cents <= 0 ) {
            echo '<p class="description">'
                . esc_html__( 'The full order amount has already been refunded.', 'pay4all-for-twint' )
                . '</p>';
            echo '</div>';
            return;
        }

        $remaining_display = number_format( $remaining_cents / 100, 2, '.', '' );
        ?>
        <p class="description">
            <?php
            printf(
                /* translators: 1: remaining refundable amount (formatted), 2: currency code */
                esc_html__( 'The refund is sent to TWINT. Leave the field empty to refund the remaining amount (%1$s %2$s), or enter a smaller amount.', 'pay4all-for-twint' ),
                esc_html( $remaining_display ),
                esc_html( $currency )
            );
            ?>
        </p>
        <div class="p4all-twint-refund-container"
             data-order-id="<?php echo esc_attr( (string) $order_id ); ?>"
             data-nonce="<?php echo esc_attr( wp_create_nonce( self::NONCE_KEY ) ); ?>">
            <p>
                <label for="p4all_twint_refund_amount"><strong><?php esc_html_e( 'Amount to refund', 'pay4all-for-twint' ); ?></strong></label>
                <input type="number"
                       step="0.01"
                       min="0.01"
                       max="<?php echo esc_attr( $remaining_display ); ?>"
                       id="p4all_twint_refund_amount"
                       class="widefat p4all-twint-refund-amount"
                       placeholder="<?php echo esc_attr( sprintf( '%s (%s)', $remaining_display, __( 'remaining', 'pay4all-for-twint' ) ) ); ?>">
            </p>
            <p>
                <label for="p4all_twint_refund_reason"><strong><?php esc_html_e( 'Reason (optional)', 'pay4all-for-twint' ); ?></strong></label>
                <input type="text"
                       id="p4all_twint_refund_reason"
                       maxlength="200"
                       class="widefat p4all-twint-refund-reason"
                       placeholder="<?php esc_attr_e( 'e.g. damaged item, customer cancellation...', 'pay4all-for-twint' ); ?>">
            </p>
            <p>
                <button type="button" class="button button-primary p4all-twint-refund-btn" style="width:100%;">
                    <?php esc_html_e( 'Refund via TWINT', 'pay4all-for-twint' ); ?>
                </button>
            </p>
            <p class="description" style="font-size:.85em;color:#666;">
                <?php esc_html_e( 'TWINT transaction:', 'pay4all-for-twint' ); ?>
                <code style="font-size:.9em;"><?php echo esc_html( $order_uuid ); ?></code>
            </p>
        </div>
        <?php
        echo '</div>';
    }

    private static function render_refund_history( array $refunds, string $currency, int $total_cents, int $refunded_cents, int $remaining_cents ) : void
    {
        $fmt = static function ( int $cents ) use ( $currency ) : string {
            return number_format( $cents / 100, 2, '.', '' ) . ' ' . $currency;
        };
        ?>
        <div style="margin-bottom:12px;">
            <h4 style="margin:0 0 6px;"><?php esc_html_e( 'Refund history', 'pay4all-for-twint' ); ?></h4>
            <table class="widefat striped" style="font-size:.85em;">
                <thead>
                    <tr>
                        <th><?php esc_html_e( 'Date', 'pay4all-for-twint' ); ?></th>
                        <th style="text-align:right;"><?php esc_html_e( 'Amount', 'pay4all-for-twint' ); ?></th>
                        <th><?php esc_html_e( 'Reason', 'pay4all-for-twint' ); ?></th>
                    </tr>
                </thead>
                <tbody>
                <?php foreach ( array_reverse( $refunds ) as $r ) :
                    $ts     = (int) ( $r['created_at']   ?? 0 );
                    $amount = (int) ( $r['amount_cents'] ?? 0 );
                    $reason = (string) ( $r['reason']    ?? '' );
                    ?>
                    <tr>
                        <td><?php echo $ts > 0 ? esc_html( wp_date( 'd.m.Y H:i', $ts ) ) : '-'; ?></td>
                        <td style="text-align:right;white-space:nowrap;"><?php echo esc_html( $fmt( $amount ) ); ?></td>
                        <td><?php echo $reason !== '' ? esc_html( $reason ) : '<span style="color:#999;">-</span>'; ?></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
                <tfoot>
                    <tr>
                        <th colspan="2"><?php esc_html_e( 'Total refunded', 'pay4all-for-twint' ); ?></th>
                        <td style="text-align:right;white-space:nowrap;"><strong><?php echo esc_html( $fmt( $refunded_cents ) ); ?></strong></td>
                    </tr>
                    <tr>
                        <td colspan="3" class="description" style="color:#666;font-style:italic;">
                            <?php
                            printf(
                                /* translators: 1: remaining refundable amount formatted, 2: total amount formatted */
                                esc_html__( '%1$s remaining of %2$s', 'pay4all-for-twint' ),
                                esc_html( $fmt( $remaining_cents ) ),
                                esc_html( $fmt( $total_cents ) )
                            );
                            ?>
                        </td>
                    </tr>
                </tfoot>
            </table>
        </div>
        <?php
    }

    /**
     * Resolves the TWINT order UUID for a form order.
     *
     * The UUID is the identifier required by TWINT's SOAP backoffice refund
     * (BackofficeTransactionService::create_refund -> reversal). It is written
     * to `_t4a_twint_order[order_uuid]` by TransactionService::start_order()
     * at payment time.
     */
    private static function resolve_order_uuid( int $order_id, FormOrderShim $shim ) : string
    {
        if ( ! $shim->is_loaded() ) {
            return '';
        }

        $data = OrderDataRepository::get_order_data( $shim );
        if ( is_array( $data ) && ! empty( $data['order_uuid'] ) ) {
            return (string) $data['order_uuid'];
        }

        return '';
    }

    /**
     * @return array<int,array{id:string,amount_cents:int,reason:string,created_at:int}>
     */
    public static function get_refunds( int $order_id ) : array
    {
        $raw = get_post_meta( $order_id, self::META_REFUNDS_HISTORY, true );
        return is_array( $raw ) ? array_values( $raw ) : [];
    }

    public static function sum_refunded_cents( array $refunds ) : int
    {
        $sum = 0;
        foreach ( $refunds as $r ) {
            $sum += (int) ( $r['amount_cents'] ?? 0 );
        }
        return $sum;
    }

    public static function handle_refund() : void
    {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( esc_html__( 'Permission denied.', 'pay4all-for-twint' ), 403 );
        }
        $nonce = isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( (string) $_REQUEST['_wpnonce'] ) ) : '';
        if ( ! wp_verify_nonce( $nonce, self::NONCE_KEY ) ) {
            wp_die( esc_html__( 'Invalid nonce.', 'pay4all-for-twint' ), 400 );
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
        $order_id = isset( $_POST['order_id'] ) ? (int) $_POST['order_id'] : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
        $amount_raw = isset( $_POST['amount'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['amount'] ) ) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
        $reason = isset( $_POST['reason'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['reason'] ) ) : '';

        $order = $order_id > 0 ? get_post( $order_id ) : null;
        if ( ! $order || $order->post_type !== 'p4all_order' ) {
            wp_die( esc_html__( 'Order not found.', 'pay4all-for-twint' ), 404 );
        }

        $shim = new FormOrderShim( $order_id );
        if ( ! $shim->is_loaded() ) {
            wp_die( esc_html__( 'Order not found.', 'pay4all-for-twint' ), 404 );
        }

        // Register the shim in the FormOrderRegistry so
        // OrderDataRepository::get/save_order_data() (called from inside the
        // SOAP TransactionService) resolve to the form order rather than
        // falling back to wc_get_order() (which returns null here).
        FormOrderRegistry::register( $shim );

        $order_uuid = self::resolve_order_uuid( $order_id, $shim );
        if ( $order_uuid === '' ) {
            self::flash_notice( 'error', __( 'No TWINT transaction ID found on this order.', 'pay4all-for-twint' ) );
            self::redirect_back( $order_id );
        }

        $totals          = $shim->get_meta( '_p4all_totals', true );
        $totals          = is_array( $totals ) ? $totals : [];
        $total_cents     = (int) round( ( (float) ( $totals['total'] ?? 0 ) ) * 100 );
        $existing        = self::get_refunds( $order_id );
        $already_refunded_cents = self::sum_refunded_cents( $existing );
        $remaining_cents = max( 0, $total_cents - $already_refunded_cents );

        if ( $remaining_cents <= 0 ) {
            self::flash_notice( 'error', __( 'The full amount has already been refunded.', 'pay4all-for-twint' ) );
            self::redirect_back( $order_id );
        }

        // Empty amount -> refund the rest; otherwise clamp.
        if ( $amount_raw === '' || (float) $amount_raw <= 0 ) {
            $amount_cents = $remaining_cents;
        } else {
            $amount_cents = (int) round( (float) $amount_raw * 100 );
            if ( $amount_cents > $remaining_cents ) {
                $amount_cents = $remaining_cents;
            }
        }

        // TWINT SOAP refund via BackofficeTransactionService::create_refund.
        // The service pulls the order UUID + currency + reference from the
        // FormConfigProvider (via FormTwintCore) - no extra plumbing needed.
        $amount_decimal = number_format( $amount_cents / 100, 2, '.', '' );

        if ( ! defined( 'T4A_MERCHANT_UUID' ) || ! T4A_MERCHANT_UUID ) {
            self::flash_notice( 'error', __( 'Missing configuration: TWINT merchant ID (T4A_MERCHANT_UUID) is not defined.', 'pay4all-for-twint' ) );
            self::redirect_back( $order_id );
        }

        try {
            $twint = FormTwintCore::create_for_admin( $order_id, T4A_MERCHANT_UUID );
            if ( ! $twint ) {
                self::flash_notice( 'error', __( 'Unable to initialize the TWINT SOAP client. Check the certificate and password.', 'pay4all-for-twint' ) );
                self::redirect_back( $order_id );
            }
            $refund_result = $twint->transactionHandler->create_refund( $amount_decimal );
        } catch ( \Throwable $e ) {
            self::add_admin_note( $order_id, sprintf(
                /* translators: %s: PHP exception message */
                __( 'TWINT refund failed (exception): %s', 'pay4all-for-twint' ),
                $e->getMessage()
            ) );
            self::flash_notice( 'error', sprintf(
                /* translators: %s: PHP exception message */
                __( 'TWINT refund failed (%s).', 'pay4all-for-twint' ),
                $e->getMessage()
            ) );
            self::redirect_back( $order_id );
        }

        if ( ! $refund_result ) {
            self::flash_notice( 'error', __( 'TWINT refund failed. Check the Pay4All log for details.', 'pay4all-for-twint' ) );
            self::redirect_back( $order_id );
        }

        // create_refund returns [ 'operation' => 'REVERSAL', 'amount' => ..., 'reference' => ... ]
        $refund_id = is_array( $refund_result ) && ! empty( $refund_result['reference'] )
            ? (string) $refund_result['reference']
            : 'twint_' . wp_generate_uuid4();

        $existing[] = [
            'id'           => $refund_id,
            'amount_cents' => $amount_cents,
            'reason'       => $reason,
            'created_at'   => time(),
        ];
        update_post_meta( $order_id, self::META_REFUNDS_HISTORY, $existing );

        $new_refunded_cents = $already_refunded_cents + $amount_cents;
        $is_now_full        = $new_refunded_cents >= $total_cents;

        if ( class_exists( '\\Pay4All\\Form\\Domain\\Order' ) ) {
            \Pay4All\Form\Domain\Order::update_payment_status(
                $order_id,
                $is_now_full ? 'refunded' : 'partially_refunded'
            );
        }

        $currency_str = $totals['currency'] ?? 'CHF';
        if ( is_array( $currency_str ) ) { $currency_str = (string) ( $currency_str['code'] ?? 'CHF' ); }
        $amount_str = number_format( $amount_cents / 100, 2, '.', '' )
            . ' ' . strtoupper( (string) $currency_str );
        $note = $reason !== ''
            ? sprintf(
                /* translators: 1: TWINT refund id, 2: refund amount, 3: merchant reason */
                __( 'TWINT refund %1$s - %2$s - reason: %3$s', 'pay4all-for-twint' ),
                $refund_id,
                $amount_str,
                $reason
            )
            : sprintf(
                /* translators: 1: TWINT refund id, 2: refund amount */
                __( 'TWINT refund %1$s - %2$s.', 'pay4all-for-twint' ),
                $refund_id,
                $amount_str
            );
        self::add_admin_note( $order_id, $note );

        $success_msg = $is_now_full
            ? __( 'Refund completed. The order is now fully refunded.', 'pay4all-for-twint' )
            : sprintf(
                /* translators: %s: amount + currency (e.g. "15.00 CHF") */
                __( 'Refund completed successfully (%s).', 'pay4all-for-twint' ),
                $amount_str
            );
        self::flash_notice( 'success', $success_msg );
        self::redirect_back( $order_id );
    }

    public static function print_footer_js() : void
    {
        $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
        if ( ! $screen instanceof \WP_Screen ) {
            return;
        }
        if ( $screen->post_type !== 'p4all_order' ) {
            return;
        }

        $action_url  = admin_url( 'admin-post.php' );
        $action      = self::ACTION_SLUG;
        $confirm_msg = __( 'Confirm the refund? This action is irreversible on the TWINT side.', 'pay4all-for-twint' );

        // The JS is a small imperative wiring for the refund button ; expose
        // its runtime parameters via wp_localize_script + attach the code as
        // an inline script through the proper WordPress enqueue pipeline.
        $handle = 'pay4all-for-twint-form-refund';
        if ( ! wp_script_is( $handle, 'registered' ) ) {
            wp_register_script( $handle, false, array(), T4A_PLUGIN_VERSION, true );
        }
        wp_enqueue_script( $handle );
        wp_localize_script( $handle, 'p4allTwintRefund', array(
            'actionUrl'  => $action_url,
            'action'     => $action,
            'confirmMsg' => $confirm_msg,
        ) );
        wp_add_inline_script( $handle, self::refund_button_js() );
    }

    private static function refund_button_js() : string
    {
        return <<<'JS'
(function(){
    var CFG       = window.p4allTwintRefund || {};
    var container = document.querySelector('.p4all-twint-refund-container');
    if (!container) return;
    var btn = container.querySelector('.p4all-twint-refund-btn');
    if (!btn) return;
    btn.addEventListener('click', function(){
        if (!window.confirm(CFG.confirmMsg || '')) return;
        var form = document.createElement('form');
        form.method = 'POST';
        form.action = CFG.actionUrl || '';
        form.style.display = 'none';
        var fields = {
            action:   CFG.action || '',
            _wpnonce: container.getAttribute('data-nonce') || '',
            order_id: container.getAttribute('data-order-id') || '',
            amount:   (container.querySelector('.p4all-twint-refund-amount') || {}).value || '',
            reason:   (container.querySelector('.p4all-twint-refund-reason') || {}).value || ''
        };
        Object.keys(fields).forEach(function(k){
            var i = document.createElement('input');
            i.type  = 'hidden';
            i.name  = k;
            i.value = fields[k];
            form.appendChild(i);
        });
        document.body.appendChild(form);
        form.submit();
    });
})();
JS;
    }

    private static function add_admin_note( int $order_id, string $content ) : void
    {
        wp_insert_comment( [
            'comment_post_ID'  => $order_id,
            'comment_author'   => 'Pay4All TWINT',
            'comment_content'  => $content,
            'comment_type'     => 'p4all_twint_note',
            'comment_approved' => 1,
        ] );
    }

    private static function flash_notice( string $type, string $message ) : void
    {
        set_transient( 'p4all_twint_form_refund_notice_' . get_current_user_id(), [
            'type'    => $type,
            'message' => $message,
        ], 30 );
    }

    private static function redirect_back( int $order_id ) : void
    {
        wp_safe_redirect( admin_url( 'post.php?post=' . $order_id . '&action=edit' ) );
        exit;
    }

    public static function maybe_print_notice() : void
    {
        // Guideline 11 : only show the refund flash on Pay4All order screens,
        // never on other admin pages. Bail before touching the transient so an
        // orphan flash from another screen can't be silently consumed.
        $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
        if ( ! $screen instanceof \WP_Screen || $screen->post_type !== 'p4all_order' ) {
            return;
        }
        $key = 'p4all_twint_form_refund_notice_' . get_current_user_id();
        $notice = get_transient( $key );
        if ( ! is_array( $notice ) || empty( $notice['message'] ) ) {
            return;
        }
        delete_transient( $key );
        $class = ( $notice['type'] ?? 'success' ) === 'error' ? 'notice-error' : 'notice-success';
        ?>
        <div class="notice <?php echo esc_attr( $class ); ?> is-dismissible">
            <p><?php echo esc_html( (string) $notice['message'] ); ?></p>
        </div>
        <?php
    }
}
