<?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\Search;

use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\PersonNotDuplicateRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class SimilarPersonMatcher
{
    final public const SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL = 'alphabetical';

    final public const SIMILAR_SEARCH_ORDER_BY_SIMILARITY = 'similarity';

    /**
     * @var AuthorizationHelper
     */
    protected $authorizationHelper;

    /**
     * @var EntityManagerInterface
     */
    protected $em;

    /**
     * @var TokenStorageInterface
     */
    protected $tokenStorage;

    public function __construct(
        EntityManagerInterface $em,
        AuthorizationHelper $authorizationHelper,
        TokenStorageInterface $tokenStorage,
        protected PersonNotDuplicateRepository $personNotDuplicateRepository,
        protected PersonRenderInterface $personRender,
    ) {
        $this->em = $em;
        $this->authorizationHelper = $authorizationHelper;
        $this->tokenStorage = $tokenStorage;
    }

    public function matchPerson(
        Person $person,
        float $precision = 0.30,
        string $orderBy = self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY,
        bool $addYearComparison = false,
    ) {
        $centers = $this->authorizationHelper->getReachableCenters(
            $this->tokenStorage->getToken()->getUser(),
            PersonVoter::SEE
        );
        $query = $this->em->createQuery();

        $qb = $this->em->createQueryBuilder();

        $qb->select('p')
            ->from(Person::class, 'p')
            ->join('p.centerHistory', 'center_history')
            ->where('SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) >= :precision')
            ->andWhere($qb->expr()->in('center_history.center', ':centers'))
            ->andWhere($qb->expr()->andX(
                $qb->expr()->lte('center_history.startDate', 'CURRENT_DATE()'),
                $qb->expr()->orX(
                    $qb->expr()->isNull('center_history.endDate'),
                    $qb->expr()->gt('center_history.endDate', 'CURRENT_DATE()')
                )
            ))
        ;

        $qb
            ->setParameter('fullName', $this->personRender->renderString($person, []))
            ->setParameter('centers', $centers)
            ->setParameter('precision', $precision);

        if (null !== $person->getBirthdate()) {
            $qb->andWhere($qb->expr()->orX(
                $qb->expr()->eq('p.birthdate', ':personBirthdate'),
                $qb->expr()->isNull('p.birthdate')
            ));

            $qb->setParameter('personBirthdate', $person->getBirthdate());
        }

        if (null !== $person->getId()) {
            $qb->andWhere($qb->expr()->neq('p.id', ':personId'));
            $qb->setParameter('personId', $person->getId());

            $notDuplicatePersons = $this->personNotDuplicateRepository->findNotDuplicatePerson($person);

            if (\count($notDuplicatePersons)) {
                $qb->andWhere($qb->expr()->notIn('p.id', ':notDuplicatePersons'));
                $qb->setParameter('notDuplicatePersons', $notDuplicatePersons);
            }
        }

        switch ($orderBy) {
            case self::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL:
                $qb->orderBy('p.fullnameCanonical', 'ASC');

                break;

            case self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY:
            default:
                $qb->orderBy('SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName)))', 'DESC');
                $qb->setParameter('fullName', $this->personRender->renderString($person, []));
        }

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