<?php
namespace Newland\Toubiz\Sync\Neos\Domain\Repository;

/*
 * 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\AbstractQuery;
use Doctrine\ORM\QueryBuilder;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Persistence\Generic\Query;
use Newland\Toubiz\Sync\Neos\Domain\Filter\AttributeFilter;
use Newland\Toubiz\Sync\Neos\Domain\Filter\FilterInterface;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Model\Attribute;

/**
 * Attribute repository.
 *
 * @Flow\Scope("singleton")
 *
 * @method Attribute|null findOneByOriginalId(string $originalId)
 * @method Attribute|null findOneByIdentificationHash(string $identificationHash)
 */
class AttributeRepository extends AbstractRepository
{

    /**
     * @param Article $article
     * @param string $name
     * @return Attribute|null
     */
    public function findOneByArticleAndName(Article $article, string $name)
    {
        $attribute = null;

        $result = $this->createQueryBuilder('attribute')
            ->innerJoin('attribute.article', 'article')
            ->andWhere('article.originalId = :originalId')
            ->setParameter('originalId', $article->getOriginalId())
            ->andWhere('attribute.name = :name')
            ->setParameter('name', $name)
            ->setMaxResults(1)
            ->getQuery()
            ->execute();

        if (is_array($result) && \count($result)) {
            $attribute = $result[0];
        }
        return $attribute;
    }

    /**
     * @param Article $article
     * @param string $name
     * @param mixed $data
     * @return array|null
     */
    public function findOneByArticleAndNameAndData(Article $article, string $name, $data)
    {
        $attribute = null;

        $result = $this->createQueryBuilder('attribute')
            ->innerJoin('attribute.article', 'article')
            ->andWhere('article.originalId = :originalId')
            ->setParameter('originalId', $article->getOriginalId())
            ->andWhere('attribute.name = :name')
            ->setParameter('name', $name)
            ->andWhere('attribute.data = :data')
            ->setParameter('data', $data)
            ->setMaxResults(1)
            ->getQuery()
            ->execute();

        if (is_array($result) && \count($result)) {
            $attribute = $result[0];
        }
        return $attribute;
    }

    public function getArticlesUsingAttributesInQuery(string $attribute, array $values): QueryBuilder
    {
        $query = $this->createQueryBuilder('attributes_' . $attribute);
        $query->select('IDENTITY(attributes_' . $attribute . '.article)');

        $query->andWhere(
            $query->expr()->andX(
                $query->expr()->eq('attributes_' . $attribute . '.name', ':' . $attribute . '_name'),
                $query->expr()->in('attributes_' . $attribute . '.data', ':' . $attribute . '_values')
            )
        );

        $query->setParameters(
            [
                $attribute . '_name' => $attribute,
                $attribute . '_values' => $values,
            ]
        );

        return $query;
    }


    public function getArticlesUsingAttributesMinMaxQuery(string $attribute, float $min, float $max): QueryBuilder
    {
        $query = $this->createQueryBuilder('attributes_' . $attribute);
        $query->select('IDENTITY(attributes_' . $attribute . '.article)');

        $query->andWhere(
            $query->expr()->andX(
                $query->expr()->eq('attributes_' . $attribute . '.name', ':' . $attribute . '_name'),
                $query->expr()->between(
                    'CAST(attributes_' . $attribute . '.data as decimal)',
                    ':' . $attribute . '_min',
                    ':' . $attribute . '_max'
                )
            )
        );

        $query->setParameters(
            [
                $attribute . '_name' => $attribute,
                $attribute . '_min' => $min,
                $attribute . '_max' => $max,
            ]
        );

        return $query;
    }

    public function getNumberBoundsForName(string $name, int $roundTo = 10, int $articleType = null): array
    {
        $query = $this->createQueryBuilder('attribute');
        $query->select(
            [
                'MIN(CAST(attribute.data AS int)) as min',
                'MAX(CAST(attribute.data AS int)) as max',
            ]
        );

        $properties = [
            'name' => $name,
            'type' => Attribute::TYPE_NUMBER
        ];
        $where = [
            $query->expr()->eq('attribute.name', ':name'),
            $query->expr()->eq('attribute.type', ':type')
        ];
        if ($articleType !== null) {
            $query->leftJoin('attribute.article', 'article');
            $where[] = $query->expr()->eq('article.mainType', ':articleType');
            $properties['articleType'] = $articleType;
        }
        $query->andWhere($query->expr()->andX(...$where));

        $results = $query->getQuery()->execute($properties, AbstractQuery::HYDRATE_ARRAY);

        $min = (int) ($results[0]['min'] ?? 0);
        $max = (int) ($results[0]['max'] ?? 0);
        return [
            'min' => floor($min / $roundTo) * $roundTo,
            'max' => ceil($max / $roundTo) * $roundTo,
        ];
    }

    public function getMostPopularTagsByArticleType(int $articleType, int $limit = 10): array
    {
        $query = $this->createQueryBuilder('attribute');

        return $query->join('attribute.article', 'article')
            ->select(
                [
                    'attribute.data',
                    'COUNT(attribute.data) as count',
                ]
            )->andWhere(
                $query->expr()->andX(
                    $query->expr()->eq('article.mainType', $articleType),
                    $query->expr()->like('attribute.name', ':name')
                )
            )->setParameters(
                [ 'name' => 'tag' ]
            )->groupBy('attribute.data')
            ->orderBy('COUNT(attribute.data)', Query::ORDER_DESCENDING)
            ->setMaxResults($limit)
            ->getQuery()
            ->execute();
    }


    public function getAllTagsByArticleType(int $articleType): array
    {
        $query = $this->createQueryBuilder('attribute');

        return $query->join('attribute.article', 'article')
            ->select(
                [
                    'attribute.data'
                ]
            )->andWhere(
                $query->expr()->andX(
                    $query->expr()->eq('article.mainType', $articleType),
                    $query->expr()->like('attribute.name', ':name')
                )
            )->setParameters(
                [ 'name' => 'tag' ]
            )->groupBy('attribute.data')
            ->orderBy('attribute.data', Query::ORDER_ASCENDING)
            ->getQuery()
            ->execute();
    }

    /**
     * Applies filter functions (from the attribute filter) onto
     * the given query builder.
     *
     * @param AttributeFilter $filter
     * @param QueryBuilder $query
     * @return QueryBuilder
     */
    protected function applyFilter(FilterInterface $filter, QueryBuilder $query): QueryBuilder
    {
        $this->applyBasicFilter($filter, $query);

        $originalIds = $filter->getOriginalIds();
        if (!empty($originalIds)) {
            $query->andWhere(
                $query->expr()->in('attribute.originalid', $originalIds)
            );
        }

        return $query;
    }
}
