<?php


namespace PayU\MerchantClientApi;


/**
 * PHP API class for PayU Token
 *
 * @package PayU\MerchantClientApi
 */
class TokenService
{
    /**
     * @var string
     */
    private $endpointUrl;

    /**
     * @var string
     */
    private $merchantCode;

    /**
     * @var string
     */
    private $merchantKey;

    /**
     * @var resource
     */
    private $curl;

    /**
     * Class constructor
     *
     * @param string $endpointUrl Endpoint URL for API calls mentioned in documentation
     * @param string $merchantCode Merchant code
     * @param string $merchantKey Merchant key
     */
    public function __construct($endpointUrl, $merchantCode, $merchantKey)
    {
        $this->endpointUrl = $endpointUrl;
        $this->merchantCode = $merchantCode;
        $this->merchantKey = $merchantKey;
    }

    /**
     * Creates a new sale using the token
     *
     * @param string $code The token number
     * @param float|int $amount New order amount
     * @param string $currency Price currency
     * @param string $externalRef Merchant Reference Number for the transaction
     * @param array $additionalData
     * @return mixed
     */
    public function newSale($code, $amount, $currency, $externalRef, array $additionalData = array())
    {
        $data = array(
            'REF_NO' => $code,
            'METHOD' => 'TOKEN_NEWSALE',
            'AMOUNT' => $amount,
            'CURRENCY' => $currency,
            'EXTERNAL_REF' => $externalRef,
        );
        return $this->request(
            array_merge($data, $additionalData)
        );
    }

    /**
     * Executes the request to PayU service
     *
     * @param array $params
     * @return mixed
     */
    private function request(array $params)
    {
        $result = $this->runCurl($this->endpointUrl, $params);

        return $this->decodeResponse($result);
    }

    /**
     * @param string $url
     * @param array $params
     * @return bool|string
     * @throws \Exception
     */
    private function runCurl($url, array $params)
    {
        if (!is_resource($this->curl)) {
            $this->curl = curl_init($url);

            curl_setopt_array(
                $this->curl,
                array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_SSL_VERIFYPEER => false,
                    CURLOPT_SSL_VERIFYHOST => 2,
                    CURLOPT_FOLLOWLOCATION => false,
                    CURLOPT_USERAGENT => __CLASS__,
                    CURLOPT_POST => true,
                )
            );
        }

        $params = array_merge(
            $params,
            array(
                'MERCHANT' => $this->merchantCode,
                'TIMESTAMP' => gmdate('YmdHis'),
            )
        );

        unset($params['SIGN']);

        $params['SIGN'] = $this->calculateSignature($params);

        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $params);

        $result = curl_exec($this->curl);

        if (false === $result) {
            throw new \Exception('Curl failed: ' . curl_error($this->curl));
        }

        $httpCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);

        curl_close($this->curl);

        if (200 != $httpCode) {
            throw new \Exception('Unexpected HTTP code: ' . $httpCode);
        }

        return $result;
    }

    /**
     * Calculates the signature
     *
     * @param array $params
     * @return string
     */
    private function calculateSignature(array $params)
    {
        ksort($params);
        $hashString = '';

        foreach ($params as $v) {
            $hashString .= strlen($v) . $v;
        }

        return hash_hmac('md5', $hashString, $this->merchantKey);
    }

    /**
     * @param string $result
     * @return mixed
     * @throws \Exception
     */
    private function decodeResponse($result)
    {
        $retval = json_decode($result, true);
        if (JSON_ERROR_NONE !== json_last_error()) {
            throw new \Exception('JSON decode error');
        }
        return $retval;
    }

    /**
     * Get info about the token
     *
     * @param string $code The token number
     * @return mixed
     */
    public function getInfo($code)
    {
        return $this->request(
            array(
                'REF_NO' => $code,
                'METHOD' => 'TOKEN_GETINFO',
            )
        );
    }

    /**
     * Cancel the token
     *
     * @param string $code The token number
     * @param string $reason Reason for cancelling the order
     * @return mixed
     */
    public function cancel($code, $reason)
    {
        return $this->request(
            array(
                'REF_NO' => $code,
                'METHOD' => 'TOKEN_CANCEL',
                'CANCEL_REASON' => $reason,
            )
        );
    }

    /**
     * Persist the token
     *
     * @param string $code The token number
     * @return mixed
     */
    public function persist($code)
    {
        return $this->request(
            array(
                'REF_NO' => $code,
                'METHOD' => 'TOKEN_PERSIST',
            )
        );
    }

    /**
     * Frees the resource when the object is destroyed
     */
    public function __destruct()
    {
        if (is_resource($this->curl)) {
            curl_close($this->curl);
        }
    }
}
