<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\EventBundle\Repository;

use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;

final readonly class EventACLAwareRepository implements EventACLAwareRepositoryInterface
{
    public function __construct(
        private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser,
        private EntityManagerInterface $entityManager,
        private Security $security,
    ) {}

    /**
     * @throws NonUniqueResultException
     * @throws NoResultException
     */
    public function countAllViewable(array $filters): int
    {
        if (!$this->security->getUser() instanceof User) {
            return 0;
        }

        $qb = $this->buildQueryByAllViewable($filters);
        $this->addFilters($filters, $qb);

        $qb->select('COUNT(event.id)');

        return $qb->getQuery()->getSingleScalarResult();
    }

    public function findAllViewable(array $filters, int $offset = 0, int $limit = 50): array
    {
        if (!$this->security->getUser() instanceof User) {
            return [];
        }

        $qb = $this->buildQueryByAllViewable($filters)->select('event');
        $this->addFilters($filters, $qb);

        $qb->setFirstResult($offset)->setMaxResults($limit);

        $qb->addOrderBy('event.date', 'DESC');

        return $qb->getQuery()->getResult();
    }

    private function addFilters(array $filters, QueryBuilder $qb): void
    {
        if (($filters['q'] ?? '') !== '') {
            $qb->andWhere('event.name LIKE :content');
            $qb->setParameter('content', '%'.$filters['q'].'%');
        }

        if (array_key_exists('dates', $filters)) {
            $dates = $filters['dates'];
            if (null !== ($dates['from'] ?? null)) {
                $qb->andWhere('event.date >= :date_from');
                $qb->setParameter('date_from', $dates['from']);
            }
            if (null !== ($dates['to'] ?? null)) {
                $qb->andWhere('event.date <= :date_to');
                $qb->setParameter('date_to', $dates['to']);
            }
        }

        if (0 < count($filters['event_types'] ?? [])) {
            $qb->andWhere('event.type IN (:event_types)');
            $qb->setParameter('event_types', $filters['event_types']);
        }

        if (0 < count($filters['centers'] ?? [])) {
            $qb->andWhere('event.center IN (:centers)');
            $qb->setParameter('centers', $filters['centers']);
        }

        if (0 < count($filters['responsables'] ?? [])) {
            $qb->andWhere('event.moderator IN (:responsables)');
            $qb->setParameter('responsables', $filters['responsables']);
        }
    }

    public function buildQueryByAllViewable(array $filters): QueryBuilder
    {
        $qb = $this->entityManager->createQueryBuilder();
        $qb->from(Event::class, 'event');

        $aclConditions = $qb->expr()->orX();

        $i = 0;
        foreach ($this->authorizationHelperForCurrentUser->getReachableCenters(EventVoter::SEE) as $center) {
            foreach ($this->authorizationHelperForCurrentUser->getReachableScopes(EventVoter::SEE, $center) as $scopes) {
                $aclConditions->add(
                    $qb->expr()->andX(
                        'event.circle IN (:scopes_'.$i.')',
                        $qb->expr()->orX(
                            'event.center = :center_'.$i,
                            $qb->expr()->exists(
                                'SELECT 1 FROM '.Participation::class.' participation_'.$i.' JOIN participation_'.$i.'.event event_'.$i.
                                ' JOIN '.Person\PersonCenterHistory::class.' person_center_history_'.$i.
                                ' WITH IDENTITY(person_center_history_'.$i.'.person) = IDENTITY(participation_'.$i.'.person) '.
                                ' AND event_'.$i.'.date <= person_center_history_'.$i.'.startDate AND (person_center_history_'.$i.'.endDate IS NULL OR person_center_history_'.$i.'.endDate > event_'.$i.'.date) '.
                                ' WHERE participation_'.$i.'.event = event'
                            )
                        )
                    )
                );
                $qb->setParameter('scopes_'.$i, $scopes);
                $qb->setParameter('center_'.$i, $center);

                ++$i;
            }
        }

        if (0 === $i) {
            $aclConditions->add('FALSE = TRUE');
        }

        $qb
            ->andWhere(
                $qb->expr()->orX(
                    'event.createdBy = :user',
                    $aclConditions
                )
            );

        $qb->setParameter('user', $this->security->getUser());

        return $qb;
    }
}
