<?php

namespace IMATHUZH\Qfq\Core\Report;

use Exception;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Store\Store;

/**
 * Class RestClient
 * @package IMATHUZH\Qfq\Core\Report
 */
class RestClient {

    /**
     * @var Store
     */
    private $store = null;

    /**
     * RestClient constructor.
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function __construct() {
        $this->store = Store::getInstance();
    }

    /**
     * @param string $str
     * @return string
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function process($str) {
        $recvBuffer = '';
        $recv = array();

        $param = $this->parseArgument($str);

//        if (isset($param[TOKEN_L_SSL])) {
//            $options['ssl'] = json_decode($param[TOKEN_L_SSL], true);
//        }

        // If $dataFile given: overwrite $data with file contents
        $data = $param[TOKEN_L_CONTENT];

        if (!empty($param[TOKEN_L_CONTENT_FILE])) {
            $dataFile = $param[TOKEN_L_CONTENT_FILE];
            $fileHandle = fopen($dataFile, "r");
            if ($fileHandle === false) {
                throw new \UserReportException("Could not read file $dataFile.", ERROR_IO_FILE_NOT_FOUND);
            }
            $data = stream_get_contents($fileHandle);
            fclose($fileHandle);
        }

        // Send request
        try {
            list($http_status, $recvBuffer) = self::callApiStream(
                strtoupper($param[TOKEN_L_METHOD]),
                $param[TOKEN_REST_CLIENT],
                $data,
                $param[TOKEN_L_HEADER],
                $param[TOKEN_L_TIMEOUT]);
            $recv[HTTP_STATUS] = $http_status;
            if ($http_status >= 300) {
                $recv[ERROR_MESSAGE] = ' Api error: ' . $param[TOKEN_REST_CLIENT] . '  HTTP code: ' . $http_status . '  Message: ' . print_r(json_decode($recvBuffer ?? '', true), true);
            }
            $rcvBufferDecoded = json_decode($recvBuffer, true);
            if (is_array($rcvBufferDecoded)) {
                array_walk_recursive($rcvBufferDecoded, function ($value, $key) use (&$recv) {
                    $recv[$key] = $value;
                });
            }
        } catch (\Exception $e) {
            $recv[HTTP_STATUS] = 0;
            $recv[ERROR_MESSAGE] = $e->getMessage() . ' (QFQ Error Code: ' . $e->getCode() . ')';
        }

        // Copy new values to STORE_CLIENT
        $this->store::setStore($recv, STORE_CLIENT, true);
        return $recvBuffer;
    }

    /**
     * Parses $str, fill some defaults and returns an array with given arguments.
     *
     * @param string $str
     * @return array
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function parseArgument($str) {
        // "n:http://antmedia-dev.math.uzh.ch/WebRTCAppEE/rest/v2/broadcasts/create|
        //  content:{'streamId' => "ASDKLJfdlajfhdkhH"}|contentFile:fileadmin/_temp_/data.txt|
        //  method:POST|header:Content-type: application/json\r\n"

        // Split string
        $param = KeyValueStringParser::parse($str, PARAM_TOKEN_DELIMITER, PARAM_DELIMITER);
        if (empty($param[TOKEN_REST_CLIENT])) {
            throw new \UserReportException("Missing RestClient target", ERROR_MISSING_VALUE);
        }

        $param[TOKEN_L_CONTENT] = trim($param[TOKEN_L_CONTENT] ?? '');

        if (!empty($param[TOKEN_L_CONTENT_FILE])) {
            $param[TOKEN_L_CONTENT_FILE] = trim($param[TOKEN_L_CONTENT_FILE]);
        }

        if (!empty($param[TOKEN_L_HEADER])) {
            $param[TOKEN_L_HEADER] = trim($param[TOKEN_L_HEADER]);
        }

        if (empty($param[TOKEN_L_METHOD])) {
            $param[TOKEN_L_METHOD] = 'GET';
        }

        // Default Timeout
        if (empty($param[TOKEN_L_TIMEOUT])) {
            $param[TOKEN_L_TIMEOUT] = 5;
        }

        // split header at "\r\n" and "\n". $header must be an array of single headers!!
        if (!empty($param[TOKEN_L_HEADER])) {
            $header = str_replace("\r\n", '$$SEP$$', $param[TOKEN_L_HEADER]);
            $header = str_replace("\n", '$$SEP$$', $header);
            $header = explode('$$SEP$$', $header);
        } else {
            $header = [];
        }

        // If 'Host' is missing in header: define - useful for Firewall/ Proxy
        // CR: if a header 'host' is given, REST calls fails always.
//        if (strpos($param[TOKEN_L_HEADER], 'host:') === false) {
//            $urlParts = parse_url($param[TOKEN_REST_CLIENT]);
//            $header[] = 'host: ' . $urlParts['host'];
//        }

        $param[TOKEN_L_HEADER] = $header;
        return $param;
    }

    /**
     * @param string $method
     * @param string $url
     * @param string $data
     * @param array $header
     * @param int $timeout
     * @return array
     * @throws Exception
     */
    public static function callApiStream(string $method, string $url, string $data = '', array $header = [], int $timeout = 5) {

        // Header: Set content-type if not set
        if (stripos(join('', $header), 'content-type:') === false) {
            $mime = (($data[0] ?? '') == '{') ? 'application/json' : 'text/plain';
            $header[] = 'content-type: ' . $mime . '; charset=utf-8';
        }

        // Header: Set connection if not set
        if (stripos(join('', $header), 'connection:') === false) {
            $header[] = 'connection: close';
        }

        // Prepare options
        $opts = [
            'http' => [
                'method' => $method,
                'timeout' => $timeout
            ]
        ];

        if (!empty($data)) {
            switch ($method) {
                case "POST":
                case "PUT":
                    // Header: Set content-length
                    $header[] = 'content-length: ' . strlen($data);
                    // Pass data
                    $opts['http']['content'] = $data;
                    break;
                case "DELETE":
                default:
                    $url = sprintf("%s?%s", $url, http_build_query(json_decode($data, true)));
            }
        }

        $opts['http']['header'] = $header;

        $context = stream_context_create($opts);
        $stream = @fopen($url, 'r', false, $context);

        if ($stream === false) {
            $err = error_get_last();
            throw new Exception("Api call {$method} {$url} failed: " . $err['message'], ERROR_REST_API_CALL);
        }

        $output = stream_get_contents($stream);

        // get HTTP status code
        $status_line = $http_response_header[0];
        preg_match('{HTTP\/\S*\s(\d{3})}', $status_line, $match);
        $httpCode = $match[1];

        return [$httpCode, $output];
    }
}
