<?php

namespace Pay4AllForMypos\Api;

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

defined( 'ABSPATH' ) || exit;

/**
 * Thin HTTP + signature client for OUTBOUND myPOS IPC calls (currently only
 * IPCRefund - the customer-facing IPCPurchase flow is a browser redirect
 * handled by IpcFormBuilder, not this class).
 *
 * The myPOS spec has no public PHP SDK so we speak the IPC protocol on the
 * wire directly with wp_remote_post + openssl_sign, using the merchant's
 * private key (PEM) taken from their Configuration Pack.
 *
 * All signatures follow the myPOS convention:
 *   base64( openssl_sign( base64( implode('-', $ordered_values) ), PK, SHA256 ) )
 */
class IpcClient
{
    /** 'live' or 'test'. */
    private string $mode;

    /** Endpoint URL derived from $mode. */
    private string $endpoint;

    public function __construct( string $mode )
    {
        $this->mode     = ( $mode === 'live' ) ? 'live' : 'test';
        $this->endpoint = $this->mode === 'live'
            ? PAY4ALL_FOR_MYPOS_IPC_URL_LIVE
            : PAY4ALL_FOR_MYPOS_IPC_URL_TEST;
    }

    /**
     * POST an IPCRefund request signed with the merchant's private key.
     *
     * @param string $trnref   myPOS transaction reference to refund
     *                         (saved on the WC order as _pay4all_for_mypos_trnref).
     * @param float  $amount   Refund amount, decimal in the order's currency.
     * @param string $currency ISO 4217 code (BGN, USD, EUR, ...).
     *
     * @return array{ok:bool,status:int,error:?string,trnref:?string}
     *         `status` is the myPOS Status integer (0 = success, > 0 = error).
     *         `trnref` is the refund transaction reference on success.
     */
    public function refund( string $trnref, float $amount, string $currency ) : array
    {
        $sid  = ConfigPage::get_pack_field( $this->mode, 'sid' );
        $cn   = ConfigPage::get_pack_field( $this->mode, 'cn' );
        $idx  = ConfigPage::get_pack_field( $this->mode, 'idx' );

        if ( $sid === '' || $cn === '' || $idx === '' ) {
            return [ 'ok' => false, 'status' => -1, 'error' => 'missing myPOS credentials', 'trnref' => null ];
        }
        if ( $trnref === '' || $amount <= 0 || $currency === '' ) {
            return [ 'ok' => false, 'status' => -1, 'error' => 'invalid refund parameters', 'trnref' => null ];
        }

        // Ordered payload - myPOS' signature scheme concatenates values in the
        // exact key order below, so we must not reorder them.
        $lang = defined( 'ICL_LANGUAGE_CODE' ) ? ICL_LANGUAGE_CODE : substr( (string) get_locale(), 0, 2 );
        $body = [
            'IPCmethod'    => 'IPCRefund',
            'IPCVersion'   => '1.4',
            'IPCLanguage'  => $lang !== '' ? $lang : 'en',
            'SID'          => $sid,
            'WalletNumber' => $cn,
            'KeyIndex'     => $idx,
            'Trn_ref'      => $trnref,
            'Amount'       => number_format( $amount, 2, '.', '' ),
            'Currency'     => strtoupper( $currency ),
            'OutputFormat' => 'json',
        ];

        $body['Signature'] = $this->sign( $body );

        $response = wp_remote_post( $this->endpoint, [
            'body'       => $body,
            'timeout'    => 30,
            'blocking'   => true,
            'sslverify'  => true,
            'user-agent' => 'Pay4All-for-myPOS/' . PAY4ALL_FOR_MYPOS_PLUGIN_VERSION,
        ] );

        if ( is_wp_error( $response ) ) {
            $err = $response->get_error_message();
            Journal::error( 'IPCRefund HTTP error: ' . $err );
            return [ 'ok' => false, 'status' => -1, 'error' => $err, 'trnref' => null ];
        }

        $raw    = (string) wp_remote_retrieve_body( $response );
        $parsed = json_decode( $raw, true );

        // Some myPOS deployments return URL-encoded key=value pairs when the
        // OutputFormat header is not honoured. Fall back to parse_str() so we
        // can still read the Status field in that case.
        if ( ! is_array( $parsed ) ) {
            $parsed = [];
            parse_str( $raw, $parsed );
        }

        $status_raw = $parsed['Status'] ?? null;
        $status_int = is_numeric( $status_raw ) ? (int) $status_raw : -1;
        $status_msg = isset( $parsed['StatusMsg'] ) ? (string) $parsed['StatusMsg'] : '';
        $ref        = isset( $parsed['IPC_Trnref'] ) ? (string) $parsed['IPC_Trnref'] : null;

        if ( $status_int === 0 ) {
            return [ 'ok' => true, 'status' => 0, 'error' => null, 'trnref' => $ref ];
        }

        Journal::error( sprintf( 'IPCRefund failed (status=%d, msg=%s)', $status_int, $status_msg ) );
        return [
            'ok'     => false,
            'status' => $status_int,
            'error'  => $status_msg !== '' ? $status_msg : ( 'HTTP ' . (int) wp_remote_retrieve_response_code( $response ) ),
            'trnref' => $ref,
        ];
    }

    /**
     * Build the myPOS signature for a set of ORDERED fields.
     *
     * Signature scheme, per the myPOS Virtual Merchant Portal spec:
     *   1. concatenate all field VALUES (in the order they appear in the
     *      request payload) with a "-" separator
     *   2. base64-encode the resulting string
     *   3. openssl_sign it with the merchant's PEM private key using SHA256
     *   4. base64-encode the raw signature
     *
     * @param array<string,scalar> $ordered_fields
     */
    private function sign( array $ordered_fields ) : string
    {
        $pk_pem = ConfigPage::get_pack_field( $this->mode, 'pk' );
        if ( $pk_pem === '' ) {
            Journal::error( 'IPCRefund signature: private key missing from Pack' );
            return '';
        }

        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- required by myPOS signature scheme
        $conc = base64_encode( implode( '-', array_values( $ordered_fields ) ) );

        $pk = openssl_pkey_get_private( $pk_pem );
        if ( ! $pk ) {
            Journal::error( 'IPCRefund signature: openssl_pkey_get_private() failed' );
            return '';
        }

        $signature = '';
        $ok        = openssl_sign( $conc, $signature, $pk, OPENSSL_ALGO_SHA256 );
        if ( ! $ok || $signature === '' ) {
            Journal::error( 'IPCRefund signature: openssl_sign() failed' );
            return '';
        }

        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- required by myPOS signature scheme
        return base64_encode( $signature );
    }
}
