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

use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;

final readonly class AccompanyingPeriodActivityGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface
{
    public const KEY = 'accompanying_period_activity_document';

    public function __construct(
        private EntityManagerInterface $em,
        private Security $security,
        private ActivityDocumentACLAwareRepositoryInterface $activityDocumentACLAwareRepository,
        private ActivityRepository $activityRepository,
    ) {}

    public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
    {
        if (null === $activity = $this->getRelatedEntity($genericDocDTO->key, $genericDocDTO->identifiers)) {
            return null;
        }

        return $activity->getDocuments()->findFirst(fn (int $key, StoredObject $storedObject) => $storedObject->getId() === $genericDocDTO->identifiers['id']);
    }

    public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
    {
        return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
    }

    public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
    {
        return self::KEY === $key && array_key_exists('activity_id', $identifiers);
    }

    private function getRelatedEntity(string $key, array $identifiers): ?Activity
    {
        return $this->activityRepository->find($identifiers['activity_id']);
    }

    public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
    {
        if (null === $activity = $this->getRelatedEntity($key, $identifiers)) {
            return null;
        }

        return new GenericDocDTO(
            self::KEY,
            $identifiers,
            \DateTimeImmutable::createFromInterface($activity->getDate()),
            $activity->getAccompanyingPeriod(),
        );
    }

    public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
    {
        $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
        $activityMetadata = $this->em->getClassMetadata(Activity::class);

        $query = new FetchQuery(
            self::KEY,
            sprintf("jsonb_build_object('id', doc_obj.%s, 'activity_id', activity.%s)", $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()),
            'doc_obj.'.$storedObjectMetadata->getColumnName('createdAt'),
            $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName().' AS doc_obj'
        );

        $query->addJoinClause(
            'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = doc_obj.id'
        );

        $query->addJoinClause(
            'JOIN public.activity activity ON activity.id = activity_doc.activity_id'
        );

        $query->addWhereClause(
            'activity.accompanyingperiod_id = ?',
            [$accompanyingPeriod->getId()],
            [Types::INTEGER]
        );

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

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

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

        return $query;
    }

    public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
    {
        return $this->security->isGranted(ActivityVoter::SEE, $accompanyingPeriod);
    }

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

    public function buildFetchQueryForPerson(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
    {
        return $this->activityDocumentACLAwareRepository
            ->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content);
    }
}
