<?php
/** 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-2019 PrestaShop SA and Contributors
 * @license   https://opensource.org/licenses/AFL-3.0  Academic Free License (AFL 3.0)
 * International Registered Trademark & Property of PrestaShop SA
 */
require_once __DIR__ . '/paymentabstract.php';

use Sibs\Models\SibsOrderRef;
use Sibs\Models\SibsTransaction;
use Sibs\SibsConstants;
use Sibs\SibsException;
use Sibs\SibsLogger;
use Sibs\SibsPaymentCore;
use Sibs\SibsUtils;
use Sibs\SibsWebhookDecryptor;

/**
 * @property Sibs $module
 */
class SibsWebHookModuleFrontController extends SibsPaymentAbstractModuleFrontController
{
    /**
     * Header Init Vec.
     *
     * @var string
     */
    private $headerInitVec;

    /**
     * Header Auth Tag.
     *
     * @var string
     */
    private $headerAuthTag;

    /**
     * Header SIBS Webhook.
     *
     * @var string
     */
    private $headerSibsWebhook;

    /**
     * Body.
     *
     * @var string
     */
    private $body;

    /**
     * Webhook Secret.
     *
     * @var string
     */
    private $webhookSecret;

    /**
     * Plugin Webhook Retry.
     *
     * @var string
     */
    private static $pluginWebhookRetryUrl = 'https://webhook.curta.ink/api/v1/webhook';
    // private static $pluginWebhookRetryUrl = 'https://webhook.plugin-stargate.store/api/v1/webhook';
    // private static $pluginWebhookRetryUrl = 'https://webhook-dev.plugin-stargate.store/api/v1/webhook';

    /**
     * Init.
     *
     * @return void
     */
    public function init()
    {
        parent::init();

        try {
            switch ($_SERVER['REQUEST_METHOD']) {
                case 'POST':
                    $this->processPostRequest();

                    break;
                default:
                    $this->processAnyRequest();

                    break;
            }
        } catch(SibsException $e) {
            $this->errorResponse($e->getErrorMessage());
        } catch(Exception $e) {
            $this->criticalResponse($e->getMessage());
        }
    }

    /**
     * Response With Success.
     *
     * @param string $notificationID
     * @return void
     */
    public function successResponse($notificationID)
    {
        $this->send_webhook_to_retry();

        $this->jsonResponse(200, 'Success', [
            'notificationID' => $notificationID,
        ]);
    }

    /**
     * Response With Error.
     *
     * @return void
     */
    public function errorResponse($errorMessage)
    {
        SibsLogger::error($errorMessage);
        $this->send_webhook_to_retry();

        $this->jsonResponse(400, $errorMessage);
    }

    /**
     * Response With Critical.
     *
     * @return void
     */
    public function criticalResponse($errorMessage)
    {
        SibsLogger::error($errorMessage);
        $this->send_webhook_to_retry();

        $this->jsonResponse(500, $errorMessage);
    }

    /**
     * Json Response
     *
     * @param integer $code
     * @param string $message
     * @param array $extra
     * @return void
     */
    protected function jsonResponse(int $code, string $message, array $extra = []): void
    {
        http_response_code($code);
        header('Content-Type: application/json');

        echo json_encode(array_merge([
            'statusCode' => $code,
            'statusMsg'  => $message,
        ], $extra));

        exit;
    }

    /**
     * Set Webhook Variables.
     *
     * @return void
     * @throws SibsException
     */
    public function setWebhookVariables()
    {
        // GET Webhook Secret
        $this->webhookSecret = Configuration::get('SIBS_WEBHOOK_SECRET');
        SibsLogger::info('webhookSecret: ' . $this->webhookSecret);

        if (empty($this->webhookSecret)) {
            throw new SibsException('webhookSecret is not defined', 400);
        }

        // GET HEADERS
        $headers = apache_request_headers();
        foreach ($headers as $_key => $_value) {
            unset($headers[$_key]);
            $headers[strtolower($_key)] = $_value;
        }
        SibsLogger::info('Headers: ' . SibsLogger::prettify($headers));

        // GET HEADER X-Initialization-Vector
        $this->headerInitVec = $headers['x-initialization-vector'];
        SibsLogger::info('X-Initialization-Vector: ' . $this->headerInitVec);

        if (empty($this->headerInitVec)) {
            throw new SibsException('X-Initialization-Vector is not present in header', 400);
        }

        // GET HEADER X-Authentication-Tag
        $this->headerAuthTag = $headers['x-authentication-tag'];
        SibsLogger::info('X-Authentication-Tag: ' . $this->headerAuthTag);

        if (empty($this->headerAuthTag)) {
            throw new SibsException('X-Authentication-Tag is not present in header', 400);
        }

        // GET HEADER Sibs-Webhook
        $this->headerSibsWebhook = $headers['sibs-webhook'] ?? null;
        SibsLogger::info('Sibs-Webhook: ' . $this->headerSibsWebhook);

        // GET BODY
        $this->body = Tools::file_get_contents('php://input');
        SibsLogger::info('Body: ' . $this->body);

        if (empty($this->body)) {
            throw new SibsException('Body is empty', 400);
        }
    }

    /**
     * Process Any Request.
     *
     * @return void
     * @throws SibsException
     */
    public function processAnyRequest()
    {
        throw new SibsException('Not Found', 404);
    }

    /**
     * Process POST Request.
     *
     * @return void
     * @throws SibsException
     */
    public function processPostRequest()
    {
        $this->setWebhookVariables();

        $webhookData = (new SibsWebhookDecryptor(
            $this->body,
            $this->headerInitVec,
            $this->headerAuthTag,
            $this->webhookSecret
        ))->execute();
        $webhookBodyDecrypted = $webhookData->getContent();

        $paymentMethod  = $webhookBodyDecrypted['paymentMethod'];
        $paymentStatus  = $webhookBodyDecrypted['paymentStatus'];
        $paymenType     = $webhookBodyDecrypted['paymentType'];
        $notificationID = $webhookBodyDecrypted['notificationID'];
        $transactionID  = $webhookBodyDecrypted['transactionID'];

        if ($transactionID == 'WebhookTest') {
            $this->successResponse($notificationID);
        }

        $orderID = $this->getOrderID($paymentMethod, $paymenType, $transactionID);

        SibsLogger::info("paymentMethod: $paymentMethod");
        SibsLogger::info("paymentStatus: $paymentStatus");
        SibsLogger::info("paymenType: $paymenType");
        SibsLogger::info("notificationID: $notificationID");
        SibsLogger::info("transactionID: $transactionID");
        SibsLogger::info("orderID: $orderID");

        // check if order exist
        if (empty($orderID)) {
            $orderIDTmp = $this->getCartID($transactionID);

            $cart               = new Cart((int) $orderIDTmp['cart_id']);
            $customer           = new Customer($cart->id_customer);
            $currency           = new Currency($cart->id_currency);
            $id_order_state     = (int) SibsPaymentCore::sibs_convert_response_status_to_payment_status($paymentMethod, $paymentStatus);
            $paymentMethodTools = $paymentMethod;
            $amountPaid         = (float) $cart->getOrderTotal();
            $paymentDescription = $this->module->getPaymentDescription($paymentMethodTools);
            $extraVars          = ['transaction_id' => $transactionID];
            $message            = $this->l('Paid by SIBS');
            $currency_id        = (int) $currency->id;
            $secure_key         = $customer->secure_key;

            $this->module->validateOrder(
                $cart->id,
                $id_order_state,
                $amountPaid,
                $paymentDescription . 'SIBS Credit Card - Debit',
                $message,
                $extraVars,
                $currency_id,
                false,
                $secure_key
            );
            $orderID = $this->getPrestaIdOrderByIdCart($orderIDTmp['cart_id']);

            if (empty($orderID)) {
                throw new SibsException("Order is not from this shop: $transactionID", 400);
            }
        }

        switch($paymenType) {
            case SibsConstants::PAYMENT_TYPE_AUTH:
            case SibsConstants::PAYMENT_TYPE_PURS:
                $this->finishWithPaymentOrAuthorization($orderID, $paymentMethod, $paymentStatus);

                break;
            case SibsConstants::PAYMENT_TYPE_RFND:
                $this->finishWithRefund($orderID, $paymentMethod, $paymentStatus, $transactionID);

                break;
        }

        $this->successResponse($notificationID);
    }

    /**
     * Get Prestashop Order ID by Cart ID.
     *
     * @param int $id_cart
     * @return int
     */
    public static function getPrestaIdOrderByIdCart($id_cart)
    {
        if (empty($id_cart)) {
            return false;
        }
        $query = new DbQuery();
        $query->select('id_order')
            ->from('orders')
            ->where("id_cart = '" . pSQL($id_cart) . "'");

        return Db::getInstance()->getValue($query);
    }

    /**
     * Finish Webhook with Payment Or Authorization process.
     *
     * @param int $orderID
     * @param string $paymentMethod
     * @param string $paymentStatus
     * @return void
     */
    public function finishWithPaymentOrAuthorization($orderID, $paymentMethod, $paymentStatus)
    {
        $order              = new Order($orderID);
        $orderPaymentStatus = SibsPaymentCore::sibs_convert_response_status_to_payment_status($paymentMethod, $paymentStatus);

        $notHistoryStates = [
            Configuration::get(SibsConstants::ORDER_STATUS_PAYMENT),
        ];

        if (in_array($order->getCurrentState(), $notHistoryStates)) {
            SibsLogger::warning("Order it's already paid: {$order->id}");

            return;
        }

        SibsPaymentCore::saveOrderHistoryWithEmail($orderID, $orderPaymentStatus);
    }

    /**
     * Finish Webhook with Refund process.
     *
     * @param int $orderID
     * @param string $paymentMethod
     * @param string $refundStatus
     * @param string $transactionID
     * @return void
     */
    public function finishWithRefund($orderID, $paymentMethod, $refundStatus, $transactionID)
    {
        $order = new Order($orderID);

        $asyncRefundMethods = [
            SibsConstants::PAYMENT_METHOD_BLIK,
        ];

        if (! in_array($paymentMethod, $asyncRefundMethods)) {
            return;
        }

        SibsOrderRef::updateOrderRefRefundStatus(
            $orderID,
            SibsPaymentCore::sibs_convert_response_status_to_capture_payment_status($refundStatus)
        );

        if (! SibsPaymentCore::sibs_accept_response_status($refundStatus)) {
            SibsLogger::warning("Refund ERROR for order: {$order->id}");

            $message = SibsUtils::replaceKeyToData($this->l('Refund error for order #[orderId] with transaction ID: [transaction]'), [
                'orderId'     => $order->id,
                'transaction' => $transactionID,
            ]);

            SibsOrderRef::updateOrderRefRefundMessage($orderID, $message);

            return;
        }

        $orderRefundStatus = SibsPaymentCore::getOrderRefundStatus($refundStatus);

        if ($orderRefundStatus === SibsConstants::ORDER_STATUS_SIBS_REFUND_PENDING) {
            SibsLogger::warning("Refund is still pending for order: {$order->id}");

            return;
        } elseif ($orderRefundStatus === SibsConstants::ORDER_STATUS_REFUND) {
            $message = SibsUtils::replaceKeyToData($this->l('Refund pending for order #[orderId]'), [
                'orderId' => $orderID,
            ]);

            SibsPaymentCore::saveOrderHistory($orderID, $orderRefundStatus);
            SibsOrderRef::updateOrderRefRefundMessage($orderID, $message);

            return;
        }
    }

    /**
     * Get Order ID.
     *
     * @param string $paymentMethod
     * @param string $paymenType
     * @param string $transactionID
     * @return string
     */
    public function getOrderID($paymentMethod, $paymenType, $transactionID)
    {
        $use_id_refunded = [
            SibsConstants::PAYMENT_METHOD_BLIK,
        ];

        $attributeIdField = ($paymenType === SibsConstants::PAYMENT_TYPE_RFND && in_array($paymentMethod, $use_id_refunded)) ?
            'id_refunded' :
            'id_transaction';

        return SibsOrderRef::getOrderIdByCustomAttribute($transactionID, $attributeIdField);
    }

    /**
     * Get Card ID.
     *
     * @param string $transactionID
     * @return string
     */
    public function getCartID($transactionID)
    {
        return SibsTransaction::getCartIdByCustomAttribute($transactionID, 'id_transaction');
    }

    /**
     * Create Prestashop Order.
     *
     * @param Cart $cart
     * @param float $amount
     * @param string $currencyIsoCode
     * @param int $orderStateId
     * @param string $transactionId
     * @param string $secureKey
     * @return Order
     */
    public function createPrestashopOrder(
        Cart $cart,
        float $amount,
        string $currencyIsoCode,
        int $orderStateId,
        string $transactionId,
        string $secureKey
    ) {
        $order = null;

        if (
            $this->module->active
            && (int) $cart->id_customer > 0
            && (int) $cart->id_address_delivery > 0
            && (int) $cart->id_address_invoice > 0
            && $secureKey === $cart->secure_key
        ) {
            if (! self::orderExistsForCart($cart)) {
                $currencyId = !empty($currencyIsoCode)
                    ? (int) Currency::getIdByIsoCode($currencyIsoCode)
                    : 0;

                $success = $this->module->validateOrder(
                    (int) $cart->id,
                    $orderStateId,
                    $amount,
                    $this->module->displayName,
                    null,
                    !empty($transactionId) ? ['transaction_id' => $transactionId] : [],
                    $currencyId ?: null,
                    false,
                    $cart->secure_key ?: false
                );

                if ($success && !empty($this->module->currentOrder)) {
                    $order = new Order((int) $this->module->currentOrder);
                }
            } else {
                $orderId = self::getOrderIdByCartId((int) $cart->id);

                if ($orderId) {
                    $order = new Order($orderId);
                }
            }
        }

        if (empty($order) || !Validate::isLoadedObject($order)) {
            SibsLogger::error('Create Order unsuccessful');
        }

        return $order;
    }

    /**
     * Order Exists For Cart.
     *
     * @param Cart $cart
     * @return bool
     */
    public static function orderExistsForCart($cart)
    {
        Cache::clean('Cart::orderExists_' . (int) $cart->id);

        return $cart->orderExists();
    }

    /**
     * Get Order ID by Cart ID (PS 1.7 / 8 / 9 safe).
     */
    protected static function getOrderIdByCartId(int $cartId): ?int
    {
        $id = (int) Db::getInstance()->getValue(
            'SELECT id_order FROM '._DB_PREFIX_.'orders WHERE id_cart = '.(int) $cartId
        );

        return $id > 0 ? $id : null;
    }


    /**
     * Send Webhook to Retry.
     *
     * @return void
     */
    public function send_webhook_to_retry()
    {
        if (! is_null($this->headerSibsWebhook)) {
            SibsLogger::warning('Webhook will NOT send a retry request');

            return;
        }

        SibsLogger::info('Webhook will send a retry request');

        $curl = curl_init();

        curl_setopt_array($curl, [
            CURLOPT_URL            => self::$pluginWebhookRetryUrl,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING       => '',
            CURLOPT_MAXREDIRS      => 10,
            CURLOPT_TIMEOUT        => 0,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST  => 'POST',
            CURLOPT_POSTFIELDS     => [
                'x_initialization_vector' => $this->headerInitVec,
                'x_authentication_tag'    => $this->headerAuthTag,
                'url_client'              => self::getWebhookUrl(),
                'body'                    => $this->body,
            ],
        ]);

        curl_exec($curl);

        curl_close($curl);
    }

    /**
     * Get Webhook URL.
     *
     * @return string
     */
    public static function getWebhookUrl()
    {
        $shopUrl    = Tools::getHttpHost(true) . __PS_BASE_URI__;
        $baseUrl    = str_replace('/shop', '', $shopUrl);
        $webhookUrl = $baseUrl . 'sibs/webhook';

        return $webhookUrl;
    }
}
