<?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\Export\Filter\PersonFilters;

use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\ExportGenerationContext;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

final readonly class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInterface, FilterInterface
{
    public function __construct(
        private TranslatableStringHelper $translatableStringHelper,
        private ActivityReasonRepository $activityReasonRepository,
        private RollingDateConverterInterface $rollingDateConverter,
    ) {}

    public function addRole(): ?string
    {
        return null;
    }

    public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void
    {
        error_log('alterQuery called with data: '.json_encode(array_keys($data)));

        // create a subquery for activity
        $sqb = $qb->getEntityManager()->createQueryBuilder();
        $sqb->select('1')
            ->from(Activity::class, 'activity_person_having_activity')
            ->leftJoin('activity_person_having_activity.person', 'person_person_having_activity');

        // add clause between date
        $sqb->where('activity_person_having_activity.date BETWEEN '
            .':person_having_activity_between_date_from'
            .' AND '
            .':person_having_activity_between_date_to'
            .' AND '
            .'(person_person_having_activity.id = person.id OR person MEMBER OF activity_person_having_activity.persons)');

        if (\in_array('activity', $qb->getAllAliases(), true)) {
            $sqb->andWhere('activity_person_having_activity.id = activity.id');
        }
        if (isset($data['reasons']) && [] !== $data['reasons']) {
            // add clause activity reason
            $sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');

            $sqb->andWhere(
                $sqb->expr()->in(
                    'reasons_person_having_activity',
                    ':person_having_activity_reasons'
                )
            );

            $qb->setParameter('person_having_activity_reasons', $data['reasons']);
        }

        $qb->andWhere(
            $qb->expr()->exists($sqb->getDQL())
        );

        $qb->setParameter(
            'person_having_activity_between_date_from',
            $this->rollingDateConverter->convert($data['date_from_rolling'])
        );
        $qb->setParameter(
            'person_having_activity_between_date_to',
            $this->rollingDateConverter->convert($data['date_to_rolling'])
        );
    }

    public function applyOn(): string
    {
        return Declarations::PERSON_TYPE;
    }

    public function buildForm(FormBuilderInterface $builder): void
    {
        $builder->add('date_from_rolling', PickRollingDateType::class, [
            'label' => 'export.filter.activity.person_between_dates.Implied in an activity after this date',
        ]);

        $builder->add('date_to_rolling', PickRollingDateType::class, [
            'label' => 'export.filter.activity.person_between_dates.Implied in an activity before this date',
        ]);

        if ([] !== $reasons = $this->activityReasonRepository->findAll()) {
            $builder->add('reasons', EntityType::class, [
                'class' => ActivityReason::class,
                'choices' => $reasons,
                'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()),
                'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()),
                'multiple' => true,
                'expanded' => false,
                'label' => 'export.filter.activity.person_between_dates.Activity reasons for those activities',
                'help' => 'export.filter.activity.person_between_dates.if no reasons',
            ]);
        }
    }

    public function getNormalizationVersion(): int
    {
        return 1;
    }

    public function normalizeFormData(array $formData): array
    {
        $normalized = [
            'date_from_rolling' => $formData['date_from_rolling']->normalize(),
            'date_to_rolling' => $formData['date_to_rolling']->normalize(),
            'reasons' => [],
        ];

        if (isset($formData['reasons']) && [] !== $formData['reasons']) {
            $normalized['reasons'] = array_map(
                fn (ActivityReason $reason) => $reason->getId(),
                $formData['reasons']
            );
        }

        return $normalized;
    }

    public function denormalizeFormData(array $formData, int $fromVersion): array
    {
        $denormalized = [
            'date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']),
            'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling']),
            'reasons' => [],
        ];

        if (isset($formData['reasons']) && [] !== $formData['reasons']) {
            $denormalized['reasons'] = array_map(
                fn ($id) => $this->activityReasonRepository->find($id),
                $formData['reasons']
            );
        }

        return $denormalized;
    }

    public function getFormDefaultData(): array
    {
        return [
            'date_from_rolling' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
            'date_to_rolling' => new RollingDate(RollingDate::T_TODAY),
            'reasons' => [],
        ];
    }

    public function describeAction($data, ExportGenerationContext $context): array
    {
        $reasons = $data['reasons'] ?? [];

        return [
            [] === $reasons ?
                'export.filter.activity.describe_action_with_no_subject'
                : 'export.filter.activity.describe_action_with_subject',
            [
                'date_from' => $this->rollingDateConverter->convert($data['date_from_rolling']),
                'date_to' => $this->rollingDateConverter->convert($data['date_to_rolling']),
                'reasons' => implode(
                    ', ',
                    array_map(
                        fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"',
                        $reasons
                    )
                ),
            ],
        ];
    }

    public function getTitle(): string
    {
        return 'export.filter.activity.person_between_dates.title';
    }

    public function validateForm($data, ExecutionContextInterface $context): void
    {
        error_log('validateForm called with data: '.json_encode(array_keys($data)));
        if ($this->rollingDateConverter->convert($data['date_from_rolling'])
            >= $this->rollingDateConverter->convert($data['date_to_rolling'])) {
            $context->buildViolation('export.filter.activity.person_between_dates.date mismatch')
                ->setTranslationDomain('messages')
                ->addViolation();
        }
    }
}
