<?php

namespace {{ spec.title | caseUcfirst }};

class Client
{
    const METHOD_GET = 'GET';
    const METHOD_POST = 'POST';
    const METHOD_PUT = 'PUT';
    const METHOD_PATCH = 'PATCH';
    const METHOD_DELETE = 'DELETE';
    const METHOD_HEAD = 'HEAD';
    const METHOD_OPTIONS = 'OPTIONS';
    const METHOD_CONNECT = 'CONNECT';
    const METHOD_TRACE = 'TRACE';

    const CHUNK_SIZE = 5 * 1024 * 1024;

    /**
     * Is Self Signed Certificates Allowed?
     *
     * @var bool
     */
    protected bool $selfSigned = false;

    /**
     * Service host name
     *
     * @var string
     */
    protected string $endpoint = '{{spec.endpoint}}';

    /**
     * Global Headers
     *
     * @var array
     */
    protected array $headers = [
        'content-type' => '',
        'user-agent' => '{{spec.title | caseUcfirst}}{{ language.name | caseUcfirst }}SDK/{{ sdk.version }} ({{deviceInfo}})',
        'x-sdk-name'=> '{{ sdk.name }}',
        'x-sdk-platform'=> '{{ sdk.platform }}',
        'x-sdk-language'=> '{{ language.name | caseLower }}',
        'x-sdk-version'=> '{{ sdk.version }}',
    ];

    /**
     * Client constructor.
     */
    public function __construct()
    {
{% for key,header in spec.global.defaultHeaders %}
        $this->headers['{{key}}'] = '{{header}}';
{% endfor %} 
    }

{% for header in spec.global.headers %}
    /**
     * Set {{header.key | caseUcfirst}}
     *
{% if header.description %}
     * {{header.description}}
     *
{% endif %}
     * @param string $value
     *
     * @return Client
     */
    public function set{{header.key | caseUcfirst}}(string $value): Client
    {
        $this->addHeader('{{header.name}}', $value);

        return $this;
    }

{% endfor %}

    /***
     * @param bool $status
     * @return $this
     */
    public function setSelfSigned(bool $status = true): Client
    {
        $this->selfSigned = $status;

        return $this;
    }

    /***
     * @param $endpoint
     * @return $this
     */
    public function setEndpoint(string $endpoint): Client
    {
        $this->endpoint = $endpoint;

        return $this;
    }

    /**
     * @param $key
     * @param $value
     */
    public function addHeader(string $key, string $value): Client
    {
        $this->headers[strtolower($key)] = $value;
        
        return $this;
    }

    /**
     * Call
     *
     * Make an API call
     *
     * @param string $method
     * @param string $path
     * @param array $params
     * @param array $headers
     * @return array|string
     * @throws {{spec.title | caseUcfirst}}Exception
     */
    public function call(
        string $method,
        string $path = '',
        array $headers = [],
        array $params = [],
        ?string $responseType = null
    )
    {
        $headers = array_merge($this->headers, $headers);
        $ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : ''));
        $responseHeaders = [];

        switch ($headers['content-type']) {
            case 'application/json':
                $query = json_encode($params);
                break;

            case 'multipart/form-data':
                $query = $this->flatten($params);
                break;

            default:
                $query = http_build_query($params);
                break;
        }

        foreach ($headers as $i => $header) {
            $headers[] = $i . ':' . $header;
            unset($headers[$i]);
        }

        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s') . '-' . php_uname('r') . ':{{ language.name | caseLower }}-' . phpversion());
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $responseType !== 'location');
        curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$responseHeaders) {
            $len = strlen($header);
            $header = explode(':', strtolower($header), 2);

            if (count($header) < 2) { // ignore invalid headers
                return $len;
            }

            $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);

            return $len;
        });

        if($method != self::METHOD_GET) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
        }

        // Allow self signed certificates
        if($this->selfSigned) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        }

        $responseBody   = curl_exec($ch);
        $contentType    = $responseHeaders['content-type'] ?? '';
        $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        $warnings = $responseHeaders['x-{{spec.title | caseLower}}-warning'] ?? '';
        if ($warnings) {
            foreach(explode(';', $warnings) as $warning) {
                echo 'Warning: ' . $warning . PHP_EOL;
            }
        }
        
        switch(substr($contentType, 0, strpos($contentType, ';'))) {
            case 'application/json':
                $responseBody = json_decode($responseBody, true);
            break;
        }

        if (curl_errno($ch)) {
            throw new {{spec.title | caseUcfirst}}Exception(curl_error($ch), $responseStatus, $responseBody['type'] ?? '', $responseBody);
        }
        
        curl_close($ch);

        if($responseStatus >= 400) {
            if(is_array($responseBody)) {
                throw new {{spec.title | caseUcfirst}}Exception($responseBody['message'], $responseStatus, $responseBody['type'] ?? '', json_encode($responseBody));
            } else {
                throw new {{spec.title | caseUcfirst}}Exception($responseBody, $responseStatus);
            }
        }

        if ($responseType === 'location') {
            return $responseHeaders['location'];
        }

        return $responseBody;
    }

    /**
     * Flatten params array to PHP multiple format
     *
     * @param array $data
     * @param string $prefix
     * @return array
     */
    protected function flatten(array $data, string $prefix = ''): array {
        $output = [];

        foreach($data as $key => $value) {
            $finalKey = $prefix ? "{$prefix}[{$key}]" : $key;

            if (is_array($value)) {
                $output += $this->flatten($value, $finalKey); // @todo: handle name collision here if needed
            }
            else {
                $output[$finalKey] = $value;
            }
        }

        return $output;
    }
}
