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

use Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice;
use Chill\CustomFieldsBundle\Entity\CustomField;
use Chill\CustomFieldsBundle\Service\CustomFieldProvider;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Export\Helper\ListPersonHelper;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;

/**
 * Render a list of people.
 */
class ListPerson implements ListInterface, GroupedExportInterface
{
    use \Chill\MainBundle\Export\ExportDataNormalizerTrait;
    private array $slugs = [];
    private readonly bool $filterStatsByCenters;

    public function __construct(
        private readonly CustomFieldProvider $customFieldProvider,
        private readonly ListPersonHelper $listPersonHelper,
        protected readonly EntityManagerInterface $entityManager,
        private readonly TranslatableStringHelper $translatableStringHelper,
        ParameterBagInterface $parameterBag,
    ) {
        $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
    }

    public function buildForm(FormBuilderInterface $builder)
    {
        // add a date  field for addresses
        $builder->add('address_date', ChillDateType::class, [
            'label' => 'Data valid at this date',
            'help' => 'Data regarding center, addresses, and so on will be computed at this date',
            'input' => 'datetime_immutable',
        ]);
    }

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

    public function normalizeFormData(array $formData): array
    {
        return ['address_date' => $this->normalizeDate($formData['address_date'])];
    }

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

    public function getFormDefaultData(): array
    {
        return ['address_date' => new \DateTimeImmutable()];
    }

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

    public function getDescription()
    {
        return 'Create a list of people according to various filters.';
    }

    public function getGroup(): string
    {
        return 'Exports of persons';
    }

    public function getLabels($key, array $values, $data)
    {
        if (\in_array($key, $this->listPersonHelper->getAllKeys(), true)) {
            return $this->listPersonHelper->getLabels($key, $values, $data);
        }

        return $this->getLabelForCustomField($key, $values, $data);
    }

    public function getQueryKeys($data): array
    {
        $fields = [];

        foreach ($this->listPersonHelper->getAllKeys() as $key) {
            $fields[] = $key;
        }

        // add the key from slugs and return
        return [...$fields, ...\array_keys($this->slugs)];
    }

    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 peoples';
    }

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

    /**
     * param array{fields: string[], address_date: DateTimeImmutable} $data.
     */
    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);

        $qb = $this->entityManager->createQueryBuilder()
            ->from(Person::class, 'person');

        if ($this->filterStatsByCenters) {
            $qb
                ->andWhere(
                    $qb->expr()->exists(
                        'SELECT 1 FROM '.Person\PersonCenterHistory::class.' pch WHERE pch.person = person.id AND pch.center IN (:authorized_centers)'
                    )
                )
                ->setParameter('authorized_centers', $centers);
        }

        $this->listPersonHelper->addSelect($qb, $data['address_date']);

        foreach ($this->getCustomFields() as $cf) {
            $cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType());

            if ($cfType instanceof CustomFieldChoice && $cfType->isMultiple($cf)) {
                foreach ($cfType->getChoices($cf) as $choiceSlug => $label) {
                    $slug = $this->slugToDQL($cf->getSlug(), 'choice', ['choiceSlug' => $choiceSlug]);
                    $qb->addSelect(
                        sprintf(
                            'GET_JSON_FIELD_BY_KEY(person.cFData, :slug%s) AS %s',
                            $slug,
                            $slug
                        )
                    );
                    $qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug());
                }
            } else {
                $slug = $this->slugToDQL($cf->getSlug());
                $qb->addSelect(
                    sprintf(
                        'GET_JSON_FIELD_BY_KEY(person.cFData, :slug%s) AS %s',
                        $slug,
                        $slug
                    )
                );
                $qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug());
            }
        }

        return $qb;
    }

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

    public function supportsModifiers()
    {
        return [Declarations::PERSON_TYPE];
    }

    private function DQLToSlug($cleanedSlug)
    {
        return $this->slugs[$cleanedSlug]['slug'];
    }

    /**
     * @return array An array with keys = 'slug', 'type', 'additionnalInfo'
     */
    private function extractInfosFromSlug(mixed $slug): array
    {
        return $this->slugs[$slug];
    }

    /**
     * Get custom fields associated with person.
     *
     * @return CustomField[]
     */
    private function getCustomFields()
    {
        return $this->entityManager
            ->createQuery('SELECT cf '
                .'FROM '.CustomField::class.' cf '
                .'JOIN cf.customFieldGroup g '
                .'WHERE cf.type != :title AND g.entity LIKE :entity AND cf.active = TRUE')
            ->setParameters([
                'title' => 'title',
                'entity' => \addcslashes(Person::class, '\\'),
            ])
            ->getResult();
    }

    private function getLabelForCustomField($key, array $values, $data)
    {
        // for fields which are custom fields
        /** @var CustomField $cf */
        $cf = $this->entityManager
            ->getRepository(CustomField::class)
            ->findOneBy(['slug' => $this->DQLToSlug($key)]);
        $cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType());
        $defaultFunction = function ($value) use ($cf) {
            if ('_header' === $value) {
                return $this->translatableStringHelper->localize($cf->getName());
            }

            if (null === $value) {
                return '';
            }

            return $this->customFieldProvider
                ->getCustomFieldByType($cf->getType())
                ->render(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), $cf, 'csv');
        };

        if ($cfType instanceof CustomFieldChoice && $cfType->isMultiple($cf)) {
            return function ($value) use ($cf, $cfType, $key) {
                $slugChoice = $this->extractInfosFromSlug($key)['additionnalInfos']['choiceSlug'];

                if (null === $value) {
                    return '';
                }

                if ('_header' === $value) {
                    $label = $cfType->getChoices($cf)[$slugChoice];

                    return $this->translatableStringHelper->localize($cf->getName())
                        .' | '.$label;
                }

                try {
                    $decoded = json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR);
                } catch (\JsonException $e) {
                    throw new \RuntimeException(sprintf('unable to decode json: %s, %s', json_last_error(), json_last_error_msg()), $e->getCode(), $e);
                }

                if ('_other' === $slugChoice && $cfType->isChecked($cf, $slugChoice, $decoded)) {
                    return $cfType->extractOtherValue($cf, $decoded);
                }

                return $cfType->isChecked($cf, $slugChoice, $decoded);
            };
        }

        return $defaultFunction;
    }

    /**
     * Clean a slug to be usable by DQL.
     */
    private function slugToDQL(string $slug, string $type = 'default', array $additionalInfos = []): string
    {
        $uid = 'slug_'.\uniqid('', false);

        $this->slugs[$uid] = [
            'slug' => $slug,
            'type' => $type,
            'additionnalInfos' => $additionalInfos,
        ];

        return $uid;
    }
}
