<?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\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\Persistence\ObjectRepository;

final readonly class HouseholdRepository implements ObjectRepository
{
    private const SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION = <<<'SQL'
        WITH participations AS (
        	SELECT DISTINCT part.accompanyingperiod_id
        	FROM chill_person_accompanying_period_participation AS part
        	WHERE person_id = ?),
        other_participants AS (
        	SELECT person_id, startDate, endDate
        	FROM chill_person_accompanying_period_participation
        	JOIN participations USING (accompanyingperiod_id)
        	WHERE person_id != ?
        ),
        households AS (SELECT DISTINCT household.*
        	FROM chill_person_household_members AS hmembers
        	JOIN other_participants AS op USING (person_id)
        	JOIN chill_person_household AS household ON hmembers.household_id = household.id
          WHERE daterange(op.startDate, op.endDate) && daterange(hmembers.startDate, hmembers.endDate)
        )
        SELECT {select} FROM households {limits}
        SQL;

    private EntityManagerInterface $em;

    private EntityRepository $repository;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->repository = $entityManager->getRepository(Household::class);
        $this->em = $entityManager;
    }

    public function countByAccompanyingPeriodParticipation(Person $person)
    {
        return $this->buildQueryByAccompanyingPeriodParticipation($person, true);
    }

    public function find($id)
    {
        return $this->repository->find($id);
    }

    public function findAll()
    {
        return $this->repository->findAll();
    }

    public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
    {
        return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
    }

    public function findByAccompanyingPeriodParticipation(Person $person, int $limit, int $offset)
    {
        return $this->buildQueryByAccompanyingPeriodParticipation($person, false, $limit, $offset);
    }

    public function findOneBy(array $criteria)
    {
        return $this->findOneBy($criteria);
    }

    public function getClassName()
    {
        return Household::class;
    }

    private function buildQueryByAccompanyingPeriodParticipation(Person $person, bool $isCount = false, int $limit = 50, int $offset = 0)
    {
        $rsm = new ResultSetMappingBuilder($this->em);
        $rsm->addRootEntityFromClassMetadata(Household::class, 'h');

        if ($isCount) {
            $rsm->addScalarResult('count', 'count');
            $sql = \strtr(self::SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION, [
                '{select}' => 'COUNT(households.*) AS count',
                '{limits}' => '',
            ]);
        } else {
            $sql = \strtr(self::SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION, [
                '{select}' => $rsm->generateSelectClause(['h' => 'households']),
                '{limits}' => "OFFSET {$offset} LIMIT {$limit}",
            ]);
        }
        $native = $this->em->createNativeQuery($sql, $rsm);
        $native->setParameters([0 => $person->getId(), 1 => $person->getId()]);

        if ($isCount) {
            return $native->getSingleScalarResult();
        }

        return $native->getResult();
    }
}
