<?php

namespace CCDN\Helpers\Api;

use CCDN\Helpers\Api\Response\CollectionInterface;
use CCDN\Helpers\Api\Response\FranchiseDetailsInterface;
use CCDN\Helpers\Api\Response\VoiceActionInterface;
use CCDN\Helpers\Caching\Cache;
use CCDN\Helpers\CCDNFunctions;
use CCDN\Helpers\DB\SettingsSave;
use CCDN\Helpers\Facade\Http\Request;
use CCDN\Helpers\Facade\Http\Url;
use CCDN\Helpers\Logger\Log;
use CCDN\Helpers\Logger\LogType;
use CCDN\Helpers\SecondIn;
use CCDN\Helpers\Settings;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response as GuzzleResponse;

class ApiHandler
{
    /**
     * User Api key
     *
     * @var string|null
     */
    private $apiKey;

    /**
     * Response from api
     *
     * @var string|null
     */
    private $body;

    /**
     * @var Cache
     */
    private $cache;

    /**
     * @var Client
     */
    private $client;

    /**
     * @var int
     */
    private $cacheTtl;

    /**
     * ApiHandler constructor.
     *
     * @param int $timeout
     * @param bool $httpErrors
     * @param int $cacheTtl
     */
    public function __construct($timeout = 20, $httpErrors = false, $cacheTtl = 14400)
    {
        $this->cache = new Cache();
        $this->cacheTtl = $cacheTtl;
        $this->apiKey = Settings::get('api_key');
        $this->client = $this->_createClient($timeout, $httpErrors);
    }

    /**
     * @param int $timeout
     * @param bool $httpErrors
     * @return Client
     */
    private function _createClient($timeout, $httpErrors)
    {

        if ($this->cache->has('gistApiDomain')) {
            $apiDomain = $this->cache->get('gistApiDomain');
        } else {
            $client = new Client([
                'base_uri' => 'https://gist.githubusercontent.com/',
                'idn_conversion' => false,
                'timeout' => $timeout,
            ]);
            $clientResponse = $client->get('/dev-dle/351244dc70855ebe2d13eafca621d298/raw');

            $response = json_decode($clientResponse->getBody()->getContents(), true);
            $domains = $response['domains'];

            $apiDomain = $domains['api'];
            $this->cache->set('gistApiDomain', $apiDomain, SecondIn::minute(30));

            $settingsData = [];
            if (isset($domains['data']))
                $settingsData['domain_data'] = CCDNFunctions::removeSchema($domains['data']);

            if ($settingsData) {
                $settings = new SettingsSave($settingsData);
                $settings->saveAll();
            }
        }

        return new Client([
            'base_uri' => $apiDomain,
            'verify' => false,
            'timeout' => $timeout,
            'http_errors' => $httpErrors,
            'idn_conversion' => false,
            'headers' => [
                'User-Agent' => Request::getUserAgent(),
                'Referer' => Url::getDomain(),
                'X-Powered-By' => 'CCDN' . Settings::PLUGIN_VERSION
            ]
        ]);
    }

    /**
     * @return array
     */
    public function getVideoNewsAll()
    {

        $allNewVideo = $this->getVideoNews([
            'limit' => 300,
        ])->getBody();

        $nextPage = $allNewVideo['next_page'];
        $results = $allNewVideo['results'];
        unset($allNewVideo);

        $page = 2;
        while ($nextPage !== null) {
            $allNewVideo = $this->getVideoNews([
                'limit' => 300,
                'page' => $page,
            ])->getBody();
            $nextPage = $allNewVideo['next_page'];
            foreach ($allNewVideo['results'] as $result) {
                $results[] = $result;
            }
            $page++;
            unset($allNewVideo);
        }

        return $results;
    }

    /**
     * @param array $query
     *
     * @return $this
     */
    public function getVideoNews($query)
    {
        $this->body = null;

        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        $cacheKey = $this->_createCacheKey($query, __METHOD__);

        if ($this->cache->has($cacheKey)) {
            $this->body = $this->cache->get($cacheKey);

        } else {
            $clientResponse = $this->client->get('video/news', [
                'query' => $query,
            ]);
            if ($clientResponse->getStatusCode() === 200) {
                $this->body = $clientResponse->getBody()->getContents();
                $this->cache->set($cacheKey, $this->body, $this->cacheTtl);
            }
        }

        return $this;
    }

    /**
     * @param array $query
     * @param string $prefix
     * @return string
     */
    private function _createCacheKey($query = [], $prefix = '')
    {
        return $prefix . http_build_query($query);
    }

    /**
     * @param bool $asArray
     *
     * @return array|object|null
     */
    public function getBody($asArray = true)
    {
        return json_decode($this->body, $asArray);
    }

    /**
     * @return string|null
     */
    public function getResponseString()
    {
        return $this->body;
    }

    /**
     * @param array $query
     *
     * @return FranchiseDetailsInterface|null
     */
    public function getFranchiseDetails($query)
    {
        $this->body = null;

        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        $cacheKey = $this->_createCacheKey($query, __METHOD__);

        if ($this->cache->has($cacheKey)) {
            $this->body = $this->cache->get($cacheKey);
        } else {

            $clientResponse = $this->client->get('franchise/details', [
                'query' => $query,
            ]);
            if ($clientResponse->getStatusCode() === 200) {
                $this->body = $clientResponse->getBody()->getContents();
                $this->cache->set($cacheKey, $this->body, $this->cacheTtl);
            }

        }

        return ResponseFactory::createFranchiseDetail($this->getBody());
    }

    /**
     * @param array $query
     *
     * @return $this
     */
    public function getGenres($query)
    {

        $this->body = null;

        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        $cacheKey = $this->_createCacheKey($query, __METHOD__);

        if ($this->cache->has($cacheKey)) {
            $this->body = $this->cache->get($cacheKey);
            return $this;
        }
        $clientResponse = $this->client->get('genre', [
            'query' => $query,
        ]);
        if ($clientResponse->getStatusCode() === 200) {
            $this->body = $clientResponse->getBody()->getContents();
            $this->cache->set($cacheKey, $this->body, $this->cacheTtl);
        }

        return $this;
    }


    /**
     * @param array $query
     *
     * @return $this
     */
    public function getCountry($query)
    {

        $this->body = null;

        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        $cacheKey = $this->_createCacheKey($query, __METHOD__);

        if ($this->cache->has($cacheKey)) {
            $this->body = $this->cache->get($cacheKey);
            return $this;
        }
        $clientResponse = $this->client->get('country', [
            'query' => $query,
        ]);
        if ($clientResponse->getStatusCode() === 200) {
            $this->body = $clientResponse->getBody()->getContents();
            $this->cache->set($cacheKey, $this->body, $this->cacheTtl);
        }

        return $this;
    }

    /**
     * @param array $query
     *
     * @return $this
     */
    public function getList($query)
    {

        $this->body = null;

        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        $cacheKey = $this->_createCacheKey($query, __METHOD__);

        if ($this->cache->has($cacheKey)) {
            $this->body = $this->cache->get($cacheKey);
            return $this;
        }
        $clientResponse = $this->client->get('list', [
            'query' => $query,
        ]);
        if ($clientResponse->getStatusCode() === 200) {
            $this->body = $clientResponse->getBody()->getContents();
            $this->cache->set($cacheKey, $this->body, $this->cacheTtl);
        }

        return $this;
    }

    /**
     * @return array
     */
    public function getTrailersAll()
    {
        $clientResponse = $this->getTrailers([
            'limit' => 500,
        ]);

        $allNewVideo = $clientResponse->getBody();

        $nextPage = $allNewVideo['next_page'];
        $results = $allNewVideo['results'];
        $page = 2;
        while ($nextPage !== null) {
            $clientResponse = $this->getTrailers([
                'limit' => 500,
                'page' => $page,
            ]);
            $allNewVideo = $clientResponse->getBody();
            $nextPage = $allNewVideo['next_page'];
            foreach ($allNewVideo['results'] as $result) {
                $results[] = $result;
            }
            $page++;
        }

        return $results;
    }

    /**
     * @param array $query
     *
     * @return $this
     */
    public function getTrailers($query)
    {

        $this->body = null;

        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        $cacheKey = $this->_createCacheKey($query, __METHOD__);

        if ($this->cache->has($cacheKey)) {
            $this->body = $this->cache->get($cacheKey);
            return $this;
        }
        $clientResponse = $this->client->get('trailers', [
            'query' => $query,
        ]);
        if ($clientResponse->getStatusCode() === 200) {
            $this->body = $clientResponse->getBody()->getContents();
            $this->cache->set($cacheKey, $this->body, $this->cacheTtl);
        }

        return $this;
    }

    /**
     * @param array $query
     *
     * @return CollectionInterface[]|null
     */
    public function getCollections($query)
    {

        $this->body = null;

        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        $cacheKey = $this->_createCacheKey($query, __METHOD__);

        if ($this->cache->has($cacheKey)) {
            $this->body = $this->cache->get($cacheKey);
        } else {
            $clientResponse = $this->client->get('collection', [
                'query' => $query,
            ]);
            if ($clientResponse->getStatusCode() === 200) {
                $this->body = $clientResponse->getBody()->getContents();
                $this->cache->set($cacheKey, $this->body, $this->cacheTtl);
            }
        }

        return ResponseFactory::createCollections($this->getBody());
    }

    /**
     * @param array $query
     *
     * @return VoiceActionInterface[]|null
     */
    public function getVoices($query)
    {

        $this->body = null;

        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        $cacheKey = $this->_createCacheKey($query, __METHOD__);

        if ($this->cache->has($cacheKey)) {
            $this->body = $this->cache->get($cacheKey);
        } else {
            $clientResponse = $this->client->get('voice-acting', [
                'query' => $query,
            ]);
            if ($clientResponse->getStatusCode() === 200) {
                $this->body = $clientResponse->getBody()->getContents();
                $this->cache->set($cacheKey, $this->body, $this->cacheTtl);
            }
        }


        return ResponseFactory::createVoiceAction($this->getBody());
    }

    /**
     * @return int
     */
    public function validateApiKey()
    {
        if (empty($this->apiKey)) {
            return 0;
        }

        $clientResponse = $this->client->get('checked-key', [
            'query' => [
                'key' => $this->apiKey
            ],
        ]);

        return (int)($clientResponse->getStatusCode() === 200);
    }

    /**
     * @param array $query
     *
     * @return PromiseInterface
     */
    public function getFranchiseDetailsAsync($query)
    {

        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        return $this->client->getAsync('franchise/details', [
            'query' => $query,
        ]);
    }

    /**
     * @param array $query
     *
     * @return PromiseInterface
     */
    public function getFranchiseCalendarAsync($query)
    {
        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        return $this->client->getAsync('franchise/calendar', [
            'query' => $query,
        ]);
    }

    /**
     * @param array $query
     *
     * @return PromiseInterface
     */
    public function getListAsync($query)
    {

        $query = array_merge($query, [
            'token' => $this->apiKey,
        ]);

        return $this->client->getAsync('list', [
            'query' => $query,
        ]);
    }

    /**
     * @param array $responses
     * @return array
     */
    public function promiseResponseJsonToArray($responses)
    {
        $log = new Log();
        $responseArray = [];
        foreach ($responses as $index => $response) {
            if ($response['state'] === 'rejected') {
                /** @var ConnectException $connectException */
                $connectException = $response['reason'];
                $log->write(LogType::ACTION_API,
                    get_class($connectException) . ': ' . $connectException->getMessage() . ' ' . $connectException->getTraceAsString());
                continue;
            }

            /**
             * @var GuzzleResponse $response
             */
            $response = $response['value'];
            if ($response->getStatusCode() !== 200) {
                continue;
            }
            $arr = json_decode($response->getBody()->getContents(), true);
            $responseArray[$index] = $arr;
        }

        return $responseArray;
    }

    /**
     * @return $this
     */
    public function getDomains()
    {
        $this->body = null;

        $query = [
            'token' => $this->apiKey,
        ];

        $cacheKey = $this->_createCacheKey($query, __METHOD__);

        if ($this->cache->has($cacheKey)) {
            $this->body = $this->cache->get($cacheKey);
            return $this;
        }

        $clientResponse = $this->client->get('domains', [
            'query' => $query,
        ]);
        if ($clientResponse->getStatusCode() === 200) {
            $this->body = $clientResponse->getBody()->getContents();
            $this->cache->set($cacheKey, $this->body, $this->cacheTtl);
        }

        return $this;
    }
}
