<?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\Repository\Household;

use Chill\MainBundle\Entity\AddressReference;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;

final readonly class HouseholdACLAwareRepository implements HouseholdACLAwareRepositoryInterface
{
    public function __construct(private EntityManagerInterface $em, private AuthorizationHelper $authorizationHelper, private Security $security) {}

    public function addACL(QueryBuilder $qb, string $alias = 'h'): QueryBuilder
    {
        $centers = $this->authorizationHelper->getReachableCenters(
            $this->security->getUser(),
            PersonVoter::SEE // the authorization to see a household is the same as seeing a person
        );

        if ([] === $centers) {
            return $qb
                ->andWhere("'FALSE' = 'TRUE'");
        }

        $qb
            ->join($alias.'.members', 'members')
            ->join('members.person', 'person')
            ->join('person.centerCurrent', 'person_center_current')
            ->andWhere(
                $qb->expr()->in('person_center_current.center', ':centers')
            )
            ->setParameter('centers', $centers);

        return $qb;
    }

    public function buildQueryByAddressReference(AddressReference $addressReference): QueryBuilder
    {
        $qb = $this->em->createQueryBuilder();
        $qb
            ->select('h')
            ->from(Household::class, 'h')
            ->join('h.addresses', 'address')
            ->where(
                $qb->expr()->eq('address.addressReference', ':reference')
            )
            ->setParameter(':reference', $addressReference)
            ->andWhere(
                $qb->expr()->andX(
                    $qb->expr()->lte('address.validFrom', ':today'),
                    $qb->expr()->orX(
                        $qb->expr()->isNull('address.validTo'),
                        $qb->expr()->gt('address.validTo', ':today')
                    )
                )
            )
            ->setParameter('today', new \DateTime('today'));

        return $qb;
    }

    public function countByAddressReference(AddressReference $addressReference): int
    {
        $qb = $this->buildQueryByAddressReference($addressReference);
        $qb = $this->addACL($qb);

        return $qb->select('COUNT(h)')
            ->getQuery()
            ->getSingleScalarResult();
    }

    public function findByAddressReference(
        AddressReference $addressReference,
        ?int $firstResult = 0,
        ?int $maxResult = 50,
    ): array {
        $qb = $this->buildQueryByAddressReference($addressReference);
        $qb = $this->addACL($qb);

        return $qb
            ->select('h')
            ->setFirstResult($firstResult)
            ->setMaxResults($maxResult)
            ->getQuery()
            ->getResult();
    }
}
