<?php
namespace Newland\Toubiz\Sync\Neos\Service;

/*
 * This file is part of the "toubiz-sync-neos" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Doctrine\ORM\Query\Expr\Comparison;
use Doctrine\ORM\QueryBuilder;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Configuration\ConfigurationManager;
use Neos\Flow\Configuration\Exception\InvalidConfigurationException;
use Newland\Toubiz\Api\ObjectAdapter\Concern\ArticleConstants;
use Newland\Toubiz\Sync\Neos\Domain\Filter\ArticleFilter;

class ArticleClientFilterService
{
    /**
     * @var ConfigurationManager
     * @Flow\Inject()
     */
    protected $configurationManager;

    public function addClientWhereClause(QueryBuilder $query, ArticleFilter $filter)
    {
        if ($filter->getMainType() !== null) {
            $mainTypes = [ $filter->getMainType() ];
        } else {
            $mainTypes = ArticleConstants::ALL_TYPES;
        }

        $typeWheres = [];
        foreach ($mainTypes as $mainType) {
            /*
             * WHERE (
             *     (mainType = 0 AND client = 'clientA')
             *     OR (mainType = 1 AND client = 'clientA')
             *     OR (mainType = 2)
             *     OR (mainType = 3)
             * )
             */
            if ($this->needsFilter('mainType', $mainType, $filter)) {
                $typeWheres[] = $query->expr()->andX(
                    $this->generateEqExprFor($query, 'mainType', $mainType, $filter, 'article'),
                    $query->expr()->eq('article.mainType', $mainType)
                );
            } else {
                $typeWheres[] = $query->expr()->eq('article.mainType', $mainType);
            }
        }
        $orx = $query->expr()->orX()->addMultiple($typeWheres);
        $query->andWhere($orx);
    }

    /**
     * @param QueryBuilder $query
     * @param string $field
     * @param mixed $value
     * @param ArticleFilter $filter
     * @param string $table
     * @return Comparison|null
     */
    public function generateEqExprFor(
        QueryBuilder $query,
        string $field,
        $value,
        ArticleFilter $filter,
        string $table = null
    ) {
        $configuration = $this->getConfiguration($filter->getCurrentSite());

        $clientField = $this->getClientField($field, $configuration);
        $clientValue = $this->getClientValue($field, (string) $value, $configuration);

        $expression = null;
        if ($clientField && $clientValue) {
            if ($table) {
                $clientField = $table . '.' . $clientField;
            }
            $expression = $query->expr()->eq($clientField, $query->expr()->literal($clientValue));
        }

        return $expression;
    }

    public function needsFilter(string $field, $mainType, ArticleFilter $filter): bool
    {
        try {
            $configuration = $this->getConfiguration($filter->getCurrentSite());
            $fieldConfiguration = $this->getArrayValue($configuration, $field);
            $valueConfiguration = $this->getArrayValue($fieldConfiguration, 'values');
            $client = $this->getArrayValue($valueConfiguration, $mainType);
            $needsFilter = !empty($client);
        } catch (\Exception $e) {
            return false;
        }

        return $needsFilter;
    }

    protected function getConfigPath(string $currentSite): string
    {
        if (empty($currentSite)) {
            throw new InvalidConfigurationException('Current site must be set.', 1545254990);
        }

        return 'Newland.Toubiz.Poi.Neos.clientFilter.' . $currentSite;
    }

    private function getClientField(string $field, array $configuration): string
    {
        $fieldConfiguration = $this->getArrayValue($configuration, $field);
        $clientField = $this->getArrayValue($fieldConfiguration, 'clientField');

        return $clientField;
    }

    private function getClientValue(string $field, string $fieldValue, array $configuration): string
    {
        $fieldConfiguration = $this->getArrayValue($configuration, $field);
        $valuesConfiguration = $this->getArrayValue($fieldConfiguration, 'values');
        $value = $this->getArrayValue($valuesConfiguration, $fieldValue);

        return $value;
    }

    /**
     * This service does a lot of array parsing. Return null instead of throwing exceptions.
     *
     * @param mixed $array
     * @param mixed $key
     * @return mixed
     */
    private function getArrayValue($array, $key)
    {
        $value = null;

        if (is_array($array) && array_key_exists($key, $array)) {
            $value = $array[$key];
        }

        return $value;
    }

    private function getConfiguration(string $currentSite = null)
    {
        if ($currentSite === null) {
            return [];
        }
        return $this->configurationManager->getConfiguration(
            ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
            $this->getConfigPath($currentSite)
        );
    }
}
