<?php

/**
 * PPMFWC_Helper_Transaction
 *
 * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
 * @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
 * @phpcs:disable PSR1.Methods.CamelCapsMethodName
 * @phpcs:disable PSR12.Properties.ConstantVisibility
 * @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
 */

class PPMFWC_Helper_Transaction
{
    /**
     * @param mixed $payStatus
     * @return false|mixed|null
     */
    public static function getCustomWooComOrderStatus(mixed $payStatus): mixed
    {
        $arrStatus['processing'] = get_option('paynl_status_paid');
        $arrStatus['cancel'] = get_option('paynl_status_cancel');
        $arrStatus['failed'] = get_option('paynl_status_failed');
        $arrStatus['authorised'] = get_option('paynl_status_authorized');
        $arrStatus['verify'] = get_option('paynl_status_verify');
        $arrStatus['chargeback'] = get_option('paynl_status_chargeback');

        $arrDefaultStatus['processing'] = 'processing';
        $arrDefaultStatus['cancel'] = 'cancelled';
        $arrDefaultStatus['failed'] = 'failed';
        $arrDefaultStatus['authorised'] = 'processing';
        $arrDefaultStatus['verify'] = 'on-hold';
        $arrDefaultStatus['chargeback'] = 'off';

        return $arrStatus[$payStatus] === false ? $arrDefaultStatus[$payStatus] : $arrStatus[$payStatus];
    }

    /**
     * @param string $transactionId
     * @param string $optionId
     * @param string $amount
     * @param string $orderId
     * @param string $startData
     * @param string|null $optionSubId
     * @return string
     */
    public static function newTransaction(string $transactionId, string $optionId, string $amount, string $orderId, string $startData, string $optionSubId = null)
    {
        global $wpdb;

        $table_name_transactions = $wpdb->prefix . "pay_transactions";

        $wpdb->insert(
            $table_name_transactions,
            array(
                'transaction_id' => $transactionId,
                'option_id' => $optionId,
                'option_sub_id' => $optionSubId,
                'amount' => $amount,
                'order_id' => $orderId,
                'status' => PPMFWC_Gateways::STATUS_PENDING,
                'start_data' => $startData,
            ),
            array(
                '%s', '%d', '%d', '%d', '%d', '%s', '%s',
            )
        );
        return $wpdb->insert_id;
    }

    /**
     * @param string $orderId
     * @return array|boolean
     */
    public static function getPaidTransactionIdForOrderId(string $orderId): bool|array
    {
        global $wpdb;
        $table_name_transactions = $wpdb->prefix . "pay_transactions";
        $result = $wpdb->get_results(
            $wpdb->prepare("SELECT * FROM $table_name_transactions WHERE order_id = %s  AND status IN ('SUCCESS', 'REFUND', 'AUTHORIZE') ", $orderId),
            ARRAY_A
        );
        if (!empty($result)) {
            return $result[0];
        } else {
            return false;
        }
    }

    /**
     * @param string $orderId
     * @return boolean
     */
    public static function isTransactionPaid(string $orderId): bool
    {
        global $wpdb;
        $table_name_transactions = $wpdb->prefix . "pay_transactions";
        $result = $wpdb->get_results(
            $wpdb->prepare("SELECT * FROM $table_name_transactions WHERE order_id = %s  AND status = 'SUCCESS'", $orderId),
            ARRAY_A
        );
        return !empty($result);
    }

    /**
     * @param string $transactionId
     * @param string $status
     * @return void
     */
    public static function updateStatus(string $transactionId, string $status): void
    {
        global $wpdb;
        $table_name_transactions = $wpdb->prefix . "pay_transactions";
        $wpdb->query(
            $wpdb->prepare("
                        UPDATE $table_name_transactions SET status = %s WHERE transaction_id = %s
                    ", $status, $transactionId)
        );
    }

    /**
     * @param string $transactionId
     * @return false|mixed
     */
    public static function getTransaction(string $transactionId): mixed
    {
        global $wpdb;

        $table_name_transactions = $wpdb->prefix . "pay_transactions";
        $result = $wpdb->get_results(
            $wpdb->prepare("SELECT * FROM $table_name_transactions WHERE transaction_id = %s", $transactionId),
            ARRAY_A
        );

        return $result[0] ?? false;
    }

    /**
     * @param \PayNL\Sdk\Model\Pay\PayOrder $payOrder
     * @param $status
     * @param $methodId
     * @return mixed|string|null
     * @throws PPMFWC_Exception
     * @throws PPMFWC_Exception_Notice
     */
    public static function processTransaction(\PayNL\Sdk\Model\Pay\PayOrder $payOrder, $status = null, $methodId = null): mixed
    {
        $transactionId = $payOrder->getOrderId();

        # Retrieve local payment state
        $transactionLocalDB = self::getTransaction($transactionId);
        $localTransactionStatus = $transactionLocalDB['status'] ?? '';
        $orderId = $transactionLocalDB['order_id'] ?? null;

        if (empty($transactionLocalDB)) {
            PPMFWC_Helper_Data::ppmfwc_payLogger('Notice: Cant find local transaction: ' . $transactionId);
            $orderId = PPMFWC_Helper_Data::getRequestArg('extra1') ?? null;
        }

        try {
            $order = new WC_Order($orderId);
        } catch (Exception $e) {
            # Could not retrieve order from WooCommerce (this is a notice so exchange won't repeat)
            throw new PPMFWC_Exception_Notice('Woocommerce could not find internal order ' . $orderId);
        }

        PPMFWC_Helper_Data::ppmfwc_payLogger('processTransaction', $transactionId, array($status, $localTransactionStatus, $orderId));

        if ($status == $localTransactionStatus) {
            PPMFWC_Helper_Data::ppmfwc_payLogger('processTransaction - status already up-to-date', $transactionId, array('status' => $status));
            throw new PPMFWC_Exception_Notice('Already ' . $status);
        }

        if (empty($transactionLocalDB)) {
            PPMFWC_Helper_Data::ppmfwc_payLogger('processTransaction', $orderId, array($status, $localTransactionStatus));
            PPMFWC_Helper_Transaction::newTransaction($transactionId, $methodId ?? 0, ($order->get_total() * 100), $orderId, 'start-data');
        }

        $transactionPaid = array($payOrder->getAmount());

        $internalPAYSatus = $payOrder->getStatusCode();
        $payApiStatus = PPMFWC_Gateways::ppmfwc_getStatusFromStatusId($internalPAYSatus);

        if ($localTransactionStatus != $payApiStatus) {
            if ($localTransactionStatus === PPMFWC_Gateways::STATUS_SUCCESS && $payApiStatus === PPMFWC_Gateways::STATUS_CANCELED) {
                PPMFWC_Helper_Data::ppmfwc_payLogger('Not changing order status to canceled, order is paid.');
            } else {
                self::updateStatus($transactionId, $payApiStatus);
            }
        }
        $wcOrderStatus = $order->get_status();

        $logArray['wc-order-id'] = $orderId;
        $logArray['wcOrderStatus'] = $wcOrderStatus;
        $logArray['Pay status'] = $payApiStatus;
        $logArray['Pay status id'] = $internalPAYSatus;

        PPMFWC_Helper_Data::ppmfwc_payLogger('processTransaction', $transactionId, $logArray);

        if (in_array($wcOrderStatus, array('complete', 'processing'))) {
            if ($payApiStatus == PPMFWC_Gateways::STATUS_REFUND) {
                PPMFWC_Helper_Data::ppmfwc_payLogger('processTransaction - Continue to process refund', $transactionId);

            } elseif ($payApiStatus == PPMFWC_Gateways::STATUS_CHARGEBACK) {
                PPMFWC_Helper_Data::ppmfwc_payLogger('processTransaction - Continue to process chargeback', $transactionId);

            } elseif ($status == PPMFWC_Gateways::STATUS_PINREFUND && $payApiStatus == PPMFWC_Gateways::STATUS_SUCCESS) {
                $pinRefundAmount = ($transactionLocalDB['amount'] / 100);
                PPMFWC_Helper_Data::ppmfwc_payLogger('processTransaction - Continue to process pin refund', $transactionId);
                $order->add_order_note(sprintf(esc_html(__('Pay.: Refunded: EUR %s via Retourpinnen', PPMFWC_WOOCOMMERCE_TEXTDOMAIN)), $pinRefundAmount));
                self::processRefund($order, $pinRefundAmount);
                return PPMFWC_Gateways::STATUS_REFUND;

            } else {
                PPMFWC_Helper_Data::ppmfwc_payLogger('processTransaction - Done', $transactionId);
                throw new PPMFWC_Exception_Notice('Order is already completed or processed: ' . $wcOrderStatus . '/' . $status . '/' . $payApiStatus);
            }
        }

        $newStatus = $payApiStatus;

        # Update status
        switch ($payApiStatus) {
            case PPMFWC_Gateways::STATUS_AUTHORIZE:
            case PPMFWC_Gateways::STATUS_SUCCESS:
                # Check the amount
                $skipAmountValidation = get_option('paynl_verify_amount') == 'yes';
                if (!$skipAmountValidation && !in_array($order->get_total(), $transactionPaid)) {
                    $order->update_status('on-hold', sprintf(__("Validation error: Paid amount does not match order amount. \npaidAmount: %s, \norderAmount: %s\n", PPMFWC_WOOCOMMERCE_TEXTDOMAIN), implode(' / ', $transactionPaid), $order->get_total())); // phpcs:ignore
                } else {

                    if ($payOrder->isFastCheckout()) {
                        if ($transactionId == $order->get_meta('transactionId') && $order->get_meta('fc')) {
                            PPMFWC_Helper_Data::ppmfwc_payLogger('FC: adding address to successful order', $transactionId);
                            PPMFWC_Hooks_FastCheckout_Exchange::addAddressToOrder($payOrder->getCheckoutData(), $order);
                        }
                    }

                    if ($payApiStatus == PPMFWC_Gateways::STATUS_AUTHORIZE) {
                        $method = $order->get_payment_method();
                        $methodSettings = get_option('woocommerce_' . $method . '_settings');
                        $auth_status = empty($methodSettings['authorize_status']) ? null : $methodSettings['authorize_status'];

                        if ($auth_status == 'parent_status') {
                            $auth_status = self::getCustomWooComOrderStatus('authorised');
                        }

                        try {
                            $order->set_transaction_id($transactionId);
                        } catch (Exception $e) {
                            PPMFWC_Helper_Data::ppmfwc_payLogger('Could not set transaction_id.', $transactionId);
                        }

                        # Launch action for custom implementation
                        do_action('paynl_order_authorised', $order->get_id(), $auth_status);

                        # auth_status is null when payment methods don't have the authorize_status-setting, like iDEAL.
                        if ($auth_status !== null) {
                            if ($wcOrderStatus != $auth_status) {
                                $order->update_status($auth_status);
                                $newStatus = $auth_status . ' as configured in settings of ' . $method;
                                #$order->add_order_note(sprintf(esc_html(__('Pay.: Authorised order set to ' . $auth_status . ' according to settings.', PPMFWC_WOOCOMMERCE_TEXTDOMAIN)), $transaction->getAccountNumber())); // phpcs:ignore
                            }

                            if ($auth_status == PPMFWC_Gateway_Abstract::STATUS_PROCESSING) {
                                # Treat as success. So continue, don't break, and set payment as complete...
                            } else {
                                # Save transaction and stop further actions
                                PPMFWC_Helper_Data::ppmfwc_payLogger('Setting transactionId, and break.', $transactionId);
                                $order->save();
                                break;
                            }
                        }
                    }

                    $initialMethod = $order->get_payment_method();
                    $usedMethod = PPMFWC_Gateways::ppmfwc_getGateWayById($methodId);

                    if (!empty($usedMethod) && $usedMethod->getId() != $initialMethod && get_option('paynl_payment_method_display') == 1) {
                        if (PPMFWC_Helper_Data::isOptionAvailable($usedMethod->getOptionId())) {
                            PPMFWC_Helper_Data::ppmfwc_payLogger('Changing payment method', $transactionId, array('usedMethod' => $usedMethod->getId(), 'method' => $initialMethod));
                            try {
                                $order->set_payment_method($usedMethod->getId());
                                $order->set_payment_method_title($usedMethod->getName());
                                $order->add_order_note(sprintf(esc_html(__('Pay.: Changed method to %s', PPMFWC_WOOCOMMERCE_TEXTDOMAIN)), $usedMethod->getName()));
                            } catch (Exception $e) {
                                PPMFWC_Helper_Data::ppmfwc_payLogger('Could not update new method names: ' . $e->getMessage(), $transactionId);
                            }
                        } else {
                            PPMFWC_Helper_Data::ppmfwc_payLogger('Could not change method, option is not available: ' . $usedMethod->getOptionId(), $transactionId);
                        }
                    }

                    $customStatus = self::getCustomWooComOrderStatus($payApiStatus == PPMFWC_Gateways::STATUS_AUTHORIZE ? 'authorised' : 'processing');
                    if (!in_array($customStatus, array('processing', 'authorised'))) {
                        $order->add_order_note(sprintf(esc_html(__('Pay.: Order status set to custom-status: %s', PPMFWC_WOOCOMMERCE_TEXTDOMAIN)), $customStatus));
                        $order->update_status($customStatus, 'According to Pay. plugin settings');
                        $order->save();
                    } else {
                        $order->payment_complete($transactionId);
                        if (!empty($payOrder->getCustomerId())) {
                            $order->add_order_note(sprintf(esc_html(__('Pay.: Payment complete (%s). customerkey: %s', PPMFWC_WOOCOMMERCE_TEXTDOMAIN)), $payApiStatus, $payOrder->getCustomerId())); // phpcs:ignore
                        } else {
                            $order->add_order_note(sprintf(esc_html(__('Pay.: Payment complete (%s).', PPMFWC_WOOCOMMERCE_TEXTDOMAIN)), $payApiStatus));
                        }
                    }
                }

            update_post_meta($orderId, 'CustomerName', esc_attr($payOrder->getCustomerName() ?? ''));
            update_post_meta($orderId, 'CustomerKey', esc_attr($payOrder->getCustomerId() ?? ''));

            break;

            case PPMFWC_Gateways::STATUS_DENIED:
                $order->add_order_note(esc_html(__('Pay.: Payment denied. ', PPMFWC_WOOCOMMERCE_TEXTDOMAIN)));
                $order->update_status(self::getCustomWooComOrderStatus('failed'));
                break;

            case PPMFWC_Gateways::STATUS_REFUND:
                if (get_option('paynl_externalrefund') == "yes") {
                    PPMFWC_Helper_Data::ppmfwc_payLogger('Changing order state to `refunded`', $transactionId);
                    $order->set_status('refunded');
                    if (get_option('paynl_exclude_restock') != "yes") {
                        wc_increase_stock_levels($orderId);
                    }
                    $order->save();
                }
                break;

            case PPMFWC_Gateways::STATUS_CHARGEBACK:
                $status = self::getCustomWooComOrderStatus('chargeback');
                if ($status == 'off') {
                    throw new PPMFWC_Exception_Notice('Ignoring: chargeback');
                }
                PPMFWC_Helper_Data::ppmfwc_payLogger('Changing order state to `chargeback`', $transactionId);
                $order->set_status($status, 'Pay. Chargeback. Reason: "' . PPMFWC_Helper_Data::getRequestArg('external_reason_description') . '".');
                if (get_option('paynl_exclude_restock') != "yes") {
                    wc_increase_stock_levels($orderId);
                }
                $order->save();
                break;

            case PPMFWC_Gateways::STATUS_CANCELED:
                $method = $order->get_payment_method();

                if (!str_starts_with($method, 'pay_gateway')) {
                    throw new PPMFWC_Exception_Notice('Not cancelling, last used method is not a Pay. method');
                }
                if ($order->is_paid()) {
                    throw new PPMFWC_Exception_Notice('Not cancelling, order is already paid');
                }
                if (!$order->has_status('pending') && !$order->has_status('on-hold')) {
                    throw new PPMFWC_Exception_Notice('Cancel ignored, order is ' . $order->get_status());
                }
                $databaseStatusSuccess = self::isTransactionPaid($order->get_id());
                if ($databaseStatusSuccess) {
                    throw new PPMFWC_Exception_Notice('Not cancelling, order is paid.');
                }

                $order->set_status(self::getCustomWooComOrderStatus('cancel'));
                $order->save();

                $order->add_order_note(esc_html(__('Pay.: Payment cancelled', PPMFWC_WOOCOMMERCE_TEXTDOMAIN)));
                break;

            case PPMFWC_Gateways::STATUS_VERIFY:
                $order->set_status(self::getCustomWooComOrderStatus('verify'), 'Pay.: ' . esc_html(__("To be verified. ", PPMFWC_WOOCOMMERCE_TEXTDOMAIN)));
                $order->save();
                break;
        }

        return $newStatus;
    }

    /**
     * @param $order
     * @param $amount
     * @return void
     * @throws PPMFWC_Exception
     * @throws Exception
     */
    public static function processRefund($order, $amount): void
    {
        $orderId = $order->get_id();
        $refund = wc_create_refund(array(
            'amount' => (float)$amount,
            'reason' => null,
            'order_id' => $orderId,
            'line_items' => array(),
            'refund_payment' => false,
            'restock_items' => false,
        ));
        if ($refund instanceof WP_Error && $refund->has_errors()) {
            throw new PPMFWC_Exception($refund->get_error_message());
        }

        if ($amount == $order->get_total()) {
            if (get_option('paynl_exclude_restock') != "yes") {
                wc_increase_stock_levels($orderId);
            }
        }
        $order->save();
    }

    /**
     * @param string $transactionId
     * @return bool
     */
    public static function checkProcessing(string $transactionId): bool
    {
        global $wpdb;

        $table_name_processing = $wpdb->prefix . "pay_processing";
        try {
            $result = $wpdb->get_results($wpdb->prepare(
                "SELECT * FROM $table_name_processing WHERE transaction_id = %s AND created > date_sub('%s', interval 1 minute)",
                $transactionId,
                date('Y-m-d H:i:s')
            ), ARRAY_A);
            $processing = !empty($result[0]);

            if (!$processing) {
                $wpdb->replace($table_name_processing, array('transaction_id' => $transactionId, 'created' => date('Y-m-d H:i:s')), array('%s', '%s'));
            }
        } catch (\Exception $e) {
            $processing = false;
        }

        return $processing;
    }

    /**
     * @param string $transactionId
     * @return void
     */
    public static function removeProcessing(string $transactionId): void
    {
        global $wpdb;
        $table_name_processing = $wpdb->prefix . "pay_processing";
        $wpdb->delete($table_name_processing, array('transaction_id' => $transactionId), array('%s'));
    }
}
