<?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
 */

namespace Prestashop\Module\AddonPayments\Operations;

use ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Refund;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponseInterface;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\OperationInterface;
use Exception;
use function in_array;
use InvalidArgumentException;
use Order;
use OrderDetail;
use OrderPayment;
use Prestashop\Module\AddonPayments\Exception\GatewayException;
use Prestashop\Module\AddonPayments\Exception\NotReallyAnException;
use Prestashop\Module\AddonPayments\OperationContext;
use Prestashop\Module\AddonPayments\Services\ApiFactory;
use PrestaShopLogger;

/**
 * Handles product cancellation.
 *
 * Ths operation takes place when from the BO, is selected either a return, refund or cancellation of a product. The
 * executed action is evaluated according to the order has been delivered or has been payed.
 */
final class ProductCancel extends ApiBaseOperation
{
    use RefundPaymentsTrait;

    /**
     * @var \Context
     */
    private $context;

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

    /**
     * ProcessTransaction constructor.
     *
     * @param $context
     * @param $module
     */
    public function __construct($context, $module)
    {
        $this->context = $context;
        $this->module = $module;
    }

    /**
     * @throws \PrestaShopDatabaseException
     * @throws \PrestaShopException
     */
    public function __invoke(Order $order, int $orderDetailId, array $cancelQuantity)
    {
        if ($order->hasBeenShipped()) {
            // This is a 'return product' action.
            throw new NotReallyAnException("Automatic refund on returns is not implemented. Refund for {$orderDetailId} on order {$order->reference} skipped.");
        }

        if (!$order->hasBeenPaid()) {
            // This is a 'cancel product' so no need for action.
            return;
        }

        // At this point is  a 'standard refund'.
        $detail = new OrderDetail($orderDetailId);
        if (null === $detail) {
            throw new InvalidArgumentException("Could not load detail {$orderDetailId} on {$order->reference}");
        }

        $amount = (float) $detail->total_price_tax_incl;
        $unitPrice =  (float) $detail->unit_price_tax_incl;

        /** @var \OrderPayment[] $payments */
        $payments = OrderPayment::getByOrderReference($order->reference);

        if (empty($payments)) {
            throw new NotReallyAnException("The order {$order->id} does not have any refundable transactions");
        }

        $payments = array_filter(
            $payments,
            function ($payment) {
                return $payment->payment_method === $this->module->name;
            }
        );

        if ($cancelQuantity) {
            $cancelQuantity = array_map('intval', $cancelQuantity);
        }

        $refundAmount = $unitPrice * $cancelQuantity[$orderDetailId];

        $processed = [];
        /** @var \ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs $apClient */
        $apClient = ApiFactory::createInstance(EPGJs::NAME);
        while ($amount > 0 && !empty($payments)) {
            $payment = array_shift($payments);

            if (in_array((string) $payment->transaction_id, $processed, true)) {
                continue;
            }

            $transaction = $this->findTransaction($order, $payment->transaction_id);

            if (null === $transaction) {
                continue;
            }

            $currentRefund = (float) ($amount > $transaction->remainingAmount ? $transaction->remainingAmount
                : $amount);

            try {
                $request = (new Refund())
                    ->setMerchantTransactionId($transaction->merchantTransactionId)
                    ->setAmount($refundAmount)
                    ->setPaymentSolution($transaction->paymentSolution)
                    ->setTransactionId($transaction->payFrexTransactionId)
                    ->setDescription(sprintf($this->module->l('Partial refund for order %s', 'productcancel'), $order->reference))
                    ->setMerchantParams(
                        [
                            'reference' => $order->reference,
                            'employee' => $this->context->employee->id,
                            'customer' => $this->context->customer->id,
                            'detail' => $detail->product_reference,
                            'full_refund' => false,
                        ]
                    );
                $apClient->refund($request);
            } catch (Exception $e) {
                $message
                    = "Could not perform refund operation for transaction {$payment->transaction_id}: {$e->getMessage()}";
                PrestaShopLogger::addLog($message, 3, $e->getCode(), 'Transactions', $payment->transaction_id);
            }
        }
    }
}
