<?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\SibsConstants;
use Sibs\SibsException;
use Sibs\SibsLog;
use Sibs\SibsModels;
use Sibs\SibsPaymentCore;
use Sibs\SibsUtils;

class SibsWebHookModuleFrontController extends SibsPaymentAbstractModuleFrontController
{
    public function init()
    {
        parent::init();

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

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

                    break;
            }
        } catch(SibsException $e) {
            SibsLog::error($e->getErrorMessage());

            header('HTTP/1.1 400 Bad Request');

            $this->ajaxDie(
                json_encode([
                    'statusCode' => $e->getCode(),
                    'statusMsg'  => $e->getErrorMessage(),
                ])
            );
        } catch(Exception $e) {
            SibsLog::critical($e->getMessage());

            header('HTTP/1.1 500 Internal Server Error');

            $this->ajaxDie(
                json_encode([
                    'statusCode' => $e->getCode(),
                    'statusMsg'  => $e->getMessage(),
                ])
            );
        }
    }

    public function success($notificationID)
    {
        header('HTTP/1.1 200 Success');

        $this->ajaxDie(
            json_encode([
                'statusCode'     => 200,
                'statusMsg'      => 'Success',
                'notificationID' => $notificationID,
            ])
        );
    }

    public function processAnyRequest()
    {
        throw new SibsException('Not Found', 404);
    }

    public function processPostRequest()
    {
        $result = self::sodium_descryption();

        $body = json_decode($result, true);
        SibsLog::info('Decripted Body' . print_r($body, true));

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

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

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

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

        // check if order exist
        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->success($notificationID);
    }

    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)) {
            SibsLog::warning("Order it's already paid: {$order->id}");

            return;
        }

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

    public function finishWithRefund($orderID, $paymentMethod, $refundStatus, $transactionID)
    {
        $order = new Order($orderID);

        $asyncRefundMethods = [
            SibsConstants::PAYMENT_METHOD_BLIK,
        ];

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

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

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

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

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

            return;
        }

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

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

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

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

            return;
        }
    }

    public function getSuccessTranslatedMessage($message, $data)
    {
        foreach ($data as $key => $value) {
            $message = str_replace("[$key]", $value, $this->l($message));
        }

        return $message;
    }

    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 SibsModels::getOrderIdByCustomAttribute($transactionID, $attributeIdField);
    }

    public static function sodium_descryption()
    {
        $webhookSecret = Configuration::get('SIBS_WEBHOOK_SECRET');
        SibsLog::info("webhookSecret: {$webhookSecret}");

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

        $headers = apache_request_headers();

        foreach ($headers as $_key => $_value) {
            if (SibsUtils::strContains(strtolower($_key), 'x-init') || SibsUtils::strContains(strtolower($_key), 'x-auth')) {
                unset($headers[$_key]);
                $headers[strtolower($_key)] = $_value;
            }
        }

        $iv_from_http_header       = $headers['x-initialization-vector'];
        $auth_tag_from_http_header = $headers['x-authentication-tag'];
        SibsLog::info("vector: {$iv_from_http_header}");
        SibsLog::info("tag: {$auth_tag_from_http_header}");

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

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

        $http_body = Tools::file_get_contents('php://input');

        if (empty($http_body)) {
            throw new SibsException('Body is empty', 400);
        }

        SibsLog::info("http_body: $http_body");
        SibsLog::info("iv_from_http_header: $iv_from_http_header");
        SibsLog::info("auth_tag_from_http_header: $auth_tag_from_http_header");

        $key         = self::sibs_convert_encode($webhookSecret);
        $iv          = self::sibs_convert_encode($iv_from_http_header);
        $cipher_text = self::sibs_convert_encode($auth_tag_from_http_header, $http_body);

        $PHPSodiumAvailable        = false;
        $PHPSodiumCompactAvailable = false;

        if (function_exists('sodium_crypto_aead_aes256gcm_is_available') && sodium_crypto_aead_aes256gcm_is_available()) {
            $PHPSodiumAvailable = true;
        }

        if ($PHPSodiumAvailable) {
            $webhook_sodium_dir = str_replace('webhook.php', '', __DIR__) .
                DIRECTORY_SEPARATOR . 'vendor' .
                DIRECTORY_SEPARATOR . 'sodium_compat-1.7.0' .
                DIRECTORY_SEPARATOR . 'autoload.php';

            SibsLog::info("webhook_sodium_dir: $webhook_sodium_dir");
            require_once $webhook_sodium_dir;

            if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available')) {
                if (\Sodium\crypto_aead_aes256gcm_is_available()) {
                    SibsLog::info('webhook sodium compact: ON');
                    $PHPSodiumCompactAvailable = true;
                }
            }
        }

        if (! $PHPSodiumAvailable) {
            $result = sodium_crypto_aead_aes256gcm_decrypt($cipher_text, '', $iv, $key);

            if ($result == false) {
                $result = sodium_crypto_aead_aes256gcm_decrypt($cipher_text, null, $iv, $key);

                if ($result == false) {
                    throw new SibsException('Bad ciphertext phpsodium', 400);
                }
            }

            return $result;
        } elseif ($PHPSodiumCompactAvailable) {
            $result = \Sodium\crypto_aead_aes256gcm_decrypt($cipher_text, '', $iv, $key);

            if ($result == false) {
                throw new SibsException('Bad ciphertext compact', 400);
            }

            return $result;
        }

        throw new SibsException('Sodium lib is not available..', 400);
    }

    public static function sibs_convert_encode($string, $http_body = null)
    {
        $to_encoding   = 'UTF-8';
        $from_encoding = 'BASE64';
        // $from_encoding = 'aes-256-gcm';

        // DPG: hex2bin - SPG: mb_convert_encoding
        if (is_null($http_body)) {
            if (ctype_xdigit($string)) {
                return hex2bin($string);
            }

            return mb_convert_encoding($string, $to_encoding, $from_encoding);
        } else {
            if (ctype_xdigit($http_body . $string)) {
                return hex2bin($http_body . $string);
            }

            return mb_convert_encoding($http_body, $to_encoding, $from_encoding) .
                mb_convert_encoding($string, $to_encoding, $from_encoding);
        }
    }
}
