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

use Chill\EventBundle\Entity\BudgetTypeEnum;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Export\Declarations;
use Chill\EventBundle\Repository\EventBudgetElementRepository;
use Chill\EventBundle\Repository\EventThemeRepository;
use Chill\EventBundle\Security\EventVoter;
use Chill\EventBundle\Templating\Entity\EventThemeRender;
use Chill\MainBundle\Export\ExportGenerationContext;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\ThirdPartyBundle\Export\Helper\LabelThirdPartyHelper;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NativeQuery;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
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\TranslatableInterface;

/**
 * Render a list of events.
 */
class ListEvents implements ListInterface, GroupedExportInterface
{
    protected array $fields = [
        'event_id',
        'event_center',
        'event_name',
        'event_date',
        'event_location',
        'event_type',
        'event_themes',
        'event_moderator',
        'event_animators',
        'event_participants_count',
        'event_budget_resources',
        'event_budget_charges',
    ];

    private readonly bool $filterStatsByCenters;

    public function __construct(
        protected readonly EntityManagerInterface $entityManager,
        ParameterBagInterface $parameterBag,
        protected readonly TranslatableStringHelperInterface $translatableStringHelper,
        protected readonly EventThemeRender $eventThemeRender,
        protected readonly EventThemeRepository $eventThemeRepository,
        protected readonly LabelThirdPartyHelper $labelThirdPartyHelper,
        protected readonly EventBudgetElementRepository $eventBudgetElementRepository,
    ) {
        $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
    }

    public function buildForm(FormBuilderInterface $builder): void
    {
        $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 getFormDefaultData(): array
    {
        return [
            'fields' => $this->fields,
        ];
    }

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

    public function getDescription(): string
    {
        return 'export.event.list.description';
    }

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

    public function getLabels($key, array $values, $data)
    {
        return match ($key) {
            'event_id' => fn ($value) => '_header' === $value ? $key : $value,
            'event_name' => fn ($value) => '_header' === $value ? $key : $value,
            'event_date' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return $key;
                }

                if ($value instanceof \DateTime) {
                    return $value->format('Y-m-d');
                }

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

                return $date ? $date->format('Y-m-d') : $value;
            },
            'event_type' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.event.list.'.$key;
                }

                return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR));
            },
            'event_center' => fn ($value) => '_header' === $value ? $key : $value,
            'event_moderator' => fn ($value) => '_header' === $value ? $key : $value,
            'event_participants_count' => fn ($value) => '_header' === $value ? $key : $value,
            'event_location' => fn ($value) => '_header' === $value ? $key : $value,
            'event_animators' => $this->labelThirdPartyHelper->getLabelMulti($key, $values, $key),
            'event_themes' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return $key;
                }

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

                return implode(
                    '|',
                    array_map(
                        fn ($t) => $this->eventThemeRender->renderString($this->eventThemeRepository->find($t), []),
                        json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)
                    )
                );
            },
            'event_budget_resources' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return $key;
                }

                if (!$value) {
                    return '';
                }

                $ids = explode(',', $value);
                $ids = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
                $elements = $this->eventBudgetElementRepository->findBy(['id' => $ids]);

                return implode('|', array_map(function ($element) {
                    $name = $this->translatableStringHelper->localize($element->getKind()->getName());
                    $amount = number_format($element->getAmount(), 2, '.', '');

                    return $name.': '.$amount;
                }, $elements));
            },

            'event_budget_charges' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return $key;
                }

                if (!$value) {
                    return '';
                }

                $ids = explode(',', $value);
                $ids = json_decode($value, true, 512, JSON_THROW_ON_ERROR);

                $elements = $this->eventBudgetElementRepository->findBy(['id' => $ids]);

                return implode('|', array_map(function ($element) {
                    $name = $this->translatableStringHelper->localize($element->getKind()->getName());
                    $amount = number_format($element->getAmount(), 2, '.', '');

                    return $name.': '.$amount;
                }, $elements));
            },


            default => fn ($value) => '_header' === $value ? $key : $value,
        };
    }

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

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

    public function getTitle(): string|TranslatableInterface
    {
        return 'export.event.list.title';
    }

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

    public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): NativeQuery|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()
            ->from(Event::class, 'event');

        if ($this->filterStatsByCenters) {
            $qb
                ->andWhere('event.center IN (:authorized_centers)')
                ->setParameter('authorized_centers', $centers);
        }

        // Add fields based on selection
        foreach ($this->fields as $field) {
            if (\in_array($field, $data['fields'], true)) {
                switch ($field) {
                    case 'event_id':
                        $qb->addSelect('event.id AS event_id');
                        break;

                    case 'event_name':
                        $qb->addSelect('event.name AS event_name');
                        break;

                    case 'event_date':
                        $qb->addSelect('event.date AS event_date');
                        break;

                    case 'event_type':
                        if (!$this->hasJoin($qb, 'event.type')) {
                            $qb->leftJoin('event.type', 'type');
                        }
                        $qb->addSelect('type.name AS event_type');
                        break;

                    case 'event_center':
                        if (!$this->hasJoin($qb, 'event.center')) {
                            $qb->leftJoin('event.center', 'center');
                        }
                        $qb->addSelect('center.name AS event_center');
                        break;

                    case 'event_moderator':
                        if (!$this->hasJoin($qb, 'event.moderator')) {
                            $qb->leftJoin('event.moderator', 'user');
                        }
                        $qb->addSelect('user.username AS event_moderator');
                        break;

                    case 'event_participants_count':
                        $qb->addSelect('(SELECT COUNT(p.id) FROM Chill\EventBundle\Entity\Participation p WHERE p.event = event.id) AS event_participants_count');
                        break;

                    case 'event_location':
                        if (!$this->hasJoin($qb, 'event.location')) {
                            $qb->leftJoin('event.location', 'location');
                        }
                        $qb->addSelect('location.name AS event_location');
                        break;

                    case 'event_animators':
                        $qb->addSelect(
                            '(SELECT AGGREGATE(tp.id) FROM Chill\ThirdPartyBundle\Entity\ThirdParty tp WHERE tp MEMBER OF event.animators) AS event_animators'
                        );
                        break;

                    case 'event_themes':
                        $qb->addSelect(
                            '(SELECT AGGREGATE(t.id) FROM Chill\EventBundle\Entity\EventTheme t WHERE t MEMBER OF event.themes) AS event_themes'
                        );
                        break;

                    case 'event_budget_resources':
                        $qb->addSelect(
                            '(SELECT AGGREGATE(ebr.id)
                          FROM Chill\EventBundle\Entity\EventBudgetElement ebr
                          JOIN ebr.kind kr
                          WHERE ebr.event = event.id AND kr.type = :resource_type) AS event_budget_resources'
                        );
                        $qb->setParameter('resource_type', BudgetTypeEnum::RESOURCE->value);
                        break;

                    case 'event_budget_charges':
                        $qb->addSelect(
                            '(SELECT AGGREGATE(ebc.id)
                          FROM Chill\EventBundle\Entity\EventBudgetElement ebc
                          JOIN ebc.kind kc
                          WHERE ebc.event = event.id AND kc.type = :charge_type) AS event_budget_charges'
                        );
                        $qb->setParameter('charge_type', BudgetTypeEnum::CHARGE->value);
                        break;
                }
            }
        }

        return $qb;
    }

    public function requiredRole(): string
    {
        return EventVoter::STATS;
    }

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

    /**
     * Helper method to check if a join already exists in the QueryBuilder.
     */
    private function hasJoin($queryBuilder, $joinPath): bool
    {
        $joins = $queryBuilder->getDQLPart('join');
        if (!isset($joins['event'])) {
            return false;
        }

        foreach ($joins['event'] as $join) {
            if ($join->getJoin() === $joinPath) {
                return true;
            }
        }

        return false;
    }

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

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

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