<?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\CalendarBundle\Service\GenericDoc\Providers;

use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CalendarDoc;
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
use Chill\PersonBundle\Entity\Person;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\MappingException;
use Service\GenericDoc\Providers\PersonCalendarGenericDocProviderTest;
use Symfony\Component\Security\Core\Security;

/**
 * Provide calendar documents for calendar associated to persons.
 *
 * @see PersonCalendarGenericDocProviderTest
 */
final readonly class PersonCalendarGenericDocProvider implements GenericDocForPersonProviderInterface
{
    public const KEY = 'person_calendar_document';

    public function __construct(
        private Security $security,
        private EntityManagerInterface $em,
    ) {}

    private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
    {
        $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);

        if (null !== $startDate) {
            $query->addWhereClause(
                sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')),
                [$startDate],
                [Types::DATE_IMMUTABLE]
            );
        }

        if (null !== $endDate) {
            $query->addWhereClause(
                sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')),
                [$endDate],
                [Types::DATE_IMMUTABLE]
            );
        }

        if (null !== $content) {
            $query->addWhereClause(
                sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')),
                ['%'.$content.'%'],
                [Types::STRING]
            );
        }

        return $query;
    }

    /**
     * @throws MappingException
     */
    public function buildFetchQueryForPerson(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
    {
        $classMetadata = $this->em->getClassMetadata(CalendarDoc::class);
        $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
        $calendarMetadata = $this->em->getClassMetadata(Calendar::class);

        $query = new FetchQuery(
            self::KEY,
            sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')),
            'cd.'.$storedObjectMetadata->getColumnName('createdAt'),
            $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd'
        );
        $query->addJoinClause(
            sprintf(
                'JOIN %s doc_store ON doc_store.%s = cd.%s',
                $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
                $storedObjectMetadata->getColumnName('id'),
                $classMetadata->getSingleAssociationJoinColumnName('storedObject')
            )
        );

        $query->addJoinClause(
            sprintf(
                'JOIN %s calendar ON calendar.%s = cd.%s',
                $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(),
                $calendarMetadata->getColumnName('id'),
                $classMetadata->getSingleAssociationJoinColumnName('calendar')
            )
        );

        $query->addWhereClause(
            sprintf('calendar.%s = ?', $calendarMetadata->getSingleAssociationJoinColumnName('person')),
            [$person->getId()],
            [Types::INTEGER]
        );

        return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content);
    }

    public function isAllowedForPerson(Person $person): bool
    {
        return $this->security->isGranted(CalendarVoter::SEE, $person);
    }
}
