<?php


namespace App\Library;

use Illuminate\Support\Facades\Cache;

class OrangeMoney
{
    const BASE_URL = 'https://api.orange.com';

    protected string $authorization_header;
    protected string $merchant_key;


    /**
     * The Token will be used for all further API calls.
     *
     * @var string
     */
    protected string $token = '';


    /**
     * @param  string  $authorization_header
     * @param  string  $merchant_key
     */
    public function __construct(string $authorization_header, string $merchant_key)
    {
        $this->authorization_header = $authorization_header;
        $this->merchant_key         = $merchant_key;
    }


    /**
     * Retrieves a token from Orange server, that will be used for all further API calls.
     *
     * @return array
     */
    public function getTokenFromConsumerKey(): array
    {
        $url      = self::BASE_URL.'/oauth/v3/token';
        $headers  = ['Authorization: '.$this->authorization_header];
        $args     = ['grant_type' => 'client_credentials'];
        $response = $this->callApi($headers, $args, $url, 'POST', 200);

        if ( ! empty($response['access_token'])) {
            $this->setToken($response['access_token']);
        }

        return $response;
    }

    /**
     * get payment url generated by orange
     *
     * @param  array  $data
     *
     * @return string[]
     */
    public function getPaymentUrl(array $data): array
    {

        $headers = [
                'Authorization: Bearer '.$this->getToken(),
                'Accept: application/json',
                'Content-Type' => 'application/json',
        ];

        return $this->callApi($headers, $data, $data['payment_url'], 'POST', 201, true);
    }


    /**
     *  Calls API Endpoints.
     *
     * @param  array  $headers  An array of HTTP header fields to set
     * @param  array  $args  The data to send
     * @param  string  $url  The URL to fetch
     * @param  string  $method  Whether to do a HTTP POST or a HTTP GET
     * @param  int  $successCode  The HTTP code that will be returned on
     *                                  success
     * @param  bool  $jsonEncodeArgs  Whether or not to json_encode $args
     *
     * @return array   Contains the results returned by the endpoint or an error
     *                 message
     */
    public function callApi(array $headers, array $args, string $url, string $method, int $successCode, bool $jsonEncodeArgs = false): array
    {
        $ch = curl_init();

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_POST, true);

            if ( ! empty($args)) {
                if ($jsonEncodeArgs === true) {
                    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($args));
                } else {
                    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($args));
                }
            }
        } else /* $method === 'GET' */ {
            if ( ! empty($args)) {
                curl_setopt($ch, CURLOPT_URL, $url.'?'.http_build_query($args));
            } else {
                curl_setopt($ch, CURLOPT_URL, $url);
            }
        }

        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        // Make sure we can access the response when we execute the call
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $data = curl_exec($ch);

        if ($data === false) {
            return ['error' => 'API call failed with cURL error: '.curl_error($ch)];
        }

        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        curl_close($ch);

        $response = json_decode($data, true);

        $jsonErrorCode = json_last_error();
        if ($jsonErrorCode !== JSON_ERROR_NONE) {
            return [
                    'error' => 'API response not well-formed (json error code: '.$jsonErrorCode.')',
            ];
        }

        if ($httpCode !== $successCode) {
            $errorMessage = '';

            if ( ! empty($response['error_description'])) {
                $errorMessage = $response['error_description'];
            } elseif ( ! empty($response['error'])) {
                $errorMessage = $response['error'];
            } elseif ( ! empty($response['description'])) {
                $errorMessage = $response['description'];
            } elseif ( ! empty($response['message'])) {
                $errorMessage = $response['message'];
            } elseif ( ! empty($response['requestError']['serviceException'])) {
                $errorMessage = $response['requestError']['serviceException']['text'].' '.$response['requestError']['serviceException']['variables'];
            } elseif ( ! empty($response['requestError']['policyException'])) {
                $errorMessage = $response['requestError']['policyException']['text'].' '.$response['requestError']['policyException']['variables'];
            }

            return ['error' => $errorMessage];
        }

        return $response;
    }


    /**
     *  Sets the Token value.
     *
     * @param  string  $token  the token
     */
    public function setToken(string $token): void
    {
        $this->token = $token;
    }


    /**
     *  Gets the (local/current) Token.
     *
     * @return string
     */
    public function getToken(): string
    {
        $token = Cache::get('OrangeMoneyAccessToken');

        if ( ! $token) {
            $this->getTokenFromConsumerKey();
            Cache::set('OrangeMoneyAccessToken', $this->token, 3600);

            return $this->token;
        }

        return $token;
    }

}
