<?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 ComerciaGlobalPayments\AddonPayments\SDK\Response\Transaction;
use Prestashop\Module\AddonPayments\Exception\CheckoutException;
use Prestashop\Module\AddonPayments\Exception\GatewayException;
use Prestashop\Module\AddonPayments\Helpers\SecurityTrait;
use Prestashop\Module\AddonPayments\OperationContext;
use Prestashop\Module\AddonPayments\Operations\ProcessTransaction;
use Prestashop\Module\AddonPayments\Operations\ValidateOrder;

class AddonpaymentsConfirmationModuleFrontController extends ModuleFrontController
{
    use SecurityTrait;

    public const ALLOWED_CONTENT_TYPES = ['application/json', 'application/xml'];

    public const OPERATION_PARAMETER = 'operation';

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

    protected $transactionId;

    public function postProcess()
    {
        try {
            if (!$this->module->active) {
                throw new CheckoutException('The payment module is not available', CheckoutException::PRESTASHOP_CONTEXT_INVALID);
            }

            if (FALSE === Tools::isSubmit(self::OPERATION_PARAMETER)) {
                throw new CheckoutException(
                    'Missing required request parameters',
                    CheckoutException::PRESTASHOP_CONTEXT_INVALID
                );
            }

            $merchantTrxId = Tools::getValue(self::OPERATION_PARAMETER);
            [$cartId] = $this->decode($merchantTrxId);
            $this->context->cart = new Cart($cartId);

            $payload = $this->getPayload();
            $property = isset($payload->{OperationInterface::RESPONSE_OPERATIONS_KEY})
                ? OperationInterface::RESPONSE_OPERATIONS_KEY
                : OperationInterface::RESPONSE_OPERATIONS_ARRAY_KEY;

            if (empty($payload->{$property})) {
                throw new GatewayException(
                    'The request does not contain any operations.',
                    GatewayException::NO_TRANSACTIONS
                );
            }

            // At this point can occur 2 scenarios:
            //  - The callback is called first so client is redirected about
            //    the same time confirmation is been processed.
            //  - The confirmation is called first so the client is redirected
            //    after the payment is settled.
            // Since this is totally random, but we know for sure the callback
            // will be issued by the payment method, if there's no order yet
            // for this cart, sleep for 15s.

            // $order_id = Order::getOrderByCartId($cartId);
            // if (false === $order_id) {
            //    sleep(15);
            // }

            $operationContext = new OperationContext(
                $this->context->cart->id,
                $this->context->cart->getOrderTotal(),
                $this->context->cart->id_currency,
                $this->context->cart->secure_key
            );

            if (property_exists($payload, 'status') && (OperationInterface::STATUS_ERROR === $payload->status
                || OperationInterface::STATUS_FAIL === $payload->status
                )) {
                $order = Order::getOrderByCartId($this->context->cart->id);
                if(!$order) {
                     $this->doDie('HTTP/1.0 200 Success');
                }
            }

            $transactions = [];

            if(is_object($payload->{$property}) && is_array( $payload->{$property}->operation )) {
                $payload->{$property} = $payload->{$property}->operation;
            }

            $operations = count($payload->{$property});

            $sorted = 1;
            foreach ($payload->{$property} as $operation) {
                $transaction = new Transaction($operation, ($sorted));

                (new ProcessTransaction($operationContext, $this->module))(
                    $transaction,
                    $transaction->getSortedOrder() === $operations
                );

                $transactions[] = $transaction;
                ++$sorted;
            }

            $transaction = $transactions[count($transactions) - 1];

            if(OperationInterface::STATUS_REDIRECTED === $transaction->getStatus()) {
                $this->doDie('HTTP/1.0 200 Success');
            }

            $order = (new ValidateOrder($this->module))(
                $transactions[count($transactions) - 1],
                $operationContext
            );

            if (null !== $order) {
                Transactions::setOrderByCart($order->id_cart, $order->id);
            }

            $this->doDie('HTTP/1.0 200 Success');

        } catch (Exception $e) {
            PrestaShopLogger::addLog(
                'Order confirmation failed: ' . $e->getMessage(),
                3,
                $e->getCode(),
                'Cart',
                null !== $this->context->cart ? $this->context->cart->id : 0
            );
            $this->doDie('HTTP/1.0 400 Bad request');
        }
    }

    /**
     * Get the confirmation request payload.
     *
     * This method evaluates for both possible content-type: JSON or XML. If any is present
     * try to read the STDIN. If any other type is submitted or the STDIN cannot be read
     * or parsed an exception is thrown interrupting the confirmation process.
     */
    protected function getPayload()
    {
        $contentType = $_SERVER['CONTENT_TYPE'] ?? null;

        if (null === $contentType
            || !in_array($contentType, self::ALLOWED_CONTENT_TYPES, true)
        ) {
            throw new GatewayException('Invalid request received.', GatewayException::INVALID_OPERATION_CONFIRMATION_REQUEST);
        }

        switch ($contentType) {
            case 'application/json':
                $payload = $this->parseJsonStdin();
                break;
            case 'application/xml':
                $payload = $this->parseXmlStdin();
                break;
            default:
                $payload = null;
        }

        if (null === $payload) {
            throw new GatewayException('Invalid request received.', GatewayException::INVALID_OPERATION_CONFIRMATION_REQUEST);
        }

        return $payload;
    }

    /**
     * Parse JSON payload directly from STDIN.
     *
     * @return object|null
     */
    protected function parseJsonStdin()
    {
        $payload = file_get_contents('php://input');
        $payload = preg_replace(['/\n/', '/\t/', '/' . PHP_EOL . '/'], '', $payload);
        $decodedPayload = json_decode($payload, false);

        if (!$payload || JSON_ERROR_NONE !== json_last_error()) {
            return null;
        }

        return $decodedPayload;
    }

    /**
     * Parse XML payload directly from STDIN.
     *
     * @return object|null
     */
    protected function parseXmlStdin()
    {
        $payload = file_get_contents('php://input');
        $payload = preg_replace(['/\n/', '/\t/', '/' . PHP_EOL . '/'], '', $payload);
        $decodedPayload = json_decode(json_encode(simplexml_load_string($payload)), false);

        if (!$payload || JSON_ERROR_NONE !== json_last_error()) {
            return null;
        }

        return $decodedPayload;
    }

    protected function doDie(string $content = 'Location: ../'): void
    {
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
        header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');

        header('Cache-Control: no-store, no-cache, must-revalidate');
        header('Cache-Control: post-check=0, pre-check=0', false);
        header('Pragma: no-cache');

        header($content);
        exit;
    }
}
