<?php
/**
 * 2007-2021 PrestaShop
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License (AFL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/afl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@prestashop.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
 * versions in the future. If you wish to customize PrestaShop for your
 * needs please refer to http://www.prestashop.com for more information.
 *
 * @author    PrestaShop SA <contact@prestashop.com>
 * @copyright 2007-2021 PrestaShop SA
 * @license   http://opensource.org/licenses/afl-3.0.php  Academic Free License
 *            (AFL 3.0) International Registered Trademark & Property of
 *            PrestaShop SA
 */

// @codingStandardsIgnoreStart
/* @noinspection AutoloadingIssuesInspection */
/* @noinspection PhpMultipleClassDeclarationsInspection */
/* @noinspection PhpIllegalPsrClassPathInspection */
/* @noinspection PhpDeprecationInspection */
/* @noinspection ReturnTypeCanBeDeclaredInspection */

// @codingStandardsIgnoreEnd

use ComerciaGlobalPayments\AddonPayments\SDK\Response\OperationInterface;
use Hashids\Hashids;
use Prestashop\Module\AddonPayments\Exception\CheckoutException;
use Prestashop\Module\AddonPayments\Helpers\FrontControllerRedirectionsTrait;
use Prestashop\Module\AddonPayments\Helpers\SecurityTrait;

/**
 * Handles gateway SUCCESS/ERROR/CANCEL callbacks.
 *
 * The main use case is when a customer is redirected to a payment solution
 * when they are redirected back to the site regardless the status of the
 * transaction, the redirection occurs before the status callback is issued.
 * This can cause the cart to left opened when the payment was successfully
 * completed or errors and cancellations not being handled properly.
 */
class AddonpaymentsCallbackModuleFrontController extends ModuleFrontController
{
    use SecurityTrait;
    use FrontControllerRedirectionsTrait;

    /**
     * @var bool
     */
    public $ssl = _PS_USE_SSL_;

    /**
     * @var \addonpayments
     */
    public $module;

    /**
     * {@inheritDoc}
     *
     * @return void
     */
    public function postProcess()
    {
        try {
            if (false === $this->checkIfPaymentOptionIsAvailable()) {
                throw new CheckoutException(
                    'This payment method is not available.',
                    CheckoutException::PRESTASHOP_PAYMENT_UNAVAILABLE
                );
            }

            $payment = Tools::getValue(addonpayments::PAYMENT_STATUS_KEY);

            if (empty($payment)) {
                throw new CheckoutException('The context is not valid',
                    CheckoutException::PRESTASHOP_CONTEXT_INVALID);
            }

            [$cart_id, $customer_id, $trx, $status] = (new Hashids(self::getEncryptionKey()))
                ->decode($payment);

            $merchantTransactionId = $this->encode($cart_id, $customer_id, $trx);
            $this->context->cart = new Cart($cart_id);

            if ($status === AddonPayments::PAYMENT_STATUS_UNKNOWN
                || $status === AddonPayments::PAYMENT_STATUS_CANCELLED
                || $status === AddonPayments::PAYMENT_STATUS_FAILED
            ) {

                $trxStatus = $this->getStatusName($status);
                $transaction = $this->get_transaction($merchantTransactionId, $trxStatus);

                if($status === AddonPayments::PAYMENT_STATUS_UNKNOWN) {
                    $status = $this->getStatusCode($transaction['status']);
                }

                $message = '';

                if (!empty($transaction) && OperationInterface::STATUS_ERROR3DS !== $transaction['status']) {
                    $message = $transaction['message'];
                    $message = $this->module->mapErrorMessage($message);
                }

                if (addonpayments::PAYMENT_STATUS_FAILED === $status
                    || addonpayments::PAYMENT_STATUS_CANCELLED === $status
                ) {

                    $duplicated = $this->context->cart->duplicate();
                    $this->context->cart = $duplicated['cart'];
                    $this->context->cookie->id_cart = $duplicated['cart']->id;
                    $this->context->cookie->checkedTOS = 1;
                    $this->context->cookie->check_cgv = true;
                }

                if (addonpayments::PAYMENT_STATUS_FAILED === $status) {
                    Tools::redirect($this->getErrorUrl($message));
                }

                if (addonpayments::PAYMENT_STATUS_CANCELLED === $status) {
                    Tools::redirect($this->getCancelUrl());
                }
            }

            $order_id = Order::getOrderByCartId($this->context->cart->id);
            $order = $order_id ? new Order($order_id) : null;

            if (null === $order) {
                $this->module->validateOrder(
                    $this->context->cart->id,
                    (int)Configuration::get(
                        addonpayments::PS_OS_EASYPAYMENTGATEWAY_PROCESSING
                    ),
                    $this->context->cart->getOrderTotal(),
                    $this->module->name,
                    $this->module->l("Payment by {$this->module->displayName}"),
                    [],
                    $this->context->cart->id_currency,
                    true,
                    $this->context->cart->secure_key
                );

                $order = new Order(Order::getOrderByCartId($this->context->cart->id));
            }

            $order->save();
            Tools::redirect($this->getSuccessUrl());

        } catch (Exception $e) {
            PrestaShopLogger::addLog($e->getMessage(), 3, $e->getCode());
            Tools::redirect($this->getErrorUrl());
        }
    }

    /**
     * Try to get last transaction status by merchant transaction ID
     */
    private function get_transaction( $merchantTransactionId, $status ) {
        $counter = 0;
        $continue = true;

        do {
            $transactions = \Transactions::getByMerchTransactionId($merchantTransactionId, $status);
            $transaction  = reset( $transactions );

            $finalStatus = [
                OperationInterface::STATUS_SUCCESS,
                OperationInterface::STATUS_ERROR,
                OperationInterface::STATUS_CANCELLED,
                OperationInterface::STATUS_FAIL,
                OperationInterface::STATUS_REJECTED,
                OperationInterface::STATUS_PENDING_CONFIRMATION,
            ];

            if(in_array($transaction['status'], $finalStatus) || empty($transaction)) {
                $continue = false;
            }

            $counter++;
            if($continue) {
                sleep( 1 );
            }

        }while ($continue && $counter < 5);

		return $transaction;
    }

    /**
     * Check that this payment option is still available in case the customer
     * changed his address just before the end of the checkout process
     */
    private function checkIfPaymentOptionIsAvailable(): bool
    {
        $modules = Module::getPaymentModules();

        if (empty($modules)) {
            return false;
        }

        foreach ($modules as $module) {
            if (isset($module['name'])
                && $this->module->name === $module['name']) {
                return true;
            }
        }

        return false;
    }


    /**
     * Return status code
     *
     * @param string $status
     *
     * @return int
     */
    private function getStatusCode(string $status): int
    {
        $status_array = [
            OperationInterface::STATUS_SUCCESS => AddonPayments::PAYMENT_STATUS_SUCCESS,
            OperationInterface::STATUS_ERROR => AddonPayments::PAYMENT_STATUS_FAILED,
            OperationInterface::STATUS_FAIL => AddonPayments::PAYMENT_STATUS_FAILED,
            OperationInterface::STATUS_CANCELLED => AddonPayments::PAYMENT_STATUS_CANCELLED,
            OperationInterface::STATUS_REDIRECTED => AddonPayments::PAYMENT_STATUS_CANCELLED,
            OperationInterface::STATUS_AWAITING_PAYSOL => AddonPayments::PAYMENT_STATUS_SUCCESS,
            OperationInterface::STATUS_REJECTED => AddonPayments::PAYMENT_STATUS_CANCELLED,
            OperationInterface::STATUS_PENDING_CONFIRMATION => AddonPayments::PAYMENT_STATUS_SUCCESS
        ];

        return $status_array[$status];
    }

    /**
     * Return status name
     *
     * @param int $status_code
     *
     * @return string|array
     */
    private function getStatusName(int $status_code) {
        $status = null;

        $status_array = [
            AddonPayments::PAYMENT_STATUS_SUCCESS => OperationInterface::STATUS_SUCCESS,
            AddonPayments::PAYMENT_STATUS_FAILED => [
                OperationInterface::STATUS_ERROR,
                OperationInterface::STATUS_FAIL,
                OperationInterface::STATUS_ERROR3DS,
            ],
            AddonPayments::PAYMENT_STATUS_CANCELLED => OperationInterface::STATUS_CANCELLED,
        ];

        if(isset($status_array[$status_code])) {
            $status = $status_array[$status_code];
        }

        return $status;
    }
}
