<?php

namespace Mnv\Modules\Payment\Click;

use Mnv\Modules\Payment\PaymentMethod;

/**
 * Class ClickPayment
 * @package Mnv\Modules\Payment\Click
 */
class ClickPayment
{

    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(): void
    {
        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 ClickPaymentException
     */
    public function run(): void
    {
        header('Content-Type: application/json; charset=UTF-8');

        $this->request = $_POST;

        if (count($this->request) == 0) {
            $request_body = file_get_contents('php://input');
            $this->request = json_decode($request_body, true);
            if (!$this->request) {
                throw new ClickPaymentException('Incorrect JSON-RPC object', ClickPaymentException::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): array
    {
        if ($request == null) $request = $_POST;

        $order = $this->find_by_merchant_trans_id($request['merchant_trans_id']);

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

        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'            => ClickPaymentStatuses::WAITING,
                'state'             => 1,

            ]);
        }

        return $result;

    }

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

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

        $order = $this->find_by_merchant_trans_id($request['merchant_trans_id']);

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

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

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

            $result = array('error' => -9, 'error_note' => 'Transaction cancelled');
        }
        elseif ($result['error'] == 0) {

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

        }

        return $result;
    }


    public function request_check($request): array
    {
        if ($this->is_not_possible_data()) {
            return array('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 array('error' => -1, 'error_note' => 'SIGN CHECK FAILED!');
        }

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

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

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

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

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

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

        return array('error' => 0, 'error_note' => 'Success');

    }


    /**
     * @return bool
     */
    private function is_not_possible_data(): bool
    {
        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
     */
    private 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')) {
            return $result;
        }

        return [];
    }


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

        return false;
    }

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

}