<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Ferienpass\CoreBundle\Entity\Consent;
use Ferienpass\CoreBundle\Entity\ConsentForm;
use Ferienpass\CoreBundle\Entity\Participant\ParticipantInterface;
use Ferienpass\CoreBundle\Entity\User;

class ConsentRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Consent::class);
    }

    /**
     * Number of distinct people holding a valid, non-revoked signature for the
     * given consent-text hash. Matches by hash (not by form) so migrated,
     * form-less host signatures are counted too — the same rule
     * {@see \Ferienpass\CoreBundle\ConsentManager\ConsentManager} gates on.
     */
    public function countValidSigneesByHash(string $hash): int
    {
        return (int) $this->createQueryBuilder('c')
            ->select('COUNT(DISTINCT c.user)')
            ->leftJoin('c.consentText', 't')
            ->where('c.hash = :hash OR t.hash = :hash')
            ->andWhere('c.decision = 1')
            ->andWhere('c.revokedAt IS NULL')
            ->andWhere('c.validUntil IS NULL OR c.validUntil >= CURRENT_TIMESTAMP()')
            ->setParameter('hash', $hash)
            ->getQuery()
            ->getSingleScalarResult()
        ;
    }

    /**
     * @param ConsentForm[]          $consentForms
     * @param ParticipantInterface[] $participants
     *
     * @return Consent[]
     */
    public function findSignatures(array $consentForms, ?User $user, array $participants): array
    {
        if ([] === $consentForms) {
            return [];
        }

        $qb = $this->createQueryBuilder('c')
            ->addSelect('f')
            ->addSelect('t')
            ->leftJoin('c.consentForm', 'f')
            ->leftJoin('c.consentText', 't')
        ;

        $formOrHash = $qb->expr()->orX();
        $formOrHash->add($qb->expr()->in('c.consentForm', ':forms'));
        $qb->setParameter('forms', $consentForms);

        $hashes = array_filter(array_map(static fn (ConsentForm $f) => $f->getConsentText()?->getHash(), $consentForms));
        if ([] !== $hashes) {
            $formOrHash->add($qb->expr()->in('c.hash', ':hashes'));
            $formOrHash->add($qb->expr()->in('t.hash', ':hashes'));
            $qb->setParameter('hashes', $hashes);
        }

        $qb->where($formOrHash);

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

        if ($user instanceof User) {
            $orX->add($qb->expr()->eq('c.user', ':user'));
            $qb->setParameter('user', $user);
        }

        if ([] !== $participants) {
            $orX->add($qb->expr()->in('c.participant', ':participants'));
            $qb->setParameter('participants', $participants);

            $users = array_filter(array_map(static fn (ParticipantInterface $p) => $p->getUser(), $participants));
            if ([] !== $users) {
                $orX->add($qb->expr()->in('c.user', ':users'));
                $qb->setParameter('users', $users);
            }
        }

        if ($orX->count() > 0) {
            $qb->andWhere($orX);
        } else {
            return [];
        }

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

    /**
     * @return Consent[]
     */
    public function findAllForUser(User $user): array
    {
        return $this->createQueryBuilder('c')
            ->leftJoin('c.participant', 'p')
            ->where('c.user = :user')
            ->orWhere('p.user = :user')
            ->setParameter('user', $user)
            ->getQuery()
            ->getResult()
        ;
    }
}
