<?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\Api\EPGJs;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Capture;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\OperationInterface;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\ResponseInterface;
use Prestashop\Module\AddonPayments\Exception\MITException;
use Prestashop\Module\AddonPayments\OperationContext;
use Prestashop\Module\AddonPayments\Operations\ProcessTransaction;
use Prestashop\Module\AddonPayments\Operations\ValidateOrder;
use Prestashop\Module\AddonPayments\Services\ApiFactory;

/**
 * BO controller to handle capturing transactions.
 */
class AdminCaptureTransactionController extends ModuleAdminControllerCore
{
    private const UNKNOWN_EXCEPTION = -1;

    private static $EXCEPTION_HANDLERS
        = [
            self::UNKNOWN_EXCEPTION              => 'errorUnknown',
            MITException::NO_TRANSACTION         => 'erroNoTransaction',
            MITException::UNSUCCESSFUL_OPERATION => 'errorUnsuccessfulResponse',
            MITException::ERROR_RESPONSE         => 'errorUnsuccessfulResponse',
        ];

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

    /**
     * @var \Transactions
     */
    private $transaction;

    /**
     * @var string
     */
    private $redirectTo;

    public function __construct()
    {
        $this->bootstrap = true;
        $this->lang = false;
        $this->actions = [];
        parent::__construct();
    }

    public function initContent()
    {
        parent::initContent();
        $this->display = 'view';
        try {
            if (null === Tools::getValue('transaction', null)
            ) {
                throw new MITException("Missing required parameter 'transaction'", MITException::NO_TRANSACTION);
            }
            $this->transaction = new Transactions((int)Tools::getValue('transaction'));
            $this->redirectTo = base64_decode(Tools::getValue('redirect'));
            $order = new Order($this->transaction->id_order);
            /** @var \ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs $client */
            $client = ApiFactory::createInstance(EPGJs::NAME);
            $request = (new Capture())
                ->setTransactionId($this->transaction->payFrexTransactionId)
                ->setMerchantTransactionId($this->transaction->merchantTransactionId)
                ->setPaymentSolution($this->transaction->paymentSolution)
                ->setDescription(
                    $this->module->l(
                        sprintf(
                            "Capture transaction %s for order %s",
                            $this->transaction->merchantTransactionId,
                            $order->reference
                        )
                    )
                )
                ->setMerchantParams(
                    [
                        'reference' => $order->reference,
                        'employee'  => $this->context->employee->id,
                        'customer'  => $order->id_customer,
                        'operation' => 'capture',
                    ]
                );
            $client->capture($request);
            // Wait for confirmation to finish, same as referenced line
            // @link public/modules/addonpayments/controllers/front/confirmation.php:88
            sleep(15);
            Tools::redirectAdmin($this->redirectTo);
        } catch (Exception $e) {
            PrestaShopLogger::addLog($e->getMessage(), 3, $e->getCode());
            Tools::redirectAdmin($this->context->link->getAdminLink('AdminOrders'));
        }
    }

    /**
     * @throws \PrestaShopException
     */
    private function processResponse(ResponseInterface $response, OperationContext $operationContext): void
    {
        if ($response instanceof OperationInterface) {
            // Captured transactions can only return SUCCESS, any other status should be considered an ERROR.
            if ($response->hasSucceed()) {
                $this->registerTransactions($response->getOperations(), $operationContext);
                $order = (new ValidateOrder($this->module))($response->getTransaction(), $operationContext);
                PrestaShopLogger::addLog(
                    $this->module->l(
                        sprintf(
                            'Capture transaction %s for order %s',
                            $this->transaction->merchantTransactionId,
                            null !== $order ? $order->reference : ''
                        )
                    ),
                    1,
                    0,
                    'Order',
                    null !== $order ? $order->id : 0
                );
                Tools::redirectAdmin($this->redirectTo);

                return;
            }

            $transaction = $response->getTransaction();
            throw new MITException(
                sprintf(
                    $this->module->l('The transaction %s could not be captured, operation returned %s: %s'),
                    $transaction->getPayFrexTransactionId(),
                    $transaction->getStatus(),
                    $transaction->getMessage()
                ), MITException::UNSUCCESSFUL_OPERATION
            );
        }

        if (null !== $response->getHttpResponse()) {
            $errorContent = json_decode((string)$response->getHttpResponse()->getBody(), true);

            if (!empty($errorContent) && !empty($errorContent['errorMessage'])
            ) {
                throw new MITException($errorContent['errorMessage'], MITException::ERROR_RESPONSE);
            }
        }

        // If we have come this far is because its an unknown error.
        throw new MITException($response->getReason(), MITException::ERROR_RESPONSE);
    }

    /**
     * @param \ComerciaGlobalPayments\AddonPayments\SDK\Response\Transaction[] $transactions
     * @param \Prestashop\Module\AddonPayments\OperationContext                $operationContext
     */
    private function registerTransactions(
        array $transactions,
        OperationContext $operationContext
    ): void {
        $operations = count($transactions);
        foreach ($transactions as $transaction) {
            (new ProcessTransaction($operationContext, $this->module))(
                $transaction,
                $transaction->getSortedOrder() === $operations
            );
        }
    }

    /**
     * Handles any exceptions thrown during controller execution.
     *
     * @throws \PrestaShopException
     *
     * @uses \AdminCaptureTransactionController::erroNoTransaction()
     * @uses \AdminCaptureTransactionController::errorUnsuccessfulResponse()
     * @uses \AdminCaptureTransactionController::errorUnknown()
     */
    private function handleException(Exception $e)
    {
        if (!($e instanceof MITException)) {
            PrestaShopLogger::addLog($e->getMessage(), 3, $e->getCode());
            throw new PrestaShopException($e->getMessage());
        }

        $exceptionHandler = self::$EXCEPTION_HANDLERS[$e->getCode()] ??
            self::$EXCEPTION_HANDLERS[self::UNKNOWN_EXCEPTION];
        $this->{$exceptionHandler}($e);
    }

    /**
     * Handles unexpected errors.
     */
    private function errorUnknown(Exception $e)
    {
        PrestaShopLogger::addLog(
            $e->getMessage(),
            3,
            $e->getCode()
        );
        $this->errors[] = $this->module->l("An unexpected exception occurred. Could not execute 'capture' operation");
    }

    /**
     * Handles error when the 'transaction' parameter was not provided.
     */
    private function erroNoTransaction(MITException $e)
    {
        PrestaShopLogger::addLog(
            $e->getMessage(),
            3,
            $e->getCode(),
            'Transactions',
            0
        );
        $this->errors[] = $this->module->l('This operation requires a valid transaction');
    }

    /**
     * Handles error when the API response returns non-success status.
     *
     * Refers to the API status field not HTTP returned status.
     */
    private function errorUnsuccessfulResponse(MITException $e)
    {
        PrestaShopLogger::addLog(
            $e->getMessage(),
            3,
            $e->getCode(),
            'Transactions',
            $this->transaction->id_transaction
        );
        $this->errors[] = $e->getMessage();
    }
}
