<?php
namespace Newland\Toubiz\Poi\Neos\ViewHelpers\EntityList\Controller;

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

use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\QueryBuilder;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Configuration\Exception\InvalidConfigurationException;
use Neos\Flow\Log\Utility\LogEnvironment;
use Neos\FluidAdaptor\Core\Widget\AbstractWidgetController;
use Newland\NeosCommon\LinkHandler\LinkingService;
use Newland\NeosCommon\Service\NodeService;
use Newland\Toubiz\Api\Constants\EntityType;
use Newland\Toubiz\Poi\Neos\Service\EntityListService;
use Newland\Toubiz\Poi\Neos\Service\LinkListService;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Model\RelatedLists\ArticleList;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;

class ArticlesController extends AbstractWidgetController
{
    /**
     * @var array
     * @Flow\InjectConfiguration(path="cityDetails.links.data.attractions")
     */
    protected $poiListLinkData;

    /**
     * @var EntityListService
     * @Flow\Inject()
     */
    protected $entityListService;

    /**
     * @var ArticleList
     */
    private $articleList;

    /**
     * @var Article
     */
    private $article;

    /**
     * @var ArticleRepository
     * @Flow\Inject()
     */
    protected $articleRepository;

    /**
     * @var LinkingService
     * @Flow\Inject()
     */
    protected $linkingService;

    /**
     * @var LinkListService
     * @Flow\Inject()
     */
    protected $linkListService;

    /**
     * @var NodeService
     * @Flow\Inject()
     */
    protected $nodeService;

    public function initializeAction(): void
    {
        $this->articleList = $this->widgetConfiguration['articleList'] ?? null;
        $this->article = $this->widgetConfiguration['article'] ?? null;
    }

    public function indexAction(): void
    {
        $articles = [];
        $totalCount = 0;
        $limit = $this->widgetConfiguration['limit'] ?? 11;
        $articleIds = $this->getArticleIdentifiers();

        if (!empty($articleIds)) {
            $queryByArticleIds = $this->query(EntityType::ARTICLE, $articleIds);

            $totalCount = (clone $queryByArticleIds)
                    ->select('COUNT(article) as count')
                    ->getQuery()
                    ->execute([], AbstractQuery::HYDRATE_ARRAY)[0]['count'] ?? 0;

            $articles = $queryByArticleIds
                ->setMaxResults($limit)
                ->getQuery()
                ->execute();
        }


        if ($totalCount < $limit) {
            $fillerArticles = $this->getFillerArticles($articleIds);

            if (count($fillerArticles) > 0) {
                $totalCount += count($fillerArticles);
                $articles = array_merge($articles, array_slice($fillerArticles, 0, $limit - \count($articles)));
            }
        }

        $this->view->assignMultiple(
            [
                'contentArguments' => [
                    'listLink' => $this->generateListLink(),
                    'articles' => $articles,
                    'totalCount' => $totalCount,
                ],
            ]
        );
    }

    /** @param string[] $articleIds */
    protected function getFillerArticles(array $articleIds): array
    {
        $fillerArticles = [];
        $categoryIds = $this->getCategoryIdentifiers();

        if (!empty($categoryIds)) {
            $queryByCategoryIds = $this->query(EntityType::CATEGORY, $categoryIds);

            if (!empty($articleIds)) {
                $queryByCategoryIds = $queryByCategoryIds->andWhere(
                    $queryByCategoryIds->expr()->notIn('article.Persistence_Object_Identifier', $articleIds)
                );
            }

            /** @var Collection|array $fillerArticles */
            $articles = $queryByCategoryIds
                ->setMaxResults($this->widgetConfiguration['limit'] ?? 11)
                ->getQuery()
                ->execute();
            if ($articles instanceof Collection) {
                $fillerArticles = $articles->toArray();
            } else {
                $fillerArticles = (array) $articles;
            }
        }

        return $fillerArticles;
    }

    private function getCategoryIdentifiers(): array
    {
        if ($this->articleList !== null) {
            return $this->articleList->getCategoryIdentifiers();
        }

        return [];
    }

    private function getArticleIdentifiers(): array
    {
        if ($this->articleList !== null) {
            return $this->articleList->getArticleIdentifiers();
        }

        return [];
    }

    private function generateListLink(): string
    {
        $node = $this->widgetConfiguration['node'];

        $siteName = $this->nodeService->getCurrentSiteNodeName($node);

        $configuration = $this->linkListService->getCityDetailConfigurationByClient($siteName);
        $this->poiListLinkData = $configuration['data']['attractions'];

        if (empty($this->poiListLinkData)) {
            throw new InvalidConfigurationException(
                'No POI list page configuration set.' .
                ' Please configure in "Newland.Toubiz.Poi.Neos.cityDetails.links.{siteName}.data.attractions".'
            );
        }

        $baseNode = $this->widgetConfiguration['node'];
        $target = $this->poiListLinkData['target'];

        $node = $this->linkingService->convertUriToObject($target, $baseNode);
        $arguments = $this->entityListService->getQuery(
            $this->getCategoryIdentifiers(),
            $this->article,
            $this->poiListLinkData['queryParameters']
        );

        try {
            $uri = $this->linkingService->createNodeUri(
                $this->getControllerContext(),
                $node,
                $baseNode,
                null,
                false,
                $arguments
            );
        } catch (\Exception $exception) {
            $this->logger->error($exception->getMessage(), LogEnvironment::fromMethodName(__METHOD__));
            $uri = '';
        }

        return $uri;
    }

    private function query(string $type, array $identifiers): QueryBuilder
    {
        $query = $this->articleRepository->createQueryBuilder('article');

        if ($type === EntityType::ARTICLE) {
            $query->andWhere($query->expr()->in('article.Persistence_Object_Identifier', $identifiers));
        } else {
            $query->leftJoin('article.categories', 'category');
            $query->andWhere($query->expr()->in('category.Persistence_Object_Identifier', $identifiers));
            if ($this->article->getIsCity()) {
                $query
                    ->leftJoin('article.cities', 'city')
                    ->andWhere(
                        $query->expr()->eq(
                            'city',
                            $query->expr()->literal($this->article->getPersistenceObjectIdentifier())
                        )
                    );
            }
        }
        return $query;
    }
}
