<?php
/**
 * Authorize.net API Helper Class
 *
 * Provides helper methods for interacting with the Authorize.net API,
 * including configuration setup based on payment type.
 *
 * @package Authorize_Net_Payments_For_Woocommerce
 * @subpackage Authorize_Net_Payments_For_Woocommerce/includes
 */

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

use net\authorize\api\contract\v1 as AnetAPI;
use net\authorize\api\controller as AnetController;

/**
 * Class Anet_Api_Helper
 * 
 * Helper class for interacting with the Authorize.net API.
 */
class Anet_Api_Helper
{
    const ANET_MASK_VALUE = 'XXXX';
    /**
     * Stores the payment settings based on the selected payment type.
     *
     * @var array
     */
    private $settings;
    /**
     * Constructor to initialize payment settings.
     *
     * @param string $type The type of payment (e.g., credit card or eCheck).
     */
    public function __construct($type)
    {
        $this->settings = (ANET_PAYMENT_TYPE_CREDIT_CARD === $type)
            ? Anet_Setting_Helper::get_cc_settings()
            : Anet_Setting_Helper::get_echeck_settings();
    }
    /**
     * Returns merchant authentication details.
     *
     * @return AnetAPI\MerchantAuthenticationType
     */
    public function merchant_authentication_request()
    {
        $merchant_auth_request = new AnetAPI\MerchantAuthenticationType();
        $merchant_auth_request->setName($this->settings['login_id']);
        $merchant_auth_request->setTransactionKey($this->settings['transaction_key']);
        return $merchant_auth_request;
    }
    /**
     * Builds and returns a transaction request using the provided payment arguments.
     *
     * @param array $payment_args Payment details for the transaction.
     * @return AnetAPI\TransactionRequestType
     */
    public function transaction_request($payment_args)
    {
        $transaction_request = new AnetAPI\TransactionRequestType();
        if (!empty($payment_args['transactionRequest']['transactionType'])) {
            $transaction_request->setTransactionType($payment_args['transactionRequest']['transactionType']);
        }
        if (!empty($payment_args['transactionRequest']['amount'])) {
            $transaction_request->setAmount($payment_args['transactionRequest']['amount']);
        }
        if (!empty($payment_args['transactionRequest']['currencyCode'])) {
            $transaction_request->setCurrencyCode($payment_args['transactionRequest']['currencyCode']);
        }
        //Solution id.
        $transaction_request->setSolution($this->get_solution_id());
        // Add order details.
        if (isset($payment_args['transactionRequest']['order'])) {
            $order_details = new AnetAPI\OrderType();
            $order_details->setInvoiceNumber($payment_args['transactionRequest']['order']['invoiceNumber']);
            $transaction_request->setOrder($order_details);
        }
        // Add tax details.
        if (isset($payment_args['transactionRequest']['tax']['amount'])) {
            $tax = new AnetAPI\ExtendedAmountType();
            $tax->setAmount($payment_args['transactionRequest']['tax']['amount']);
            $transaction_request->setTax($tax);
        }
        // Add shipping details.
        if (isset($payment_args['transactionRequest']['shipping']['amount'])) {
            $shipping = new AnetAPI\ExtendedAmountType();
            $shipping->setAmount($payment_args['transactionRequest']['shipping']['amount']);
            $transaction_request->setShipping($shipping);
        }
        // Add customer details.
        if (isset($payment_args['transactionRequest']['customer'])) {
            $customer = new AnetAPI\CustomerDataType();
            $customer->setId($payment_args['transactionRequest']['customer']['id']);
            $customer->setEmail($payment_args['transactionRequest']['customer']['email']);
            $transaction_request->setCustomer($customer);
        }
        // Add line items.
        if (isset($payment_args['transactionRequest']['lineItems'])) {
            $line_items = []; // Initialize an array to hold LineItemType objects.
            foreach ($payment_args['transactionRequest']['lineItems'] as $line_item) {
                $line_item_obj = new AnetAPI\LineItemType();
                $line_item_obj->setItemId($line_item['itemId']);
                $line_item_obj->setName($line_item['name']);
                $line_item_obj->setDescription($line_item['description']);
                $line_item_obj->setQuantity($line_item['quantity']);
                $line_item_obj->setUnitPrice($line_item['unitPrice']);
                $line_item_obj->setTaxable($line_item['taxable']);
                $line_item_obj->setTotalAmount($line_item['totalAmount']);
                $line_items[] = $line_item_obj; // Add each LineItemType object to the array.
            }
            $transaction_request->setLineItems($line_items); // Set the array of line items.
        }
        // Add transaction settings.
        if (isset($payment_args['transactionRequest']['transactionSettings']['setting'])) {
            $settings = []; // Initialize an array to hold SettingType objects.
            foreach ($payment_args['transactionRequest']['transactionSettings']['setting'] as $setting) {
                $transaction_setting = new AnetAPI\SettingType();
                $transaction_setting->setSettingName($setting['settingName']);
                $transaction_setting->setSettingValue($setting['settingValue']);
                $settings[] = $transaction_setting; // Add each SettingType object to the array.
            }
            $transaction_request->setTransactionSettings($settings); // Set the array of settings.
        }
        // Add customer IP address.
        if (!empty($_SERVER['REMOTE_ADDR'])) {
            $ip_address = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']));
            $transaction_request->setCustomerIP($ip_address);
        }
        // Add user fields.
        if (isset($payment_args['transactionRequest']['userFields']['userField'])) {
            $user_fields = []; // Initialize an array to hold userField objects.
            foreach ($payment_args['transactionRequest']['userFields']['userField'] as $user_field) {
                $user_field_obj = new AnetAPI\UserFieldType();
                $user_field_obj->setName($user_field['name']);
                $user_field_obj->setValue($user_field['value']);
                $user_fields[] = $user_field_obj; // Add each userField object to the array.
            }
            $transaction_request->setUserFields($user_fields); // Set the array of user fields.
        }
        return $transaction_request;
    }
    /**
     * Builds and returns a complete transaction request with payment, billing, and shipping details.
     *
     * @param array $payment_args Payment arguments for the transaction.
     * @return AnetAPI\TransactionRequestType
     */
    public function build_transaction_request($payment_args)
    {
        $transaction_request = $this->transaction_request($payment_args);
        //Payment request.
        $payment = new AnetAPI\PaymentType();
        if (isset($payment_args['transactionRequest']['payment']['creditCard'])) {
            $credit_card = new AnetAPI\CreditCardType();
            $credit_card->setCardNumber($payment_args['transactionRequest']['payment']['creditCard']['cardNumber']);
            $credit_card->setExpirationDate($payment_args['transactionRequest']['payment']['creditCard']['expirationDate']);
            $credit_card->setCardCode($payment_args['transactionRequest']['payment']['creditCard']['cardCode']);
            $payment->setCreditCard($credit_card);
        } elseif (isset($payment_args['transactionRequest']['payment']['opaqueData'])) {
            $opaque_data = new AnetAPI\OpaqueDataType();
            $opaque_data->setDataDescriptor($payment_args['transactionRequest']['payment']['opaqueData']['dataDescriptor']);
            $opaque_data->setDataValue($payment_args['transactionRequest']['payment']['opaqueData']['dataValue']);
            $payment->setOpaqueData($opaque_data);
        } elseif (isset($payment_args['transactionRequest']['payment']['bankAccount'])) {
            $bank_data = new AnetAPI\BankAccountType();
            $bank_data->setAccountType($payment_args['transactionRequest']['payment']['bankAccount']['accountType']);
            $bank_data->setRoutingNumber($payment_args['transactionRequest']['payment']['bankAccount']['routingNumber']);
            $bank_data->setAccountNumber($payment_args['transactionRequest']['payment']['bankAccount']['accountNumber']);
            $bank_data->setNameOnAccount($payment_args['transactionRequest']['payment']['bankAccount']['nameOnAccount']);
            $payment->setBankAccount($bank_data);
        }
        $transaction_request->setPayment($payment);
        // Add billing address.
        if (isset($payment_args['transactionRequest']['billTo'])) {
            $billing_address = new AnetAPI\CustomerAddressType();
            $billing_address->setFirstName($payment_args['transactionRequest']['billTo']['firstName']);
            $billing_address->setLastName($payment_args['transactionRequest']['billTo']['lastName']);
            $billing_address->setAddress($payment_args['transactionRequest']['billTo']['address']);
            $billing_address->setCity($payment_args['transactionRequest']['billTo']['city']);
            $billing_address->setState($payment_args['transactionRequest']['billTo']['state']);
            $billing_address->setZip($payment_args['transactionRequest']['billTo']['zip']);
            $billing_address->setCountry($payment_args['transactionRequest']['billTo']['country']);
            $billing_address->setPhoneNumber($payment_args['transactionRequest']['billTo']['phoneNumber']);
            $transaction_request->setBillTo($billing_address);
        }
        // Add shipping address.
        if (isset($payment_args['transactionRequest']['shipTo'])) {
            $shipping_address = new AnetAPI\NameAndAddressType();
            $shipping_address->setFirstName($payment_args['transactionRequest']['shipTo']['firstName']);
            $shipping_address->setLastName($payment_args['transactionRequest']['shipTo']['lastName']);
            $shipping_address->setAddress($payment_args['transactionRequest']['shipTo']['address']);
            $shipping_address->setCity($payment_args['transactionRequest']['shipTo']['city']);
            $shipping_address->setState($payment_args['transactionRequest']['shipTo']['state']);
            $shipping_address->setZip($payment_args['transactionRequest']['shipTo']['zip']);
            $shipping_address->setCountry($payment_args['transactionRequest']['shipTo']['country']);
            $transaction_request->setShipTo($shipping_address);
        }
        return $transaction_request;
    }
    /**
     * Builds and returns a payment object using opaque data.
     *
     * @param array $payment_args Payment arguments containing opaque data.
     * @return AnetAPI\PaymentType
     */
    public function opaque_data($payment_args)
    {
        $opaque_data = new AnetAPI\OpaqueDataType();
        $opaque_data->setDataDescriptor($payment_args['opaqueData']['dataDescriptor']);
        $opaque_data->setDataValue($payment_args['opaqueData']['dataValue']);
        $payment = new AnetAPI\PaymentType();
        $payment->setOpaqueData($opaque_data);
        return $payment;
    }
    /**
     * Returns the solution object for API requests.
     *
     * @return AnetAPI\SolutionType The solution object with appropriate ID.
     */
    public function get_solution_id()
    {
        $solution = new AnetAPI\SolutionType();
        $solution_id = $this->settings['testmode'] ? ANET_SANDBOX_SOLUTION_ID : ANET_PRODUCTION_SOLUTION_ID;
        $solution->setId($solution_id);
        return $solution;
    }
    /**
     * Creates and returns a customer profile request using user ID and payment details.
     *
     * @param int   $user_id      The WooCommerce user ID.
     * @param array $payment_args Payment arguments including email and opaque data.
     * @return AnetAPI\CustomerProfileType
     */
    public function create_customer_profile_request($user_id, $payment_args)
    {
        $customer_profile = new AnetAPI\CustomerProfileType();
        $customer_profile->setMerchantCustomerId($user_id);
        $customer_profile->setEmail($payment_args['email']);
        $customer_profile->setDescription('WooCommerce Customer Profile');
        $payment = $this->opaque_data($payment_args);
        $payment_profile = new AnetAPI\CustomerPaymentProfileType();
        $payment_profile->setCustomerType('individual');
        $payment_profile->setPayment($payment);
        $payment_profile->setDefaultPaymentProfile(true);
        $customer_profile->addToPaymentProfiles($payment_profile);
        return $customer_profile;
    }
    /**
     * Creates and returns a customer payment profile request using billing and payment details.
     *
     * @param array $payment_args Payment arguments including billing and opaque data.
     * @return AnetAPI\CustomerPaymentProfileType
     */
    public function create_customer_payment_profile_request($payment_args)
    {
        $payment = $this->opaque_data($payment_args);
        $billing_address = new AnetAPI\CustomerAddressType();
        $billing_address->setFirstName($payment_args['billTo']['firstName']);
        $billing_address->setLastName($payment_args['billTo']['lastName']);
        $billing_address->setAddress($payment_args['billTo']['address']);
        $billing_address->setCity($payment_args['billTo']['city']);
        $billing_address->setState($payment_args['billTo']['state']);
        $billing_address->setZip($payment_args['billTo']['zip']);
        $billing_address->setCountry($payment_args['billTo']['country']);
        $payment_profile = new AnetAPI\CustomerPaymentProfileType();
        $payment_profile->setCustomerType('individual');
        $payment_profile->setBillTo($billing_address);
        $payment_profile->setPayment($payment);
        return $payment_profile;
    }
    /**
     * Builds a transaction request using stored customer profile and payment profile IDs.
     *
     * @param array $payment_args Payment arguments containing token data.
     * @return AnetAPI\TransactionRequestType
     */
    public function stored_card_request($payment_args)
    {
        $transaction_request = $this->transaction_request($payment_args);
        // Set up the profile method.
        $profile_to_charge = new AnetAPI\CustomerProfilePaymentType();
        $profile_to_charge->setCustomerProfileId($payment_args['transactionRequest']['payment']['tokenData']['customerProfileId']);
        $payment_profile = new AnetAPI\PaymentProfileType();
        $payment_profile->setPaymentProfileId($payment_args['transactionRequest']['payment']['tokenData']['paymentProfileId']);
        $profile_to_charge->setPaymentProfile($payment_profile);
        $transaction_request->setProfile($profile_to_charge);
        if (isset($payment_args['transactionRequest']['shipTo'])) {
            $shipping_address = new AnetAPI\NameAndAddressType();
            $shipping_address->setFirstName($payment_args['transactionRequest']['shipTo']['firstName']);
            $shipping_address->setLastName($payment_args['transactionRequest']['shipTo']['lastName']);
            $shipping_address->setAddress($payment_args['transactionRequest']['shipTo']['address']);
            $shipping_address->setCity($payment_args['transactionRequest']['shipTo']['city']);
            $shipping_address->setState($payment_args['transactionRequest']['shipTo']['state']);
            $shipping_address->setZip($payment_args['transactionRequest']['shipTo']['zip']);
            $shipping_address->setCountry($payment_args['transactionRequest']['shipTo']['country']);
            $transaction_request->setShipTo($shipping_address);
        }
        return $transaction_request;
    }
    /**
     * Returns the validation mode based on the current environment.
     *
     * @return string 'testMode' or 'liveMode'
     */
    public function validation_mode()
    {
        $validation_mode = $this->settings['testmode'] ? ANET_ENVIRONMENT_TESTMODE : ANET_ENVIRONMENT_LIVEMODE;
        return $validation_mode;
    }
    /**
     * Returns the appropriate Authorize.net environment based on test mode setting.
     *
     * @return string 
     */
    public function controller_response()
    {
        $environment = $this->settings['testmode']
            ? \net\authorize\api\constants\ANetEnvironment::SANDBOX
            : \net\authorize\api\constants\ANetEnvironment::PRODUCTION;
        return $environment;
    }
    /**
     * Explicitly sets the credentials for the API helper.
     *
     * @param string $login_id API Login ID Credentials.
     * @param string $transaction_key Transaction Key Credentials.
     * @param bool $testmode Testmode or Production mode.
     */
    public function set_credentials($login_id, $transaction_key, $testmode)
    {
        $this->settings['login_id'] = $login_id;
        $this->settings['transaction_key'] = $transaction_key;
        $this->settings['testmode'] = $testmode;
    }
    /**
     * Validates the merchant authentication by calling GetMerchantDetails API.
     *
     * @return bool True if authentication is valid, false otherwise.
     */
    public function is_authentication_valid()
    {
        try {
            $merchant_auth = $this->merchant_authentication_request();
            $request = new AnetAPI\GetMerchantDetailsRequest();
            $request->setMerchantAuthentication($merchant_auth);
            $controller = new AnetController\GetMerchantDetailsController($request);
            $response = $controller->executeWithApiResponse($this->controller_response());
            if (ANET_SANDBOX_STATUS_AUTHORIZED === $response->getMessages()->getResultCode()) {
                return true;
            } else {
                $error = $response->getMessages()->getMessage()[0]->getText();
                Anet_Setting_Helper::log("Authorize.Net Authentication Error: " . $error, ANET_PAYMENT_TYPE_CREDIT_CARD, 'error');
                return false;
            }
        } catch (Throwable $e) {
            $error_message = 'Unable to process your request. Please try again later.';
            if (isset($response) && $response->getTransactionResponse() && $response->getTransactionResponse()->getErrors()) {
                $error_message = $response->getTransactionResponse()->getErrors()[0]->getErrorText();
            } elseif (isset($response) && $response->getMessages()) {
                $error_message = $response->getMessages()->getMessage()[0]->getText();
            } else {
                $error_message = $e->getMessage();
            }
            Anet_Setting_Helper::log("Authorize.Net Exception: " . $error_message, ANET_PAYMENT_TYPE_CREDIT_CARD, 'error');
            return false;
        }
    }
    /**
     * Masks sensitive data in a transaction request.
     *
     * @param mixed $request JSON string or array.
     * @return string Masked JSON string.
     */
    public static function mask_sensitive_data($request)
    {
        $decoded = !empty($request) ? json_decode(wp_json_encode($request), true) : false;
        if (!$decoded) {
            return $request;
        }
        $transaction_request = &$decoded['transactionRequest'];
        // Mask all values in an associative array.
        $mask_all_values = function (&$data) {
            foreach ($data as $key => $value) {
                $data[$key] = self::ANET_MASK_VALUE;
            }
        };
        // Mask billTo and shipTo.
        if (isset($transaction_request['billTo'])) {
            $mask_all_values($transaction_request['billTo']);
        }
        if (isset($transaction_request['shipTo'])) {
            $mask_all_values($transaction_request['shipTo']);
        }
        if (isset($decoded['payment_args']['billTo'])) {
            $mask_all_values($decoded['payment_args']['billTo']);
        }
        // Mask credit card details.
        if (isset($transaction_request['payment']['creditCard'])) {
            $card = &$transaction_request['payment']['creditCard'];
            foreach (['cardNumber', 'expirationDate', 'cardCode'] as $field) {
                if (isset($card[$field])) {
                    $card[$field] = self::mask_by_length($card[$field]);
                }
            }
        }
        // Mask eCheck details.
        if (isset($transaction_request['payment']['bankAccount'])) {
            $bank = &$transaction_request['payment']['bankAccount'];
            foreach (['accountNumber', 'routingNumber', 'nameOnAccount', 'accountType'] as $field) {
                if (isset($bank[$field])) {
                    $bank[$field] = self::mask_by_length($bank[$field]);
                }
            }
        }
        // Mask opaqueData.
        if (isset($transaction_request['payment']['opaqueData']['dataValue'])) {
            $transaction_request['payment']['opaqueData']['dataValue'] = self::ANET_MASK_VALUE;
        }
        // Mask customer email.
        if (isset($transaction_request['customer']['email'])) {
            $transaction_request['customer']['email'] = self::ANET_MASK_VALUE;
        }
        // Mask transaction key.
        if (isset($decoded['merchantAuthentication']['transactionKey'])) {
            $decoded['merchantAuthentication']['transactionKey'] = self::ANET_MASK_VALUE;
        }
        // Mask userFields.
        if (isset($transaction_request['userFields']['userField'])) {
            foreach ($transaction_request['userFields']['userField'] as &$field) {
                if (isset($field['value'])) {
                    $field['value'] = self::ANET_MASK_VALUE;
                }
            }
        }
        // Mask customer IP.
        if (isset($transaction_request['customerIP'])) {
            $transaction_request['customerIP'] = self::ANET_MASK_VALUE;
        }
        return wp_json_encode($decoded, JSON_PRETTY_PRINT);
    }
    /**
     * Mask sensitive data in the request for logging purposes.
     *
     * @param mixed $request The request data to mask.
     * @return string The masked request data as a JSON string.
     */
    public static function mask_tokenization_request($request)
    {
        $decoded = !empty($request) ? json_decode(wp_json_encode($request), true) : false;
        if (!$decoded) {
            return $request;
        }
        // Helper to mask all values in an associative array.
        $mask_all_values = function (&$data) {
            foreach ($data as $key => $value) {
                $data[$key] = self::ANET_MASK_VALUE;
            }
        };
        // Mask billTo.
        if (isset($decoded['paymentProfile']['billTo'])) {
            $mask_all_values($decoded['paymentProfile']['billTo']);
        }
        // Mask credit card expirationDate.
        if (isset($decoded['paymentProfile']['payment']['creditCard']['expirationDate'])) {
            $decoded['paymentProfile']['payment']['creditCard']['expirationDate'] = self::ANET_MASK_VALUE;
        }
        // Mask customer email.
        if (isset($decoded['profile']['email'])) {
            $decoded['profile']['email'] = self::ANET_MASK_VALUE;
        }
        // Mask transaction key.
        if (isset($decoded['merchantAuthentication']['transactionKey'])) {
            $decoded['merchantAuthentication']['transactionKey'] = self::ANET_MASK_VALUE;
        }
        // Mask opaqueData in profile.paymentProfiles[0].
        if (isset($decoded['profile']['paymentProfiles'][0]['payment']['opaqueData']['dataValue'])) {
            $decoded['profile']['paymentProfiles'][0]['payment']['opaqueData']['dataValue'] = self::ANET_MASK_VALUE;
        }
        // Mask opaqueData in paymentProfile.
        if (isset($decoded['paymentProfile']['payment']['opaqueData']['dataValue'])) {
            $decoded['paymentProfile']['payment']['opaqueData']['dataValue'] = self::ANET_MASK_VALUE;
        }
        return wp_json_encode($decoded, JSON_PRETTY_PRINT);
    }
    /**
     * Masks a string by replacing each character with 'X'.
     *
     * @param string $value The string value to mask.
     * @return string The masked string with X characters.
     */
    private static function mask_by_length($value)
    {
        return str_repeat('X', strlen($value));
    }
}
