<?php

use Mnv\Modules\Payment\PaymentMethod;


/**
 * Class ClickAPI
 */
class ClickAPI
{
    private string $_table = 'shop_orders';
    private string $primaryKey = 'order_id';

    private $request = [];
    private $response = [];

    private $payment_method;
    private $secret;

    /**
     * ClickAPI constructor.
     */
    public function __construct()
    {

        $payment_method = new PaymentMethod();
        $this->payment_method = $payment_method->get_payment('click');
        $this->secret = $this->payment_method['settings']['secret_key'];

        $this->callback();
    }

    public function callback()
    {
        if (isset($_GET['payment_status'])) {
            switch ($_GET['payment_status']) {
                case 0:
                    header('Location: ' . SITE_URL.'/success/');
                    exit;
                case -10000:
                case -4008:
                    header('Location: ' . SITE_URL.'/cancel/');
                    exit;
            }
        }

    }

    /**
     * @throws ClickException
     */
    public function run()
    {
        header('Content-Type: application/json; charset=UTF-8');

        // set request
        $this->request = $_POST;

        if (count($this->request) == 0) {
            // setting rest api request body
            $request_body = file_get_contents('php://input');
            // parsing the request body
            $this->request = json_decode($request_body, true);
            // check request to possible
            if (!$this->request) {
                throw new ClickException('Incorrect JSON-RPC object', ClickException::ERROR_INVALID_JSON_RPC_OBJECT);
            }
        }

        if (isset($this->request['action']) && $this->request['action'] != null) {

            if ((int)$this->request['action'] == 0) {
                $this->response = $this->prepare();
            }

            if ((int)$this->request['action'] == 1) {
                $this->response = $this->complete();
            }

            echo json_encode($this->response);

        } else {
            echo json_encode(array('status' => 403, 'message' => 'empty_post'));
        }

        exit;
    }

    /**
     * @param null $request
     * @return array
     */
    public function prepare($request = null)
    {
        if ($request == null) $request = $_POST;

        $merchant_confirm_id = 0;
        $merchant_prepare_id = 0;
        $order = $this->find_by_merchant_trans_id($request['merchant_trans_id']);
        if ($order) {
            $merchant_confirm_id = $order[$this->primaryKey];
            $merchant_prepare_id = $order[$this->primaryKey];
        }

        $result = $this->request_check($request);
        $result += [
            'click_trans_id'      => $request['click_trans_id'],
            'merchant_trans_id'   => $request['merchant_trans_id'],
            'merchant_confirm_id' => $merchant_confirm_id,
            'merchant_prepare_id' => $merchant_prepare_id
        ];

        if ($result['error'] == 0) {

            $this->update_by_id($order[$this->primaryKey], [
                'click_trans_id'    => $request['click_trans_id'],
                'merchant_trans_id' => $request['merchant_trans_id'],
                'click_paydoc_id'   => $request['click_paydoc_id'],
                'error_note'        => $request['error_note'],
                'error'             => $request['error'],
                'status'            => PaymentsStatus::WAITING,
                'state'             => 1,

            ]);
        }

        return $result;

    }

    /**
     * @param null $request
     * @return array
     */
    public function complete($request = null)
    {

        if ($request == null) $request = $_POST;

        $merchant_confirm_id = 0;
        $merchant_prepare_id = 0;
        $order = $this->find_by_merchant_trans_id($request['merchant_trans_id']);
        if (!empty($order)) {
            $merchant_confirm_id = $order[$this->primaryKey];
            $merchant_prepare_id = $order[$this->primaryKey];
        }

        $result = $this->request_check($request);
        $result += [
            'click_trans_id'      => $request['click_trans_id'],
            'merchant_trans_id'   => $request['merchant_trans_id'],
            'merchant_confirm_id' => $merchant_confirm_id,
            'merchant_prepare_id' => $merchant_prepare_id
        ];

        if ($request['error'] < 0 && !in_array($result['error'], [-4, -9])) {

            $this->update_by_id($order[$this->primaryKey], [
                'status' => PaymentsStatus::REJECTED,
                'state'  => 3
            ]);

            $result = ['error' => -9, 'error_note' => 'Transaction cancelled'];

        } elseif ($result['error'] == 0) {

            $this->update_by_id($order[$this->primaryKey], [
                'status' => PaymentsStatus::CONFIRMED,
                'state'  => 2
            ]);

        }

        return $result;
    }


    public function request_check($request)
    {
        if ($this->is_not_possible_data()) {
            return [ 'error' => -8, 'error_note' => 'Error in request from click' ];
        }

        $sign_string = $request['click_trans_id']
            . $request['service_id']
            . $this->secret
            . $request['merchant_trans_id']
            . ($request['action'] == 1 ? $request['merchant_prepare_id'] : '')
            . $request['amount']
            . $request['action']
            . $request['sign_time'];

        $sign_string = md5($sign_string);

        if ($sign_string != $request['sign_string']) {
            return [ 'error' => -1, 'error_note' => 'SIGN CHECK FAILED!' ];
        }

        if (!((int) $request['action'] == 0 || (int) $request['action'] == 1)) {
            return [ 'error' => -3, 'error_note' => 'Action not found' ];
        }

        $order = $this->find_by_merchant_trans_id($request['merchant_trans_id']);
        if (!$order) {
            return [ 'error' => -5, 'error_note' => 'User does not exist' ];
        }

        if ($request['action'] == 1) {
            $order = $this->find_by_id($request['merchant_prepare_id']);
            if(!$order) {
                return [ 'error' => -6, 'error_note' => 'Transaction does not exist'];
            }
        }

        if ($order['status'] == PaymentsStatus::CONFIRMED) {
            return ['error' => -4, 'error_note' => 'Already paid'];
        }

        if (abs((float)$order['amount'] - (float)$request['amount']) > 0.01) {
            return ['error' => -2, 'error_note' => 'Incorrect parameter amount'];
        }

        if ($order['status'] == PaymentsStatus::REJECTED) {
            return ['error' => -9, 'error_note' => 'Transaction cancelled'];
        }

        return ['error' => 0, 'error_note' => 'Success'];

    }


    /**
     * @return bool
     */
    private function is_not_possible_data()
    {
        if (!(
                isset($_POST['click_trans_id']) &&
                isset($_POST['service_id']) &&
                isset($_POST['merchant_trans_id']) &&
                isset($_POST['amount']) &&
                isset($_POST['action']) &&
                isset($_POST['error']) &&
                isset($_POST['error_note']) &&
                isset($_POST['sign_time']) &&
                isset($_POST['sign_string']) &&
                isset($_POST['click_paydoc_id'])
            ) || $_POST['action'] == 1 && !isset($_POST['merchant_prepare_id'])) {

            return true;
        }
        return false;
    }


    /** SQL */

    /**
     * этот метод может найти данные платежа по merchant_trans_id
     *
     * @param $merchant_trans_id
     * @return bool|int
     */
    function find_by_merchant_trans_id($merchant_trans_id)
    {
        if ($result = connect($this->_table)->select('order_id, amount, status')->where($this->primaryKey, $merchant_trans_id)->get('array')) {
            $order = $result;
        } else {
            $order = false;
        }

        return $order;
    }


    /**
     * этот метод может найти данные платежа по order_id
     *
     * @param $order_id
     * @return bool|int
     */
    public function find_by_id($order_id)
    {
        if ($result = connect($this->_table)->select('order_id, amount, status')->where($this->primaryKey, $order_id)->get('array')) {
            $transaction = $result;
        } else {
            $transaction = false;
        }
        return $transaction;
    }

    /**
     * Обновление статуса заказа
     *
     * @param int $order_id
     * @param array $data
     */
    public function update_by_id($order_id, $data)
    {
        connect($this->_table)->where($this->primaryKey, $order_id)->update($data);
    }


}


/**
 * Class PaymentsStatus
 */
class PaymentsStatus
{
    /** @var WAITING string */
    const WAITING = 'waiting';

    /** @var CONFIRMED string */
    const CONFIRMED = 'confirmed';

    /** @var REJECTED string */
    const REJECTED  = 'rejected';

}



/**
 * Class ClickException
 */
class ClickException extends \Exception
{
    /** @var ERROR_INTERNAL_SYSTEM */
    const ERROR_INTERNAL_SYSTEM = -32400;

    /** @var ERROR_INSUFFICIENT_PRIVILEGE */
    const ERROR_INSUFFICIENT_PRIVILEGE = -32504;

    /** @var ERROR_INVALID_JSON_RPC_OBJECT */
    const ERROR_INVALID_JSON_RPC_OBJECT = -32600;

    /** @var ERROR_METHOD_NOT_FOUND */
    const ERROR_METHOD_NOT_FOUND = -32601;

    /** @var ERROR_INVALID_AMOUNT */
    const ERROR_INVALID_AMOUNT = -31001;

    /** @var ERROR_TRANSACTION_NOT_FOUND */
    const ERROR_TRANSACTION_NOT_FOUND = -31003;

    /** @var ERROR_INVALID_ACCOUNT */
    const ERROR_INVALID_ACCOUNT = -31050;

    /** @var ERROR_COULD_NOT_CANCEL */
    const ERROR_COULD_NOT_CANCEL = -31007;

    /** @var ERROR_COULD_NOT_PERFORM */
    const ERROR_COULD_NOT_PERFORM = -31008;

    /** @var error array-like */
    public $error;

    public $error_note;
    public $error_code;

    /**
     * ClickException contructor
     * @param error_note string
     * @param error_code integer
     */
    public function __construct($error_note, $error_code)
    {
        $this->error_note = $error_note;
        $this->error_code = $error_code;

        $this->error = ['error_code' => $this->error_code];

        if ($this->error_note) {
            $this->error['error_note'] = $this->error_note;
        }
    }

    /**
     * @name error method
     * @return error array-like
     */
    public function error()
    {
        return $this->error;
    }
}