<?php
/**
 * Class EvoPay
 *
 * @package Mtc\Plugins\EvoPay\Classes
 * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 * @author Uldis Zvirbulis <uldis.zvirbulis@mtcmedia.co.uk>
 */
namespace Mtc\Plugins\EvoPay\Classes;

use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Mtc\Shop\Order;

/**
 * Class EvoPay
 *
 * EvoPay payment processing class.
 * Supports initializing and processing a payment.
 *
 * @package Mtc\Plugins\EvoPay\Classes
 * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 * @author Uldis Zvirbulis <uldis.zvirbulis@mtcmedia.co.uk>
 */
class EvoPay
{
    /**
     * Render payment form.
     * The form is used in basket overview step to offer user payment through this gateway.
     *
     * @param \Basket $basket current users basket
     * @return string the payment form template
     */
    public static function form(\Basket $basket)
    {
        $merchant_id = EVOPAY_TEST_MODE ? EVOPAY_TEST_MERCHANT_ID : EVOPAY_LIVE_MERCHANT_ID;
        $hcmac_key = EVOPAY_TEST_MODE ? EVOPAY_TEST_HMAC_KEY : EVOPAY_LIVE_HMAC_KEY;
        $amount = round($basket->cost_total * 100);

        try {
            $transToken = bin2hex(random_bytes(4));
        } catch (\Exception $exception) {
            $transToken = substr(md5(microtime(true)), -8);
        }

        $trans_id = $basket->order_id . '-' . $transToken;

        \Mtc\Shop\Order::query()
            ->where('id', $basket->order_id)
            ->update(['order_ref' => $trans_id]);
        $currency = 'GBP';

        $hcmac_params = [
            '', // PayID that we won't be using
            $trans_id,
            $merchant_id,
            $amount,
            $currency
        ];
        $hcmac = hash_hmac('sha256', implode('*', $hcmac_params), $hcmac_key);

        $form_data = urldecode(http_build_query([
            'MerchantID' => $merchant_id,
            'TransID' => $trans_id,
            'RefNr' => $basket->id,
            'Amount' => $amount,
            'Currency' => $currency,
            'URLSuccess' => SITE_URL . '/plugins/EvoPay/response_success.php',
            'URLFailure' => SITE_URL . '/plugins/EvoPay/response_failure.php',
            'URLNotify' => SITE_URL . '/plugins/EvoPay/response_notify.php',
            'OrderDesc' => config('app.name') . ' purchase',
            'MAC' => $hcmac,
        ]));
        //echo $form_data;

        $twig = App::make('twig');
        return $twig->render('Evopay\form.twig', [
            'basket' => $basket,
            'merchant_id' => EVOPAY_TEST_MODE ? EVOPAY_TEST_MERCHANT_ID : EVOPAY_LIVE_MERCHANT_ID,
            'length' => strlen($form_data),
            'data' => self::formEncrypt($form_data)
        ]);
    }

    /**
     * Processes EvoPay response from request.
     * Returns decoded attributes
     *
     * @param $request
     * @return array|bool
     */
    public static function processResponse($request)
    {
        $key = EVOPAY_TEST_MODE ? EVOPAY_TEST_ENCRYPTION_KEY : EVOPAY_LIVE_ENCRYPTION_KEY;
        $raw_data = $request['Data'];
        $len = $request['Len'];
        // converts hex to bin
        $raw_data = pack('H' . strlen($raw_data), $raw_data);
        if ($len > strlen($raw_data)) {
            echo 'Length mismatch. The parameter len is too large.';
            return false;
        }

        $blowfish = new Blowfish();
        $blowfish->bf_set_key($key);

        $query_string = mb_substr($blowfish->decrypt($raw_data), 0, $len);
        $query_params = explode('&', $query_string);
        $data = [];
        foreach ($query_params as $query_param) {
            $param_parts = explode('=', $query_param);
            $data[$param_parts[0]] = $param_parts[1];
        }
        $data['len'] = $len;

        self::logResponse($data);

        return $data;
    }

    /**
     * Simple method for confirming that all required components are on site.
     * This way we ensure that there are no missing dependencies.
     *
     * @param string $class_to_confirm Class name from the required component
     * @param string $module_name verbose component name that explains what is missing
     * @throws \Exception
     */
    public static function confirmComponentExists($class_to_confirm, $module_name)
    {
        if (!class_exists($class_to_confirm)) {
            throw new \Exception("{$module_name} is required by EvoPay");
        }
    }

    /**
     * Encrypts form data
     *
     * @param $data
     * @return string
     */
    private static function formEncrypt($data)
    {
        $key = EVOPAY_TEST_MODE ? EVOPAY_TEST_ENCRYPTION_KEY : EVOPAY_LIVE_ENCRYPTION_KEY;
        $blowfish = new Blowfish();
        $plaintext = $blowfish->expand($data);
        $blowfish->bf_set_key($key);

        return bin2hex($blowfish->encrypt($plaintext));
    }

    /**
     * Logs EvoPay response into database
     *
     * @param $data
     * @return bool
     */
    private static function logResponse($data)
    {
        DB::table('evopay_responses')->insert([
            'order_id' => self::extractOrderId($data['TransID']) ?? $data['TransID'],
            'status' => $data['Status'],
            'response' => json_encode($data),
            'created_at' => date('Y-m-d H:i:s'),
        ]);
        return true;
    }


    public static function extractOrderId($transId)
    {
        if ($transId === null || $transId === '') {
            return null;
        }

        $orderReference = (string) $transId;
        if (strpos($orderReference, '-') !== false) {
            $orderReference = strstr($orderReference, '-', true);
        }

        if (!ctype_digit($orderReference)) {
            if (preg_match('/(\d+)$/', $orderReference, $matches)) {
                $orderReference = $matches[1];
            } else {
                return null;
            }
        }

        return (int) $orderReference;
    }
}
