<?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\PersonBundle\Export\Helper;

use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

/**
 * Filter accompanying period list and related, removing confidential ones
 * based on ACL rules.
 */
final readonly class FilterListAccompanyingPeriodHelper implements FilterListAccompanyingPeriodHelperInterface
{
    private bool $filterStatsByCenters;

    public function __construct(
        private CenterRepositoryInterface $centerRepository,
        private AuthorizationHelperInterface $authorizationHelper,
        ParameterBagInterface $parameterBag,
    ) {
        $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
    }

    public function addFilterAccompanyingPeriods(QueryBuilder &$qb, array $requiredModifiers, array $acl, User $user, array $data = []): void
    {
        $centers = match ($this->filterStatsByCenters) {
            true => array_map(static fn ($el) => $el['center'], $acl),
            false => $this->centerRepository->findAll(),
        };

        // add filtering on confidential accompanying period. The confidential is applyed on the current status of
        // the accompanying period (we do not use the 'calc_date' here
        $aclConditionsOrX = $qb->expr()->orX(
            // either the current user is the refferer for the course
            'acp.user = :list_acp_current_user',
        );
        $qb->setParameter('list_acp_current_user', $user);

        $i = 0;
        foreach ($centers as $center) {
            $scopes = $this->authorizationHelper->getReachableScopes($user, AccompanyingPeriodVoter::SEE_DETAILS, $center);
            $scopesConfidential =
                $this->authorizationHelper->getReachableScopes($user, AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, $center);
            $orScopes = $qb->expr()->orX();

            foreach ($scopes as $scope) {
                $scopeCondition = match (in_array($scope, $scopesConfidential, true)) {
                    true => sprintf(':scope_%s MEMBER OF acp.scopes', $i),
                    false => $qb->expr()->andX(
                        'acp.confidential = FALSE',
                        sprintf(':scope_%s MEMBER OF acp.scopes', $i),
                    ),
                };

                $orScopes->add($qb->expr()->orX(sprintf('acp.user = :user_%d', $i), $scopeCondition));
                $qb->setParameter("scope_{$i}", $scope);
                $qb->setParameter("user_{$i}", $user);
                ++$i;
            }

            if ($this->filterStatsByCenters) {
                $andX = $qb->expr()->andX(
                    $qb->expr()->exists(
                        'SELECT 1 FROM '.AccompanyingPeriodParticipation::class." acl_count_part_{$i}
                    JOIN ".PersonCenterHistory::class." acl_count_person_history_{$i} WITH IDENTITY(acl_count_person_history_{$i}.person) = IDENTITY(acl_count_part_{$i}.person)
                    WHERE acl_count_part_{$i}.accompanyingPeriod = acp.id AND acl_count_person_history_{$i}.center IN (:authorized_center_{$i})
                    AND acl_count_person_history_{$i}.startDate <= CURRENT_DATE() AND (acl_count_person_history_{$i}.endDate IS NULL or acl_count_person_history_{$i}.endDate > CURRENT_DATE())
                    "
                    ),
                    $orScopes,
                );
                $qb->setParameter('authorized_center_'.$i, $center);
                $aclConditionsOrX->add($andX);
            } else {
                $aclConditionsOrX->add($orScopes);
            }

            ++$i;
        }

        $qb->andWhere($aclConditionsOrX);
    }
}
