<?php

namespace WebPConvert\Convert\Converters;

use WebPConvert\Convert\Converters\AbstractConverter;
use WebPConvert\Convert\Converters\ConverterTraits\CloudConverterTrait;
use WebPConvert\Convert\Converters\ConverterTraits\CurlTrait;
use WebPConvert\Convert\Exceptions\ConversionFailedException;
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\InvalidApiKeyException;
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
use WebPConvert\Options\BooleanOption;
use WebPConvert\Options\SensitiveStringOption;
use WebPConvert\Options\OptionFactory;

/**
 * Convert images to webp using ewww cloud service.
 *
 * @package    WebPConvert
 * @author     Bjørn Rosell <it@rosell.dk>
 * @since      Class available since Release 2.0.0
 */
class Ewww extends AbstractConverter
{
    use CloudConverterTrait;
    use CurlTrait;

    /** @var array  Array of invalid or exceeded api keys discovered during conversions (during the request)  */
    public static $nonFunctionalApiKeysDiscoveredDuringConversion;

    public function getUniqueOptions($imageType)
    {
        return OptionFactory::createOptions([
            ['api-key', 'string', [
                'title' => 'Ewww API key',
                'description' => 'ewww API key. ' .
                    'If you choose "auto", webp-convert will ' .
                    'convert to both lossy and lossless and pick the smallest result',
                'default' => '',
                'sensitive' => true,
                'ui' => [
                    'component' => 'password',
                ]
            ]],
            ['check-key-status-before-converting', 'boolean', [
                'title' => 'Check key status before converting',
                'description' =>
                    'If enabled, the api key will be validated (relative inexpensive) before trying ' .
                    'to convert. For automatic conversions, you should enable it. Otherwise you run the ' .
                    'risk that the same files will be uploaded to ewww cloud service over and over again, ' .
                    'in case the key has expired. For manually triggered conversions, you can safely disable ' .
                    'the option.',
                'default' => true,
                'ui' => [
                    'component' => 'checkbox',
                ]
            ]],
        ]);
    }

    protected function getUnsupportedDefaultOptions()
    {
        return [
            'alpha-quality',
            'auto-filter',
            'encoding',
            'low-memory',
            'method',
            'near-lossless',
            'preset',
            'sharp-yuv',
            'size-in-percentage',
        ];
    }

    /**
     * Get api key from options or environment variable
     *
     * @return string|false  api key or false if none is set
     */
    private function getKey()
    {
        if (!empty($this->options['api-key'])) {
            return $this->options['api-key'];
        }
        if (defined('WEBPCONVERT_EWWW_API_KEY')) {
            return constant('WEBPCONVERT_EWWW_API_KEY');
        }
        if (!empty(getenv('WEBPCONVERT_EWWW_API_KEY'))) {
            return getenv('WEBPCONVERT_EWWW_API_KEY');
        }
        return false;
    }


    /**
     * Check operationality of Ewww converter.
     *
     * @throws SystemRequirementsNotMetException  if system requirements are not met (curl)
     * @throws ConverterNotOperationalException   if key is missing or invalid, or quota has exceeded
     */
    public function checkOperationality()
    {

        $apiKey = $this->getKey();

        if ($apiKey === false) {
            if (isset($this->options['key'])) {
                throw new InvalidApiKeyException(
                    'The "key" option has been renamed to "api-key" in webp-convert 2.0. ' .
                    'You must change the configuration accordingly.'
                );
            }

            throw new InvalidApiKeyException('Missing API key.');
        }

        if (strlen($apiKey) < 20) {
            throw new InvalidApiKeyException(
                'Api key is invalid. Api keys are supposed to be 32 characters long - ' .
                'the provided api key is much shorter'
            );
        }

        // Check for curl requirements
        $this->checkOperationalityForCurlTrait();

        if ($this->options['check-key-status-before-converting']) {
            $keyStatus = self::getKeyStatus($apiKey);
            switch ($keyStatus) {
                case 'great':
                    break;
                case 'exceeded':
                    throw new ConverterNotOperationalException('Quota has exceeded');
                    //break;
                case 'invalid':
                    throw new InvalidApiKeyException('Api key is invalid');
                    //break;
            }
        }
    }

    /*
    public function checkConvertability()
    {
        // check upload limits
        $this->checkConvertabilityCloudConverterTrait();
    }
    */

    // Although this method is public, do not call directly.
    // You should rather call the static convert() function, defined in AbstractConverter, which
    // takes care of preparing stuff before calling doConvert, and validating after.
    protected function doActualConvert()
    {

        $options = $this->options;

        $ch = self::initCurl();

        //$this->logLn('api key:' . $this->getKey());

        $postData = [
            'api_key' => $this->getKey(),
            'webp' => '1',
            'file' => curl_file_create($this->source),
            'quality' => $this->getCalculatedQuality(),
            'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
        ];

        curl_setopt_array(
            $ch,
            [
            CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
            CURLOPT_HTTPHEADER => [
                'User-Agent: WebPConvert',
                'Accept: image/*'
            ],
            CURLOPT_POSTFIELDS => $postData,
            CURLOPT_BINARYTRANSFER => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => false,
            CURLOPT_SSL_VERIFYPEER => false
            ]
        );

        $response = curl_exec($ch);

        if (curl_errno($ch)) {
            throw new ConversionFailedException(curl_error($ch));
        }

        // The API does not always return images.
        // For example, it may return a message such as '{"error":"invalid","t":"exceeded"}
        // Messages has a http content type of ie 'text/html; charset=UTF-8
        // Images has application/octet-stream.
        // So verify that we got an image back.
        if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
            //echo curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
            curl_close($ch);

            /*
            For bogus or expired key it returns:  {"error":"invalid","t":"exceeded"}
            For exceeded key it returns:          {"error":"exceeded"}
            */
            $responseObj = json_decode($response);
            if (isset($responseObj->error)) {
                $this->logLn('We received the following error response: ' . $responseObj->error);
                $this->logLn('Complete response: ' . json_encode($responseObj));

                // Store the invalid key in array so it can be received once the Stack is completed
                // (even when stack succeeds)
                if (!isset(self::$nonFunctionalApiKeysDiscoveredDuringConversion)) {
                    self::$nonFunctionalApiKeysDiscoveredDuringConversion = [];
                }
                if (!in_array($options['api-key'], self::$nonFunctionalApiKeysDiscoveredDuringConversion)) {
                    self::$nonFunctionalApiKeysDiscoveredDuringConversion[] = $options['api-key'];
                }
                if ($responseObj->error == "invalid") {
                    throw new InvalidApiKeyException('The api key is invalid (or expired)');
                } else {
                    throw new InvalidApiKeyException('The quota is exceeded for the api-key');
                }
            }

            throw new ConversionFailedException(
                'ewww api did not return an image. It could be that the key is invalid. Response: '
                . $response
            );
        }

        // Not sure this can happen. So just in case
        if ($response == '') {
            throw new ConversionFailedException('ewww api did not return anything');
        }

        $success = file_put_contents($this->destination, $response);

        if (!$success) {
            throw new ConversionFailedException('Error saving file');
        }
    }

    /**
     *  Keep subscription alive by optimizing a jpeg
     *  (ewww closes accounts after 6 months of inactivity - and webp conversions seems not to be counted? )
     */
    public static function keepSubscriptionAlive($source, $key)
    {
        try {
            $ch = curl_init();
        } catch (\Exception $e) {
            return 'curl is not installed';
        }
        if ($ch === false) {
            return 'curl could not be initialized';
        }
        curl_setopt_array(
            $ch,
            [
            CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
            CURLOPT_HTTPHEADER => [
                'User-Agent: WebPConvert',
                'Accept: image/*'
            ],
            CURLOPT_POSTFIELDS => [
                'api_key' => $key,
                'webp' => '0',
                'file' => curl_file_create($source),
                'domain' => $_SERVER['HTTP_HOST'],
                'quality' => 60,
                'metadata' => 0
            ],
            CURLOPT_BINARYTRANSFER => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => false,
            CURLOPT_SSL_VERIFYPEER => false
            ]
        );

        $response = curl_exec($ch);
        if (curl_errno($ch)) {
            return 'curl error' . curl_error($ch);
        }
        if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
            curl_close($ch);

            /* May return this: {"error":"invalid","t":"exceeded"} */
            $responseObj = json_decode($response);
            if (isset($responseObj->error)) {
                return 'The key is invalid';
            }

            return 'ewww api did not return an image. It could be that the key is invalid. Response: ' . $response;
        }

        // Not sure this can happen. So just in case
        if ($response == '') {
            return 'ewww api did not return anything';
        }

        return true;
    }

    /*
        public static function blacklistKey($key)
        {
        }

        public static function isKeyBlacklisted($key)
        {
        }*/

    /**
     *  Return "great", "exceeded" or "invalid"
     */
    public static function getKeyStatus($key)
    {
        $ch = self::initCurl();

        curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/verify/");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, [
            'api_key' => $key
        ]);

        curl_setopt($ch, CURLOPT_USERAGENT, 'WebPConvert');

        $response = curl_exec($ch);
        // echo $response;
        if (curl_errno($ch)) {
            throw new \Exception(curl_error($ch));
        }
        curl_close($ch);

        // Possible responses:
        // “great” = verification successful
        // “exceeded” = indicates a valid key with no remaining image credits.
        // an empty response indicates that the key is not valid

        if ($response == '') {
            return 'invalid';
        }
        $responseObj = json_decode($response);
        if (isset($responseObj->error)) {
            if ($responseObj->error == 'invalid') {
                return 'invalid';
            } else {
                throw new \Exception('Ewww returned unexpected error: ' . $response);
            }
        }
        if (!isset($responseObj->status)) {
            throw new \Exception('Ewww returned unexpected response to verify request: ' . $response);
        }
        switch ($responseObj->status) {
            case 'great':
            case 'exceeded':
                return $responseObj->status;
        }
        throw new \Exception('Ewww returned unexpected status to verify request: "' . $responseObj->status . '"');
    }

    public static function isWorkingKey($key)
    {
        return (self::getKeyStatus($key) == 'great');
    }

    public static function isValidKey($key)
    {
        return (self::getKeyStatus($key) != 'invalid');
    }

    public static function getQuota($key)
    {
        $ch = self::initCurl();

        curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/quota/");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, [
            'api_key' => $key
        ]);
        curl_setopt($ch, CURLOPT_USERAGENT, 'WebPConvert');

        $response = curl_exec($ch);
        return $response; // ie -830 23. Seems to return empty for invalid keys
        // or empty
        //echo $response;
    }
}
