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

use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

class ListActivity implements ListInterface, GroupedExportInterface
{
    protected array $fields = [
        'id',
        'date',
        'durationTime',
        'attendee',
        'list_reasons',
        'user_username',
        'circle_name',
        'type_name',
        'person_firstname',
        'person_lastname',
        'person_id',
        'household_id',
    ];
    private readonly bool $filterStatsByCenters;

    public function __construct(
        protected EntityManagerInterface $entityManager,
        protected TranslatorInterface $translator,
        protected TranslatableStringHelperInterface $translatableStringHelper,
        private readonly ActivityRepository $activityRepository,
        ParameterBagInterface $parameterBag,
    ) {
        $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
    }

    public function buildForm(FormBuilderInterface $builder)
    {
        $builder->add('fields', ChoiceType::class, [
            'multiple' => true,
            'expanded' => true,
            'choices' => array_combine($this->fields, $this->fields),
            'label' => 'Fields to include in export',
            'constraints' => [new Callback([
                'callback' => static function ($selected, ExecutionContextInterface $context) {
                    if (0 === \count($selected)) {
                        $context->buildViolation('You must select at least one element')
                            ->atPath('fields')
                            ->addViolation();
                    }
                },
            ])],
        ]);
    }

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

    public function normalizeFormData(array $formData): array
    {
        return ['fields' => $formData['fields']];
    }

    public function denormalizeFormData(array $formData, int $fromVersion): array
    {
        return ['fields' => $formData['fields']];
    }

    public function getFormDefaultData(): array
    {
        return [];
    }

    public function getAllowedFormattersTypes()
    {
        return [FormatterInterface::TYPE_LIST];
    }

    public function getDescription()
    {
        return 'List activities linked to a person description';
    }

    public function getGroup(): string
    {
        return 'Exports of activities linked to a person';
    }

    public function getLabels($key, array $values, $data)
    {
        switch ($key) {
            case 'date':
                return static function ($value) {
                    if ('_header' === $value) {
                        return 'date';
                    }

                    $date = \DateTime::createFromFormat('Y-m-d H:i:s', $value);

                    return $date->format('d-m-Y');
                };

            case 'attendee':
                return static function ($value) {
                    if ('_header' === $value) {
                        return 'attendee';
                    }

                    return $value ? 'X' : '';
                };

            case 'list_reasons':
                $activityRepository = $this->activityRepository;

                return function ($value) use ($activityRepository): string {
                    if ('_header' === $value) {
                        return 'activity reasons';
                    }

                    $activity = $activityRepository->find($value);

                    return implode(', ', array_map(fn (ActivityReason $r) => '"'.
                            $this->translatableStringHelper->localize($r->getCategory()->getName())
                            .' > '.
                            $this->translatableStringHelper->localize($r->getName())
                            .'"', $activity->getReasons()->toArray()));
                };

            case 'circle_name':
                return function ($value) {
                    if ('_header' === $value) {
                        return 'circle';
                    }

                    return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR));
                };

            case 'type_name':
                return function ($value) {
                    if ('_header' === $value) {
                        return 'activity type';
                    }

                    return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR));
                };

            default:
                return static function ($value) use ($key) {
                    if ('_header' === $value) {
                        return $key;
                    }

                    return $value;
                };
        }
    }

    public function getQueryKeys($data): array
    {
        return $data['fields'];
    }

    public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array
    {
        return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
    }

    public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface
    {
        return 'List activity linked to a person';
    }

    public function getType(): string
    {
        return Declarations::ACTIVITY;
    }

    public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder
    {
        $centers = array_map(static fn ($el) => $el['center'], $acl);

        // throw an error if no fields are present
        if (!\array_key_exists('fields', $data)) {
            throw new InvalidArgumentException('No fields have been checked.');
        }

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

        $qb
            ->from('ChillActivityBundle:Activity', 'activity')
            ->join('activity.person', 'person')
            ->join(
                HouseholdMember::class,
                'householdmember',
                Query\Expr\Join::WITH,
                'person = householdmember.person AND householdmember.startDate <= activity.date AND (householdmember.endDate IS NULL OR householdmember.endDate > activity.date)'
            )
            ->join('householdmember.household', 'household');

        if ($this->filterStatsByCenters) {
            $qb->join('person.centerHistory', 'centerHistory');
            $qb->where(
                $qb->expr()->andX(
                    $qb->expr()->lte('centerHistory.startDate', 'activity.date'),
                    $qb->expr()->orX(
                        $qb->expr()->isNull('centerHistory.endDate'),
                        $qb->expr()->gt('centerHistory.endDate', 'activity.date')
                    )
                )
            )
                ->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
                ->setParameter('centers', $centers);
        }

        foreach ($this->fields as $f) {
            if (\in_array($f, $data['fields'], true)) {
                switch ($f) {
                    case 'id':
                        $qb->addSelect('activity.id AS id');

                        break;

                    case 'person_firstname':
                        $qb->addSelect('person.firstName AS person_firstname');

                        break;

                    case 'person_lastname':
                        $qb->addSelect('person.lastName AS person_lastname');

                        break;

                    case 'person_id':
                        $qb->addSelect('person.id AS person_id');

                        break;

                    case 'household_id':
                        $qb->addSelect('household.id AS household_id');

                        break;

                    case 'user_username':
                        $qb->join('activity.user', 'actuser');
                        $qb->addSelect('actuser.username AS user_username');

                        break;

                    case 'circle_name':
                        $qb->join('activity.scope', 'circle');
                        $qb->addSelect('circle.name AS circle_name');

                        break;

                    case 'type_name':
                        $qb->join('activity.activityType', 'type');
                        $qb->addSelect('type.name AS type_name');

                        break;

                    case 'list_reasons':
                        // this is a trick... The reasons is filled with the
                        // activity id which will be used to load reasons
                        $qb->addSelect('activity.id AS list_reasons');

                        break;

                    case 'attendee':
                        $qb->addSelect('IDENTITY(activity.attendee) AS attendee');

                        break;

                    default:
                        $qb->addSelect(sprintf('activity.%s as %s', $f, $f));

                        break;
                }
            }
        }

        return $qb;
    }

    public function requiredRole(): string
    {
        return ActivityStatsVoter::LISTS;
    }

    public function supportsModifiers(): array
    {
        return [
            Declarations::ACTIVITY,
            Declarations::ACTIVITY_PERSON,
            PersonDeclarations::PERSON_TYPE,
        ];
    }
}
