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

use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Form\EventType;
use Chill\EventBundle\Form\Type\PickEventType;
use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Chill\PersonBundle\Privacy\PrivacyEvent;
use Doctrine\Persistence\ManagerRegistry;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Csv;
use PhpOffice\PhpSpreadsheet\Writer\Ods;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * Class EventController.
 */
final class EventController extends AbstractController
{
    /**
     * EventController constructor.
     */
    public function __construct(
        private readonly EventDispatcherInterface $eventDispatcher,
        private readonly AuthorizationHelperInterface $authorizationHelper,
        private readonly FormFactoryInterface $formFactoryInterface,
        private readonly TranslatorInterface $translator,
        private readonly PaginatorFactory $paginator,
        private readonly Security $security,
        private readonly ManagerRegistry $managerRegistry,
        private readonly NormalizerInterface $normalizer,
    ) {}

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/delete', name: 'chill_event__event_delete', requirements: ['event_id' => '\d+'], methods: ['GET', 'POST', 'DELETE'])]
    public function deleteAction($event_id, Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|Response
    {
        $em = $this->managerRegistry->getManager();
        $event = $em->getRepository(Event::class)->findOneBy([
            'id' => $event_id,
        ]);

        if (!$event) {
            throw $this->createNotFoundException('Unable to find this event.');
        }

        /** @var array $participations */
        $participations = $event->getParticipations();
        $budgetElements = $event->getBudgetElements();

        $form = $this->createDeleteForm($event_id);

        if (Request::METHOD_POST === $request->getMethod()) {
            $form->handleRequest($request);

            if ($form->isSubmitted() && $form->isValid()) {
                foreach ($participations as $participation) {
                    $em->remove($participation);
                }

                foreach ($budgetElements as $e) {
                    $em->remove($e);
                }

                $em->remove($event);
                $em->flush();

                $this->addFlash(
                    'success',
                    $this->translator
                        ->trans('The event has been sucessfully removed')
                );

                return $this->redirectToRoute('chill_main_search', [
                    'name' => 'event_regular',
                    'q' => '@event',
                ]);
            }
        }

        return $this->render('@ChillEvent/Event/confirm_delete.html.twig', [
            'id' => $event->getId(),
            'delete_form' => $form->createView(),
        ]);
    }

    /**
     * List events subscriptions for a person.
     *
     * @throws \Doctrine\ORM\NonUniqueResultException
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{person_id}/list', name: 'chill_event__list_by_person', methods: ['GET'])]
    public function listByPersonAction($person_id): Response
    {
        $em = $this->managerRegistry->getManager();

        $person = $em->getRepository(Person::class)->find($person_id);

        if (null === $person) {
            throw $this->createNotFoundException('Person not found');
        }

        $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);

        $reachablesCircles = $this->authorizationHelper->getReachableScopes(
            $this->getUser(),
            EventVoter::SEE,
            $person->getCenter()
        );

        $total = $em->getRepository(Participation::class)->countByPerson($person_id);

        $paginator = $this->paginator->create($total);

        $participations = $em->getRepository(Participation::class)->findByPersonInCircle(
            $person_id,
            $reachablesCircles,
            $paginator->getCurrentPage()->getFirstItemNumber(),
            $paginator->getItemsPerPage()
        );

        $privacyEvent = new PrivacyEvent($person, [
            'element_class' => Participation::class,
            'action' => 'list',
        ]);
        $this->eventDispatcher->dispatch($privacyEvent, PrivacyEvent::PERSON_PRIVACY_EVENT);

        $addEventParticipationByPersonForm = $this->createAddEventParticipationByPersonForm($person);

        return $this->render('@ChillEvent/Event/listByPerson.html.twig', [
            'participations' => $participations,
            'person' => $person,
            'paginator' => $paginator,
            'form_add_event_participation_by_person' => $addEventParticipationByPersonForm->createView(),
        ]);
    }

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/most_recent', name: 'chill_event_list_most_recent', options: [null])]
    public function mostRecentIndexAction()
    {
        return $this->redirectToRoute('chill_main_search', [
            'q' => '@event',
        ]);
    }

    /**
     * Displays a form to create a new Event entity.
     *
     * @throws ExceptionInterface
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/new', name: 'chill_event__event_new', methods: ['GET', 'POST'])]
    public function newAction(?Center $center, Request $request): Response
    {
        $user = $this->security->getUser();

        if (!$user instanceof User) {
            throw new AccessDeniedHttpException('not a regular user. Maybe an administrator ?');
        }

        if (null === $center) {
            $center_id = $request->query->get('center_id');
            $center = $this->managerRegistry->getRepository(Center::class)->find($center_id);
        }

        $entity = new Event();
        $entity->setCenter($center);
        $entity->setLocation($user->getCurrentLocation());

        $form = $this->createCreateForm($entity);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->managerRegistry->getManager();
            $em->persist($entity);
            $em->flush();

            $this->addFlash('success', $this->translator
                ->trans('The event was created'));

            return $this->redirectToRoute('chill_event__event_show', ['id' => $entity->getId()]);
        }

        $entity_array = $this->normalizer->normalize($entity, 'json', ['groups' => 'read']);

        return $this->render('@ChillEvent/Event/new.html.twig', [
            'entity' => $entity,
            'form' => $form->createView(),
            'entity_json' => $entity_array,
        ]);
    }

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/new/pick-center', name: 'chill_event__event_new_pickcenter', options: [null])]
    public function newPickCenterAction(): Response
    {
        $role = 'CHILL_EVENT_CREATE';

        $centers = $this->authorizationHelper->getReachableCenters($this->getUser(), $role);

        if (1 === \count($centers)) {
            return $this->redirectToRoute('chill_event__event_new', [
                'center_id' => $centers[0]->getId(),
            ]);
        }

        $form = $this->formFactoryInterface
            ->createNamedBuilder('', FormType::class, null, [
                'csrf_protection' => false,
            ])
            ->setMethod('GET')
            ->setAction(
                $this->generateUrl('chill_event__event_new')
            )
            ->add('center_id', EntityType::class, [
                'class' => Center::class,
                'choices' => $centers,
                'placeholder' => $this->translator->trans('Pick a center'),
                'label' => 'To which territory should the event be associated ?',
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Next step',
            ])
            ->getForm();

        return $this->render('@ChillEvent/Event/newPickCenter.html.twig', [
            'form' => $form->createView(),
        ]);
    }

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{id}/show', name: 'chill_event__event_show')]
    public function showAction(Event $event, Request $request)
    {
        if (!$event) {
            throw $this->createNotFoundException('Unable to find Event entity.');
        }

        $this->denyAccessUnlessGranted(
            EventVoter::SEE_DETAILS,
            $event,
            'You are not allowed to see details on this event'
        );

        $addParticipationByPersonForm = $this->createAddParticipationByPersonForm($event);

        $exportParticipationsList = $this->exportParticipationsList($event, $request);

        if ($exportParticipationsList['response']) {
            return $exportParticipationsList['response'];
        }

        return $this->render('@ChillEvent/Event/show.html.twig', [
            'event' => $event,
            'form_add_participation_by_person' => $addParticipationByPersonForm->createView(),
            'form_export' => $exportParticipationsList['form']->createView(),
        ]);
    }

    /**
     * Edits an existing Event entity.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/edit', name: 'chill_event__event_edit', methods: ['GET', 'POST', 'PUT'])]
    public function updateAction(Request $request, $event_id): \Symfony\Component\HttpFoundation\RedirectResponse|Response
    {
        $em = $this->managerRegistry->getManager();

        $entity = $em->getRepository(Event::class)->find($event_id);

        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Event entity.');
        }

        $editForm = $this->createForm(EventType::class, $entity, [
            'center' => $entity->getCenter(),
            'role' => EventVoter::UPDATE,
        ]);

        $editForm->add('submit', SubmitType::class, ['label' => 'Update']);

        $editForm->handleRequest($request);

        if ($editForm->isSubmitted() && $editForm->isValid()) {
            $em->persist($entity);
            $em->flush();

            $this->addFlash('success', $this->translator->trans('The event was updated'));

            return $this->redirectToRoute('chill_event__event_show', ['id' => $event_id]);
        }

        return $this->render('@ChillEvent/Event/edit.html.twig', [
            'entity' => $entity,
            'edit_form' => $editForm->createView(),
        ]);
    }

    /**
     * create a form to add a participation with an event.
     *
     * @return \Symfony\Component\Form\FormInterface
     */
    protected function createAddEventParticipationByPersonForm(Person $person)
    {
        /** @var \Symfony\Component\Form\FormBuilderInterface $builder */
        $builder = $this
            ->formFactoryInterface
            ->createNamedBuilder(
                '',
                FormType::class,
                null,
                [
                    'method' => 'GET',
                    'action' => $this->generateUrl('chill_event_participation_new'),
                    'csrf_protection' => false,
                ]
            );

        $builder->add('event_id', PickEventType::class, [
            'role' => 'CHILL_EVENT_CREATE',
            'centers' => $person->getCenter(),
        ]);

        $builder->add('person_id', HiddenType::class, [
            'data' => $person->getId(),
        ]);

        $builder->add('return_path', HiddenType::class, [
            'data' => $this->generateUrl('chill_event__list_by_person', [
                'person_id' => $person->getId(),
            ]),
        ]);

        $builder->add(
            'submit',
            SubmitType::class,
            [
                'label' => 'Subscribe an event',
            ]
        );

        return $builder->getForm();
    }

    /**
     * create a form to add a participation with a person.
     *
     * @return \Symfony\Component\Form\FormInterface
     */
    protected function createAddParticipationByPersonForm(Event $event)
    {
        $builder = $this->formFactoryInterface
            ->createNamedBuilder(
                '',
                FormType::class,
                null,
                [
                    'method' => 'GET',
                    'action' => $this->generateUrl('chill_event_participation_new'),
                    'csrf_protection' => false,
                ]
            );

        $builder->add('person_id', PickPersonDynamicType::class, [
            'as_id' => true,
            'multiple' => false,
            'submit_on_adding_new_entity' => true,
            'label' => 'Add a participation',
        ]);

        $builder->add('event_id', HiddenType::class, [
            'data' => $event->getId(),
        ]);

        return $builder->getForm();
    }

    /**
     * @return \Symfony\Component\Form\FormInterface
     */
    protected function createExportByFormatForm()
    {
        $builder = $this->createFormBuilder(['format' => 'xlsx'])
            ->add('format', ChoiceType::class, [
                'choices' => [
                    'xlsx' => 'xlsx',
                    'ods' => 'ods',
                    'csv' => 'csv',
                ],
                'label' => false,
                'placeholder' => 'Select a format',
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Export',
            ]);

        return $builder->getForm();
    }

    /**
     * @return Spreadsheet
     *
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    protected function createExportSpreadsheet(Event $event)
    {
        $spreadsheet = new Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();

        $trans = $this->translator->getLocale();

        $headerValues = [
            'A1' => 'Event',
            'B1' => $event->getId(),
            'A2' => 'Date',
            'B2' => $event->getDate()->format('d-m-Y H:i'),
            'A3' => 'Name',
            'B3' => $event->getName(),
            'A4' => 'Type',
            'B4' => $event->getType()->getName()[$trans],
            'A5' => 'Circle',
            'B5' => $event->getCircle()->getName()[$trans],
            'A6' => 'Moderator',
            'B6' => null !== $event->getModerator() ? $event->getModerator()->getUsernameCanonical() : null,
        ];

        foreach ($headerValues as $k => $value) {
            $sheet->setCellValue($k, $value);
        }

        $columnNames = ['id', 'firstname', 'lastname', 'role', 'status', 'email', 'phone', 'mobile'];
        $columnLetter = 'A';

        foreach ($columnNames as $columnName) {
            $sheet->setCellValue($columnLetter.'8', $columnName);
            ++$columnLetter;
        }

        $columnValues = [];

        foreach ($event->getParticipations() as $participation) {
            /* @var Participation $participation */
            $columnValues[] = [
                $participation->getPerson()->getId(),
                $participation->getPerson()->getFirstname(),
                $participation->getPerson()->getLastname(),
                $participation->getRole()->getName()[$trans],
                $participation->getStatus()->getName()[$trans],
                $participation->getPerson()->getEmail(),
                $participation->getPerson()->getPhoneNumber(),
                $participation->getPerson()->getMobileNumber(),
            ];
        }

        $i = 9;

        foreach ($columnValues as $columnValue) {
            $columnLetter = 'A';

            foreach ($columnValue as $value) {
                $sheet->setCellValue($columnLetter.$i, $value);
                ++$columnLetter;
            }
            ++$i;
        }

        return $spreadsheet;
    }

    /**
     * @return array
     *
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    protected function exportParticipationsList(Event $event, Request $request)
    {
        $form = $this->createExportByFormatForm();
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();
            $format = $data['format'];
            $filename = 'export_event'.$event->getId().'_participations.'.$format;

            $spreadsheet = $this->createExportSpreadsheet($event);

            switch ($format) {
                case 'ods':
                    $contentType = 'application/vnd.oasis.opendocument.spreadsheet';
                    $writer = new Ods($spreadsheet);

                    break;

                case 'xlsx':
                    $contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
                    $writer = new Xlsx($spreadsheet);

                    break;

                case 'csv':
                    $contentType = 'text/csv';
                    $writer = new Csv($spreadsheet);

                    break;

                default:
                    return ['form' => $form, 'response' => null];
            }

            $response = new StreamedResponse();
            $response->headers->set('Content-Type', $contentType);
            $response->headers->set('Content-Disposition', 'attachment;filename="'.$filename.'"');
            $response->setPrivate();
            $response->headers->addCacheControlDirective('no-cache', true);
            $response->headers->addCacheControlDirective('must-revalidate', true);
            $response->setCallback(static function () use ($writer) {
                $writer->save('php://output');
            });

            return ['form' => $form, 'response' => $response];
        }

        return ['form' => $form, 'response' => null];
    }

    /**
     * Creates a form to create a Event entity.
     *
     * @param Event $entity The entity
     *
     * @return \Symfony\Component\Form\FormInterface
     */
    private function createCreateForm(Event $entity)
    {
        $form = $this->createForm(EventType::class, $entity, [
            'method' => 'POST',
            'center' => $entity->getCenter(),
            'role' => 'CHILL_EVENT_CREATE',
        ]);

        $form->add('submit', SubmitType::class, ['label' => 'Create']);

        return $form;
    }

    /**
     * @return \Symfony\Component\Form\FormInterface
     */
    private function createDeleteForm($event_id)
    {
        return $this->createFormBuilder()
            ->setAction($this->generateUrl('chill_event__event_delete', [
                'event_id' => $event_id,
            ]))
            ->add('submit', SubmitType::class, ['label' => 'Delete'])
            ->getForm();
    }
}
