<?php

use GuzzleHttp\HandlerStack;
use \JsonStreamingParser\Listener\IdleListener;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Kevinrob\GuzzleCache\KeyValueHttpHeader;
use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
use Kevinrob\GuzzleCache\Strategy\GreedyCacheStrategy;
use Doctrine\Common\Cache\FilesystemCache;

class BackgroundBaseClient extends IdleListener
{
    protected $prop;
    protected $objectsProcessed;
    protected $startTime;
    protected $endTime;
    protected $errorBody;
    protected $apiName;
    protected $logParams;
    protected $logger; //NOTE: Logger harus diset dari controller, karena didapat dari DI.
    protected $cacheTTL = 2678400; // 1 month, In Seconds
    protected $cacheHeaderKey = []; //Specify header key that can change the cache key, e.g: ['Authorization']

    public function __construct($prop)
    {
        $this->prop = $prop;
    }

    public function filterResponse($body, $result) {
        //NOTE: Default filterResponse
        if($this->logger != null) {
            $delta = $this->getDeltaTime();
            $this->logger->addItem('delta_api_time_metric_f', $delta);
            $this->logger->addItem('api_name', $this->apiName ?? get_called_class());
            $this->logger->info('Log API');
        }
    }

    protected function
    execute($method, $path, $params, $verifySSL = true, $doCaching = false)
    {
        try {
            if($doCaching) {
                $cachePath = (getenv('GUZZLE_CACHE_DIRECTORY') && !empty(getenv('GUZZLE_CACHE_DIRECTORY'))) ? getenv('GUZZLE_CACHE_DIRECTORY') : getcwd() . '/cache';
                $stack = HandlerStack::create();
                $strategy = null;
                if(empty($this->cacheHeaderKey)) {
                    $strategy = new GreedyCacheStrategy(
                        new DoctrineCacheStorage(
                            new FilesystemCache($cachePath)
                        ),
                        $this->cacheTTL
                    );
                }
                else {
                    $strategy = new GreedyCacheStrategy(
                        new DoctrineCacheStorage(
                            new FilesystemCache($cachePath)
                        ),
                        $this->cacheTTL,
                        new KeyValueHttpHeader($this->cacheHeaderKey)
                    );
                }

                $stack->push(
                    new CacheMiddleware($strategy),
                    'cache'
                );
            }

            $options = [
                'base_uri' => $this->prop["server"],
                'verify' => $verifySSL
            ];

            if($doCaching) $options['handler'] = $stack;

            $client = new GuzzleHttp\Client($options);

            // handle dh key too short
            $params['curl'] = [
                CURLOPT_SSL_CIPHER_LIST => 'DEFAULT@SECLEVEL=1'
            ];

            $this->startTime = microtime(true);
            $body = $client->request($method, $path, $params)->getBody();
            $this->endTime = microtime(true);
            $result = json_decode($body, true);

            if (empty($result)) {
                $this->errorBody = $body;
            }

            $this->filterResponse($body, $result);

            return $result;
        } catch (\Exception $e) {
            $this->errorBody = $e->getMessage();
            $this->filterResponse([], []);
            if ($this->logger != null) {
                $this->logger->addItem('api_result_code', $e->getCode());
                $this->logger->addItem('api_result_message', $e->getMessage());
            }

            $api_params = http_build_query($this->logParams ?? $params['query'] ?? []);
            LogHelper::apiLogError($this->apiName ?? get_called_class(), $api_params, $this->getErrorBody());

            if ($e->getCode() == 404) {
                throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, 'Data Not Found');
            }

            throw $e;
        }
    }

    protected function executeGetHeader($method, $path, $params, $verifySSL = true, $doCaching = false)
    {
        try {
            if($doCaching) {
                $cachePath = (getenv('GUZZLE_CACHE_DIRECTORY') && !empty(getenv('GUZZLE_CACHE_DIRECTORY'))) ? getenv('GUZZLE_CACHE_DIRECTORY') : (str_contains(getcwd(), 'public') ? getcwd() . '/../cache' : getcwd() . '/cache');
                $stack = HandlerStack::create();
                $strategy = null;
                if(empty($this->cacheHeaderKey)) {
                    $strategy = new GreedyCacheStrategy(
                        new DoctrineCacheStorage(
                            new FilesystemCache($cachePath)
                        ),
                        $this->cacheTTL
                    );
                }
                else {
                    $strategy = new GreedyCacheStrategy(
                        new DoctrineCacheStorage(
                            new FilesystemCache($cachePath)
                        ),
                        $this->cacheTTL,
                        new KeyValueHttpHeader($this->cacheHeaderKey)
                    );
                }

                $stack->push(
                    new CacheMiddleware($strategy),
                    'cache'
                );
            }

            $options = [
                'base_uri' => $this->prop["server"],
                'verify' => $verifySSL
            ];

            if($doCaching) $options['handler'] = $stack;

            $client = new GuzzleHttp\Client($options);

            // handle dh key too short
            $params['curl'] = [
                CURLOPT_SSL_CIPHER_LIST => 'DEFAULT@SECLEVEL=1'
            ];

            $this->startTime = microtime(true);
            $result = $client->request($method, $path, $params)->getHeaders();
            $this->endTime = microtime(true);

            if (empty($result)) {
                $this->errorBody = $result;
            }

            $this->filterResponse($result, $result);

            return $result;
        } catch (\Exception $e) {
            $this->errorBody = $e->getMessage();
            $this->filterResponse([], []);
            if ($this->logger != null) {
                $this->logger->addItem('api_result_code', $e->getCode());
                $this->logger->addItem('api_result_message', $e->getMessage());
            }

            $api_params = http_build_query($this->logParams ?? $params['query'] ?? []);
            LogHelper::apiLogError($this->apiName ?? get_called_class(), $api_params, $this->getErrorBody());

            if ($e->getCode() == 404) {
                throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, 'Data Not Found');
            }

            throw $e;
        }
    }

    protected function executeBase64($method, $path, $params, $verifySSL = false, $doCaching = false)
    {
        try {
            if($doCaching) {
                $cachePath = (getenv('GUZZLE_CACHE_DIRECTORY') && !empty(getenv('GUZZLE_CACHE_DIRECTORY'))) ? getenv('GUZZLE_CACHE_DIRECTORY') : (str_contains(getcwd(), 'public') ? getcwd() . '/../cache' : getcwd() . '/cache');
                $stack = HandlerStack::create();
                $strategy = null;
                if(empty($this->cacheHeaderKey)) {
                    $strategy = new GreedyCacheStrategy(
                        new DoctrineCacheStorage(
                            new FilesystemCache($cachePath)
                        ),
                        $this->cacheTTL
                    );
                }
                else {
                    $strategy = new GreedyCacheStrategy(
                        new DoctrineCacheStorage(
                            new FilesystemCache($cachePath)
                        ),
                        $this->cacheTTL,
                        new KeyValueHttpHeader($this->cacheHeaderKey)
                    );
                }

                $stack->push(
                    new CacheMiddleware($strategy),
                    'cache'
                );
            }

            $options = [
                'base_uri' => $this->prop["server"],
                'verify' => $verifySSL
            ];

            if($doCaching) $options['handler'] = $stack;

            $client = new GuzzleHttp\Client($options);

            $this->startTime = microtime(true);
            $body = $client->request($method, $path, $params)->getBody();
            $this->endTime = microtime(true);

            $result = base64_encode($body);

            $this->filterResponse($body, $result);

            return $result;
        } catch (\Exception $e) {
            $this->errorBody = $e->getMessage();

            $this->filterResponse([], []);
            if($this->logger != null) {
                $this->logger->addItem('api_result_code', $e->getCode());
                $this->logger->addItem('api_result_message', $e->getMessage());
            }

            $api_params = http_build_query($this->logParams ?? $params['query'] ?? []);
            LogHelper::apiLogError( $this->apiName ?? get_called_class(), $api_params, $this->getErrorBody());

            if ($e->getCode() == 404) {
                throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, 'Data Not Found');
            }

            throw $e;
        }
    }

    protected function jsonStream($method, $path, $params, $listener, $doCaching = false)
    {
        try {
            if($doCaching) {
                $cachePath = (getenv('GUZZLE_CACHE_DIRECTORY') && !empty(getenv('GUZZLE_CACHE_DIRECTORY'))) ? getenv('GUZZLE_CACHE_DIRECTORY') : (str_contains(getcwd(), 'public') ? getcwd() . '/../cache' : getcwd() . '/cache');
                $stack = HandlerStack::create();
                $strategy = null;
                if(empty($this->cacheHeaderKey)) {
                    $strategy = new GreedyCacheStrategy(
                        new DoctrineCacheStorage(
                            new FilesystemCache($cachePath)
                        ),
                        $this->cacheTTL
                    );
                }
                else {
                    $strategy = new GreedyCacheStrategy(
                        new DoctrineCacheStorage(
                            new FilesystemCache($cachePath)
                        ),
                        $this->cacheTTL,
                        new KeyValueHttpHeader($this->cacheHeaderKey)
                    );
                }

                $stack->push(
                    new CacheMiddleware($strategy),
                    'cache'
                );
            }

            $options = [
                'base_uri' => $this->prop["server"],
            ];
            if($doCaching) $options['handler'] = $stack;

            $client = new GuzzleHttp\Client($options);
            $params["stream"] = true;
            $params["read_timeout"] = 60;

            $this->startTime = microtime(true);
            $body = $client->request($method, $path, $params)->getBody();
            $this->endTime = microtime(true);

            $phpStream = \GuzzleHttp\Psr7\StreamWrapper::getResource($body);
            $parser = new \JsonStreamingParser\Parser($phpStream, $listener);
            $parser->parse();
            $this->filterResponse($body, []);
        } catch (\Exception $e) {
            $this->filterResponse([], []);
            if($this->logger != null) {
                $this->logger->addItem('api_result_code', $e->getCode());
                $this->logger->addItem('api_result_message', $e->getMessage());
            }

            $this->errorBody = $e->getMessage();
            echo $e->getMessage();

            $api_params = http_build_query($this->logParams ?? $params['query'] ?? []);
            LogHelper::apiLogError($this->apiName ?? get_called_class(), $api_params, $this->getErrorBody());

            throw $e;
        }
    }
    public function getObjectsProcessed()
    {
        return $this->objectsProcessed;
    }

    public function getStartTime()
    {
        return $this->startTime;
    }

    public function getEndTime()
    {
        return $this->endTime;
    }

    public function getErrorBody()
    {
        return $this->errorBody;
    }

    public function getDeltaTime() {
        return round($this->endTime ?? 0 - $this->startTime ?? 0, 3) * 1000;
    }

    public function setApiName($apiName) {
        $this->apiName = $apiName;
    }

    public function setLogParams(array $logParams) {
        $this->logParams = $logParams;
    }

    public function setLogger($logger) {
        $this->logger = $logger;
    }

    public function setCacheTTL($cacheTTL) {
        $this->cacheTTL = $cacheTTL;
    }

    public function setCacheHeaderKey($cacheHeaderKey) {
        $this->cacheHeaderKey = $cacheHeaderKey;
    }
}
