<?php

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

use \Verifone\Core\DependencyInjection\CoreResponse\PaymentResponseImpl;
use \Verifone\Core\Converter\Response\CoreResponseConverter;
use \Verifone\Core\DependencyInjection\Service\TransactionImpl;
use \Verifone\Core\DependencyInjection\CoreResponse\PaymentStatusImpl;

class WC_Verifone_Payment
{

    const RETRY_DELAY_IN_SECONDS = 2;
    const RETRY_MAX_ATTEMPTS = 5;

    /**
     * @param WC_Order $order
     * @param PaymentResponseImpl $responseBody
     */
    public static function doneOrder(WC_Order $order, $responseBody)
    {
        $trans_id = preg_replace("/[^0-9]+/", "", $responseBody->getTransactionNumber());
        $_transactionId = $responseBody->getTransactionNumber();

        $order->add_meta_data('ext_order_id', $trans_id);
        $order->add_meta_data('payment_method', $responseBody->getPaymentMethodCode());

        $order->add_meta_data('verifone_payment_method', $responseBody->getPaymentMethodCode());
		$order->add_meta_data('verifone_transaction_id', $trans_id);

        // save masked pan number if available
        do_action_ref_array('verifone_add_masked_pan_number', array(&$order, $responseBody));

        $amount = $responseBody->getOrderGrossAmount() / 100;
        $note = sprintf(esc_html__('Payment captured. Transaction id: %1$s, transaction amount: %2$s', 'verifone-bluecommerce-for-woocommerce'), $_transactionId, wc_price($amount));
        $order->add_order_note($note);
        $order->payment_complete($_transactionId);
    }

    public static function processSuccess($status, $delayedSuccess = false)
    {
		$fields = [
			'i-f-1-11_interface-version',
			'i-f-1-3_order-currency-code',
			'l-f-1-20_order-gross-amount',
			'l-f-1-20_transaction-number',
			's-f-1-10_software-version',
			's-f-1-20_reference-number',
			's-f-1-30_payment-method-code',
			's-f-1-36_order-number',
			's-t-1-26_filing-code',
			's-t-1-36_order-note',
			's-t-1-6_card-expected-validity',
			's-t-256-256_signature-one',
			's-t-256-256_signature-two',
			't-f-14-19_order-timestamp',
		];

		$params = [];
		foreach ($fields as $field) {
			if (isset($_POST[$field])) { // phpcs:ignore WordPress.Security.NonceVerification -- not coming from user input, but from Verifone payment system thus cannot be verified with nonce
				$params[$field] = sanitize_text_field(wp_unslash($_POST[$field])); // phpcs:ignore WordPress.Security.NonceVerification -- not coming from user input, but from Verifone payment system thus cannot be verified with nonce
			}
		}

        $returnData = [
            'status' => $status,
            'error' => true,
            'message' => ''
        ];

        $orderNumber = WC_Verifone_Core_Payment::getOrderNumberFromResponse($params);

        if (empty($orderNumber)) {
            return self::processFail($status);
        }

        $order = new WC_Order($orderNumber);
		$returnData['order'] = $order;

        if ($order->is_paid()) {
            // Don't need to process paid order.
            return self::_returnPaidOrderData($order, $returnData);
        }

        $attempts = 0;
        while (!WC_Verifone_OrderLocker::lockOrder($orderNumber) && $attempts < self::RETRY_MAX_ATTEMPTS) {
            sleep(self::RETRY_DELAY_IN_SECONDS);
            ++$attempts;
        }

        if ($attempts > 0 && $attempts < self::RETRY_MAX_ATTEMPTS) {
            $orderTmp = new WC_Order($orderNumber);

            if ($orderTmp->is_paid()) {
                // Don't need to process paid order.
                return self::_returnPaidOrderData($orderTmp, $returnData);
            }

        }

        try {
            $parsedResponse = WC_Verifone_Core_Payment::validateAndParsePaymentResponse($params, $order);

            /** @var PaymentResponseImpl $body */
            $body = $parsedResponse->getBody();
            $validate = true;
        } catch (Exception $e) {
            return self::processFail($status);
        }

        if ($validate
            && $parsedResponse->getStatusCode() == CoreResponseConverter::STATUS_OK
            && empty($body->getCancelMessage())
            && !$order->is_paid()
        ) {

            self::doneOrder($order, $body);

            WC_Verifone_OrderLocker::unlockOrder($orderNumber);

            $returnData['return_url'] = $order->get_checkout_order_received_url();
            $returnData['error'] = false;

        } else {
            return self::processFail($status);
        }

        return $returnData;
    }

    /**
     * @param WC_Order $order
     * @param array $data
     * @return array
     */
    protected static function _returnPaidOrderData($order, $data)
    {

        $orderNumber = $order->get_id();

        $data['return_url'] = $order->get_checkout_order_received_url();
        $data['error'] = false;
		$data['order'] = $order;

		/**
		 * Remove key for security reasons. With the key, full order details can be viewed
		 * by anyone, so we really shouldn't be passing it around unnecessarily when user
		 * has originally had only the order id.
		 */
		$data['return_url'] = remove_query_arg( 'key', $data['return_url'] );

        if (WC_Verifone_OrderLocker::isLockedOrder($orderNumber)) {
            WC_Verifone_OrderLocker::unlockOrder($orderNumber);
        }

        return $data;
    }

    /**
     * @param $status
     * @return array
     */
    public static function processFail($status)
    {
		$fields = [
			's-t-1-40_shop-order__phase',
			'i-f-1-11_interface-version',
			's-f-1-10_software-version',
			's-f-1-36_order-number',
			's-t-1-30_cancel-reason',
			's-t-256-256_signature-one',
			's-t-256-256_signature-two',
		];

		$params = [];
		foreach ($fields as $field) {
			if (isset($_POST[$field])) { // phpcs:ignore WordPress.Security.NonceVerification -- not coming from user input, but from Verifone payment system thus cannot be verified with nonce
				$params[$field] = sanitize_text_field(wp_unslash($_POST[$field])); // phpcs:ignore WordPress.Security.NonceVerification -- not coming from user input, but from Verifone payment system thus cannot be verified with nonce
			}
		}

        $returnData = [
            'status' => $status,
            'error' => true,
            'message' => ''
        ];

        $orderNumber = WC_Verifone_Core_Payment::getOrderNumberFromResponse($params);

        if (empty($orderNumber)) {
            $returnData['return_url'] = wc_get_checkout_url();
            return $returnData;
        }

        $order = new WC_Order($orderNumber);

        if ($order->is_paid()) {
            // Don't need to process paid order.
            // Order fail in case when customer return to the webshop after long time (expired).
            // but order is confirmed by delayed success action
            return self::_returnPaidOrderData($order, $returnData);
        }

        try {
            $parsedResponse = WC_Verifone_Core_Payment::validateAndParsePaymentResponse($params, $order);

            /** @var PaymentResponseImpl $body */
            $body = $parsedResponse->getBody();
            $validate = true;
        } catch (Exception $e) {
            $validate = false;
            $parsedResponse = null;
            $body = null;
        }

        if ( ! $validate ) {
            WC_Verifone::log( wp_sprintf( esc_html__( 'Illegal access detected on processFail for order #%d. Request validation failed.', 'verifone-bluecommerce-for-woocommerce' ), $orderNumber ) );

			$returnData['message'] = esc_html__( 'Security Error. Illegal access detected', 'verifone-bluecommerce-for-woocommerce' );
			$returnData['return_url'] = wc_get_cart_url();
			return $returnData;
        } else {
            $returnData['message'] = sprintf(esc_html__('Payment was canceled. Cancel reason: %s', 'verifone-bluecommerce-for-woocommerce'), $body->getCancelMessage());
            self::_cancelOrder($order, $returnData['message']);
        }

        self::_restoreCart($order);

        $returnData['return_url'] = $order->get_cancel_order_url();
        $returnData['order'] = $order;

        return $returnData;
    }

    protected static function _restoreCart(WC_Order $order)
    {
        /** @var WooCommerce $woocommerce */
        global $woocommerce;

        /** @var WC_Order_Item $item */
        foreach ($order->get_items() as $item) {
            $data = $item->get_data();

            if (isset($data['product_id'])) {

                if(isset($data['variation_id'])) {
                    $variations = [];
                    $item_meta_data = $item->get_meta_data();
                    foreach($item_meta_data as $meta_data) {
                        $variation_data = $meta_data->get_data();
                        $variations[$variation_data['key']] = $variation_data['value'];
                    }
                    $woocommerce->cart->add_to_cart($data['product_id'], $item->get_quantity(), $data['variation_id']);
                } else {
                    $woocommerce->cart->add_to_cart($data['product_id'], $item->get_quantity());
                }
            }

        }

    }

    /**
     * @param WC_Order $order
     * @param $note
     */
    protected static function _cancelOrder(WC_Order $order, $note)
    {
        $order->update_status('cancelled', $note);
    }

	/**
	 * Check the payment status for the given order.
	 *
	 * Throws Exception when order status is not suitable for checking payment status, or no transaction found.
	 * Returns true when payment is confirmed, false when payment is cancelled, null when payment is not confirmed yet.
	 *
	 * @param WC_Order $order
	 * @param boolean $cron
	 * @return boolean|null
	 * @throws Exception
	 */
    public static function checkPaymentStatus( WC_Order $order, $cron = false ) {
		$payment_statuses_with_transactions = [];
		$payment_status_found_with_transaction_id = false;
        $order_statuses_to_check = [
            'pending',
            'on-hold',
            'processing'
        ];

        if ( $order->is_paid() || ! in_array( $order->get_status(), $order_statuses_to_check ) ) {
			throw new Exception( esc_html__( 'Order is not in a state that allows payment status check.', 'verifone-bluecommerce-for-woocommerce' ) );
        }

		// Get the payment method and transaction id from order
		$order_payment_method = $order->get_meta( 'verifone_payment_method', true );
		$order_transaction_id = $order->get_meta( 'verifone_transaction_id', true );

		/**
		 * Logic to get payment status in two scenarios:
		 * 1. User has returned to the webshop after payment and we have both payment method and transaction id saved in order meta
		 * 2. User has not returned to the webshop after payment or it was subscription renewal that didn't get ok right away, and we only have order
		 */
		if ( ! empty( $order_payment_method ) && ! empty( $order_transaction_id ) ) {
			// If we have both, try to get the transaction status directly
			$payment_status = WC_Verifone_Core_Payment::getPaymentStatus( $order_payment_method, $order_transaction_id );

			if ( is_null( $payment_status ) ) {
				throw new Exception( esc_html__( 'Could not retrieve payment status from Verifone gateway.', 'verifone-bluecommerce-for-woocommerce' ) );
			}

			$payment_statuses_with_transactions = [ [
				'payment_status' => $payment_status,
				'transaction' => null,
			] ];
			$payment_status_found_with_transaction_id = true;
		} else {
			// Otherwise, get all transactions with the order ID
			$transactions = WC_Verifone_Core_Payment::getTransactionsFromGate( $order );

			if ( is_null( $transactions ) || count( $transactions ) === 0 ) {
				throw new Exception( esc_html__( 'Could not retrieve transactions from Verifone gateway.', 'verifone-bluecommerce-for-woocommerce' ) );
			}

			$transaction_ids_for_note = [];
			foreach ( $transactions as $transaction ) {
				$transaction_ids_for_note[] = $transaction->getNumber() . ' (' . $transaction->getMethodCode() . ')';

				$payment_status = WC_Verifone_Core_Payment::getPaymentStatus( $transaction->getMethodCode(), $transaction->getNumber() );
				if ( is_null( $payment_status ) ) {
					continue;
				}

				$payment_statuses_with_transactions[] = [
					'payment_status' => $payment_status,
					'transaction' => $transaction,
				];
			}

			$order->add_order_note( sprintf( esc_html__( 'No transaction ID for the order, did search with order ID and found following transactions: %s', 'verifone-bluecommerce-for-woocommerce' ), implode( ', ', $transaction_ids_for_note ) ) );
		}

		// If there's only one payment status and we found it with transaction ID, we can handle it directly
		if ( count( $payment_statuses_with_transactions ) === 1 && $payment_status_found_with_transaction_id ) {
			$payment_status = $payment_statuses_with_transactions[0]['payment_status'];

			if ( self::_confirmPayment( $payment_status->getCode() ) ) {
				self::doneOrderBackend( $order, $payment_status );
				return true;
			} elseif ( $payment_status->getCode() === 'cancelled' ) {
				return false;
			}

			return null;
		}

		// If we found multiple payment statuses with order ID, loop through them and try to find order transaction with matching amount
		foreach ( $payment_statuses_with_transactions as $payment_status_with_transaction ) {
			$payment_status = $payment_status_with_transaction['payment_status'];

			if ( (int) $payment_status->getOrderAmount() === (int) ( $order->get_total() * 100 ) ) {
				$order->add_order_note( sprintf( esc_html__( 'Transaction %s amount matched the order ID and amount.', 'verifone-bluecommerce-for-woocommerce' ), $payment_status_with_transaction['transaction']->getNumber() ) );

				if ( self::_confirmPayment( $payment_status->getCode() ) ) {
					self::doneOrderBackend( $order, $payment_status );
					return true;
				} elseif ( $payment_status->getCode() === 'cancelled' ) {
					return false;
				}

				return null;
			}
		}

        throw new Exception( esc_html__( 'No transaction found for the order.', 'verifone-bluecommerce-for-woocommerce' ) );
    }

    protected static function _confirmPayment($status)
    {
        $confirm = ['committed', 'settled', 'verified'];

        return in_array($status, $confirm);
    }

    public static function doneOrderBackend(WC_Order $order, PaymentStatusImpl $transaction)
    {
        $trans_id = preg_replace("/[^0-9]+/", "", $transaction->getTransactionNumber());
        $_transactionId = $transaction->getTransactionNumber();

        $order->add_meta_data('ext_order_id', $trans_id);
        $order->add_meta_data('payment_method', $transaction->getPaymentMethodCode());

		$order->add_meta_data('verifone_payment_method', $transaction->getPaymentMethodCode());
		$order->add_meta_data('verifone_transaction_id', $trans_id);

        $amount = $transaction->getOrderAmount() / 100;
        $note = sprintf(esc_html__('Payment captured. Transaction id: %1$s, transaction amount: %2$s', 'verifone-bluecommerce-for-woocommerce'), $_transactionId, wc_price($amount));
        $order->add_order_note($note);
        $order->payment_complete($_transactionId);
    }
}
