<?php

namespace Comitium5\DesignerBundle\Controller\Widget;

use Comitium5\ApiClientBundle\Client\Client;
use Comitium5\ApiClientBundle\Provider\ApiProvider;
use Comitium5\CommonWidgetsBundle\Cache\RedisWrapper;
use Comitium5\CommonWidgetsBundle\Controller\AbstractWidgetControllerBase;
use Comitium5\CommonWidgetsBundle\Exceptions\WidgetControllerErrorException;
use Comitium5\CommonWidgetsBundle\Services\ParametersStorage;
use Comitium5\CommonWidgetsBundle\Services\Security\DataEncryption;
use Comitium5\DesignerBundle\Helper\FileUtils;
use Comitium5\DesignerBundle\Helper\Widget\WidgetHelper;
use Comitium5\DesignerBundle\Model\Interfaces\Widget\AbstractDesignerWidgetControllerInterface;
use Comitium5\DesignerBundle\Model\Interfaces\Widget\WidgetInterface;
use Comitium5\DesignerBundle\Model\WidgetType;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Yaml\Yaml;

/**
 * Class AbstractDesignerWidgetController
 *
 * @author Carles Gómez <carles@bab-soft.com>
 * @package Comitium5\DesignerBundle\Controller\Widget
 */
abstract class AbstractDesignerWidgetController extends AbstractWidgetControllerBase implements AbstractDesignerWidgetControllerInterface
{
    /**
     * This constant defines the route path to parameters config file
     */
    const CONFIG_FILE_PATH = '%s/Widgets/%s/Config/config.yml';

    /**
     * This constant defines the route path to public resources folder
     */
    const RESOURCES_PUBLIC_PATH = 'bundles/cs/Widgets/%s/%s/%s';

    /**
     * string
     */
    const WIDGET_CACHE_KEY = 'widget_instance_%s_%s_%s_%s';

    /**
     * string
     */
    const WIDGET_PARTIAL_CACHE_KEY = 'widget_instance_%s_%s';

    const FIELDS_INDEX = "_fields";

    const PAGE_INDEX = "_page";

    const ENTITY_INDEX = "_entity";

    const EDITOR_INDEX = "_editor";

    const PREVIEW_INDEX = "_preview";

    const LOCALES_INDEX = "_locales";

    const UUID_INDEX = "_uuid";

    const WIDGET_INDEX = "_widget";

    const FIXTURES_INDEX = "_fixtures";

    const GLOBAL_VARS_INDEX = "_global";

    /**
     * @var WidgetInterface
     */
    private $widget;

    /**
     * @var WidgetHelper
     */
    private $widgetHelper;

    /**
     * @var string
     */
    private $bundlePath;

    /**
     * @var int
     */
    protected static $cacheTTL = null;

    /**
     * @var bool
     */
    protected static $enabledRenderCache = true;

    /**
     * @var string
     */
    protected static $uniqueCacheKey = null;

    /**
     * @var ApiProvider
     */
    protected $apiService;

    /**
     * @return WidgetInterface
     */
    public function getWidget()
    {
        return $this->widget;
    }

    /**
     * @param mixed $widget
     *
     * @return $this
     */
    public function setWidget($widget)
    {
        $this->widget = $widget;

        return $this;
    }

    /**
     * @param WidgetHelper $widgetHelper
     *
     * @return mixed
     */
    public function setWidgetHelper(WidgetHelper $widgetHelper)
    {
        $this->widgetHelper = $widgetHelper;
    }

    /**
     * @return mixed
     */
    public function getWidgetHelper()
    {
        return $this->widgetHelper;
    }

    /**
     * @param $bundlePath
     *
     * @return mixed
     */
    public function setBundlePath($bundlePath)
    {
        $this->bundlePath = $bundlePath;
    }

    /**
     * @return mixed
     */
    public function getBundlePath()
    {
        return $this->bundlePath;
    }

    /**
     * @return array
     * @throws \Exception
     */
    final public function getConfigParameters()
    {
        $configFile = sprintf(
            self::CONFIG_FILE_PATH,
            $this->bundlePath,
            $this->widget->buildFolderName(false)
        );

        if (!file_exists($configFile)) {
            throw new \Exception(sprintf('Config file %s not exists', $configFile));
        }

        $config = Yaml::parse($configFile);

        return isset($config['parameters']) ? $config['parameters'] : [];
    }

    /**
     * @return mixed
     */
    final public function getCssResourcesPaths()
    {
        $path = $this->widgetHelper->resourceCssPath($this->widget);

        return $this->resolvePaths($path, "css");
    }

    /**
     * @return mixed
     */
    final public function getJsResourcesPaths()
    {
        $path = $this->widgetHelper->resourceJsPath($this->widget);

        return $this->resolvePaths($path, "js");
    }

    /**
     * @return mixed
     */
    final public function getIconPaths()
    {
        $path = $this->widgetHelper->resourceIconPath($this->widget);

        return $this->resolvePaths($path, "icon");
    }

    /**
     * @return mixed
     * @throws WidgetControllerErrorException
     */
    final public function renderCached()
    {
        if ($this->checkCacheParameters() === null) {
            return;
        }

        if ($content = $this->getFromCache()) {
            return Response::create($content);
        }

        return;
    }

    /**
     * @param string $view
     * @param array $viewParameters
     * @param Response|null $response
     *
     * @return Response
     * @throws WidgetControllerErrorException
     */
    final public function render($view, array $viewParameters = [], Response $response = null)
    {
        if ($this->isEditor() === true || $this->isPreview() === true) {
            $environment = $this->get("twig");
            $environment->enableAutoReload();
        }

        $content = parent::render(
            $view,
            $this->addGlobalVars($viewParameters),
            $response
        );

        if ($this->checkCacheParameters() === true) {
            $this->saveInCache($content->getContent());
        }

        return $content;
    }

    /**
     * After refactor, must will be declared abstract
     * @param array $parameters
     */
    //abstract public function resolveData(array $parameters = []);
    /**
     * @param array $parameters
     */
    public function resolveData(array $parameters = [])
    {
        // return some custom data
    }

    /**
     * @param string $path
     * @param $folder
     *
     * @return array
     */
    protected function resolvePaths($path, $folder)
    {
        if (!file_exists($path)) {
            return [];
        }

        $finder = Finder::create();
        $finder->files()->in($path);

        $data = [];

        foreach ($finder as $file) {
            if (FileUtils::read($file->getRealPath()) === "") {
                continue;
            }

            $data[] = sprintf(
                self::RESOURCES_PUBLIC_PATH,
                $this->widget->formatName(),
                $folder,
                $file->getFilename()
            );
        }

        return $data;
    }

    /**
     *
     * @return bool|void
     * @throws WidgetControllerErrorException
     */
    protected function checkCacheParameters()
    {
        $parameters = static::$parameters;

        if (static::$enabledRenderCache === false) {
            return;
        }

        if (empty($parameters)) {
            return;
        }

        if (!isset($parameters['_widget']['type'])) {
            throw new WidgetControllerErrorException($this, "Widget type parameter must be provided to get cache data");
        }

        if ($parameters['_widget']['type'] !== WidgetType::CACHE) {
            return;
        }

        if (isset($parameters[self::EDITOR_INDEX]) && $parameters[self::EDITOR_INDEX] === true) {
            return;
        }

        if (isset($parameters[self::PREVIEW_INDEX]) && $parameters[self::PREVIEW_INDEX] === true) {
            return;
        }

        if (!isset($parameters['_uuid'])) {
            throw new WidgetControllerErrorException($this, "_uuid parameter must be provided to get cache data");
        }

        if (!isset($parameters['_locale'])) {
            throw new WidgetControllerErrorException($this, "_locale parameter must be provided to get cache data");
        }

        return true;
    }

    /**
     *
     * @return string
     */
    public function buildCacheKey()
    {
        return sprintf(
            self::WIDGET_CACHE_KEY,
            static::$parameters['_locale'],
            static::$parameters['_uuid'],
            md5(serialize(static::$parameters)),
            self::$uniqueCacheKey !== null ? self::$uniqueCacheKey: md5($this->get('request_stack')->getMasterRequest()->getRequestUri())
        );
    }

    /**
     * @return string
     */
    public function buildPartialCacheKey()
    {
        return sprintf(self::WIDGET_PARTIAL_CACHE_KEY, static::$parameters['_locale'], static::$parameters['_uuid']);
    }

    /**
     * Method to return values from parameters
     *
     * @param $param
     * @param string $key
     * @param string $property
     * @param null   $defaultValue
     *
     * @return null
     */
    protected static function getParameterValue($param, $key = "_parameters", $property = "value", $defaultValue = null)
    {
        if (empty(static::$parameters[$key][$param][$property])) {
            return $defaultValue;
        }

        return static::$parameters[$key][$param][$property];
    }

    /**
     * @param $param
     * @param null $defaultValue
     * @return null
     */
    protected static function getParameterData($param, $defaultValue = null)
    {
        return self::getParameterValue($param, "_parameters", "value", $defaultValue);
    }

    /**
     * @param $content
     * @param null $key
     * @param null $ttl
     */
    protected function saveInCache($content, $key = null, $ttl = null)
    {
        $cache = $this->getCacheService();

        $cache->set(
            $key === null ? $this->buildCacheKey() : $key,
            $content,
            $ttl === null ? static::$cacheTTL : $ttl
        );
    }

    /**
     * @param null $key
     *
     * @return mixed|null
     */
    protected function getFromCache($key = null)
    {
        $cache = $this->getCacheService();

        $key = $key === null ? $this->buildCacheKey() : $key;

        return $cache->get($key);
    }

    /**
     * @return RedisWrapper
     */
    protected function getCacheService(): RedisWrapper
    {
        return self::isEditor() ?
            $this->get('designer.cache.redis') :
            $this->get('comitium5_common_widgets.cache.redis');
    }

    /**
     * @return ParametersStorage
     */
    protected function getParametersStorage(): ParametersStorage
    {
        return self::isEditor() ?
            $this->get('designer.parameters_storage') :
            $this->get('comitium5_common_widgets.parameters_storage');
    }

    /**
     * @deprecated In favor to v2. Use for v1 calls only
     * @param null $locale
     * @param array $subSite
     *
     * @return object
     * @throws WidgetControllerErrorException
     */
    protected function initApiService($locale = null, array $subSite = [])
    {
        $locale  = $locale === null  ? self::getLocale() : $locale;
        $subSite = !empty($subSite) ? $subSite : self::getSubsite();

        if (empty($locale)) {
            throw new WidgetControllerErrorException("Locale must be provided");
        }

        $api = $this->get('comitium5_api_client');
        $api->setAcceptLanguage($locale);

        if (!empty($subSite["acronym"])) {
            $api->buildClient([
                "subsite" => $subSite["acronym"],
            ]);
        }

        $this->apiService = $api;

        return $api;
    }

    /**
     * @param null $sitePrefix
     * @param null $subSiteAcronym
     * @param null $localeCode
     * @param null $token
     *
     * @return Client
     */
    protected function createApiInstance(
        $sitePrefix = null,
        $subSiteAcronym = null,
        $localeCode = null,
        $token = null
    ) {
        $sitePrefix     = $sitePrefix === null ? self::getSite()["prefix"] : $sitePrefix;
        $subSiteAcronym = $subSiteAcronym === null ? self::getSubsite()["acronym"] : $subSiteAcronym;
        $localeCode     = $localeCode === null ? self::getLocale() : $localeCode;

        return $this
            ->get("designer.api")
            ->buildClient(
                $sitePrefix,
                $subSiteAcronym,
                $localeCode,
                $token
            );
    }

    /**
     * @param $locale
     * @param null $key
     * @return array
     */
    protected static function getWidgetFields($locale, $key = null)
    {
        $data = [];

        if (isset(static::$parameters[self::FIELDS_INDEX])) {
            $fields = static::$parameters[self::FIELDS_INDEX];

            if (isset($fields[$locale])) {
                $fields = $fields[$locale];
                if ($key !== null) {
                    if (isset($fields[$key])) {
                        $data  = $fields[$key];
                    }
                } else {
                    $data = $fields;
                }
            }
        }
        return $data;
    }

    /**
     * @return array
     * @deprecated Use getPageFromRequest method instead
     */
    protected static function getPage()
    {
        return isset(static::$parameters[self::PAGE_INDEX]) ? static::$parameters[self::PAGE_INDEX] : [];
    }

    /**
     * @return array
     */
    protected function getEntityFromRequest()
    {
        $request = $this->get('request_stack')->getMasterRequest();

        return $request->get(self::ENTITY_INDEX, null);
    }

    /**
     * @return bool
     */
    protected static function isEditor()
    {
        if (isset(static::$parameters[self::EDITOR_INDEX])) {
            return static::$parameters[self::EDITOR_INDEX];
        }

        return false;
    }

    /**
     * @return bool
     */
    protected static function isPreview()
    {
        if (isset(static::$parameters[self::PREVIEW_INDEX])) {
            return static::$parameters[self::PREVIEW_INDEX];
        }

        return false;
    }

    /**
     * @return array
     */
    protected static function getLocales()
    {
        if (isset(static::$parameters[self::LOCALES_INDEX])) {
            return static::$parameters[self::LOCALES_INDEX];
        }

        return [];
    }

    /**
     * @return mixed
     */
    protected static function getUuid()
    {
        if (isset(static::$parameters[self::UUID_INDEX])) {
            return static::$parameters[self::UUID_INDEX];
        }

        return null;
    }

    /**
     * @return mixed
     */
    protected static function getWidgetData()
    {
        if (isset(static::$parameters[self::WIDGET_INDEX])) {
            return static::$parameters[self::WIDGET_INDEX];
        }

        return null;
    }

    /**
     *
     */
    public function resetDefaultValues()
    {
        if ($this->apiService instanceof ApiProvider) {
            $this->apiService->useCache(true);
            $this->apiService->setAcceptLanguage(self::getLocale());
            $this->apiService->setForceGetContent(false);
        }
    }

    /**
     * @return bool
     */
    protected function loadFixtures()
    {
        if (isset(static::$parameters[self::FIXTURES_INDEX])) {
            return true;
        }

        return false;
    }

    /**
     * @param $parameters
     * @deprecated In favor to decryptWidgetParameters
     * @return array|mixed
     * @throws \Exception
     */
    protected function getWidgetParameters($parameters)
    {
        return $this->decryptWidgetParameters($parameters);
    }

    /**
     * @param $parameters
     * @return array|mixed
     * @throws \Exception
     */
    protected function decryptWidgetParameters($parameters)
    {
        if (empty($parameters)) {
            return self::$parameters;
        }

        $encryptor = new DataEncryption($parameters);

        return json_decode($encryptor->decrypt(), true);
    }

    /**
     * @return string
     * @throws \Exception
     */
    protected function encryptWidgetParameters()
    {
        $encryptor = new DataEncryption(json_encode(self::$parameters));

        return $encryptor->encrypt();
    }

    /**
     * @return array
     */
    protected function getPageFromRequest()
    {
        $request = $this
            ->get('request_stack')
            ->getMasterRequest();

        $pageId = $request->attributes->get('pageId');

        if (self::isEditor() && $pageId !== null) {
            return ['id' => $pageId];
        }

        return parent::getPageFromRequest();
    }

    /**
     * @param array $viewParameters
     *
     * @return array
     */
    protected function addGlobalVars(array $viewParameters): array
    {
        return array_merge($viewParameters, [
            self::GLOBAL_VARS_INDEX => [
                "isEditor"  => self::isEditor(),
                "isPreview" => self::isPreview(),
            ],
        ]);
    }
}
