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

use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Form\ActivityType;
use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Repository\ActivityTypeCategoryRepository;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\LocationRepository;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Security\ChillSecurity;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Privacy\PrivacyEvent;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

final class ActivityController extends AbstractController
{
    public function __construct(
        private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
        private readonly ActivityTypeRepositoryInterface $activityTypeRepository,
        private readonly ActivityTypeCategoryRepository $activityTypeCategoryRepository,
        private readonly PersonRepository $personRepository,
        private readonly ThirdPartyRepository $thirdPartyRepository,
        private readonly LocationRepository $locationRepository,
        private readonly ActivityRepository $activityRepository,
        private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository,
        private readonly EntityManagerInterface $entityManager,
        private readonly EventDispatcherInterface $eventDispatcher,
        private readonly LoggerInterface $logger,
        private readonly SerializerInterface $serializer,
        private readonly UserRepositoryInterface $userRepository,
        private readonly CenterResolverManagerInterface $centerResolver,
        private readonly TranslatorInterface $translator,
        private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
        private readonly TranslatableStringHelperInterface $translatableStringHelper,
        private readonly PaginatorFactory $paginatorFactory,
        private readonly ChillSecurity $security,
    ) {}

    /**
     * Deletes a Activity entity.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/activity/{id}/delete', name: 'chill_activity_activity_delete', methods: ['GET', 'POST', 'DELETE'])]
    public function deleteAction(Request $request, mixed $id)
    {
        $view = null;

        [$person, $accompanyingPeriod] = $this->getEntity($request);

        $activity = $this->activityRepository->find($id);

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

        if ($activity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
            $view = '@ChillActivity/Activity/confirm_deleteAccompanyingCourse.html.twig';
            $accompanyingPeriod = $activity->getAccompanyingPeriod();
        } else {
            $view = '@ChillActivity/Activity/confirm_deletePerson.html.twig';
        }

        // TODO
        // $this->denyAccessUnlessGranted('CHILL_ACTIVITY_DELETE', $activity);

        $form = $this->createDeleteForm($activity->getId(), $person, $accompanyingPeriod);

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

            if ($form->isSubmitted() && $form->isValid()) {
                $this->logger->notice('An activity has been removed', [
                    'by_user' => $this->getUser()->getUsername(),
                    'activity_id' => $activity->getId(),
                    'person_id' => $activity->getPerson() ? $activity->getPerson()->getId() : null,
                    'comment' => $activity->getComment()->getComment(),
                    'scope_id' => $activity->getScope() ? $activity->getScope()->getId() : null,
                    'reasons_ids' => $activity->getReasons()
                        ->map(
                            static fn (ActivityReason $ar): int => $ar->getId()
                        )
                        ->toArray(),
                    'type_id' => $activity->getActivityType()->getId(),
                    'duration' => $activity->getDurationTime() ? $activity->getDurationTime()->format('U') : null,
                    'date' => $activity->getDate()->format('Y-m-d'),
                    'attendee' => $activity->getAttendee(),
                ]);

                $this->entityManager->remove($activity);
                $this->entityManager->flush();

                $this->addFlash('success', $this->translator
                    ->trans('The activity has been successfully removed.'));

                $params = $this->buildParamsToUrl($person, $accompanyingPeriod);

                return $this->redirectToRoute('chill_activity_activity_list', $params);
            }
        }

        return $this->render($view, [
            'activity' => $activity,
            'delete_form' => $form->createView(),
            'person' => $person,
            'accompanyingCourse' => $accompanyingPeriod,
        ]);
    }

    /**
     * Displays a form to edit an existing Activity entity.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/activity/{id}/edit', name: 'chill_activity_activity_edit', methods: ['GET', 'POST', 'PUT'])]
    public function editAction(int $id, Request $request): Response
    {
        $view = null;

        $entity = $this->activityRepository->find($id);

        if (null === $entity) {
            throw $this->createNotFoundException('Unable to find Activity entity.');
        }

        $accompanyingPeriod = $entity->getAccompanyingPeriod();
        $person = $entity->getPerson();

        if ($entity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
            $view = '@ChillActivity/Activity/editAccompanyingCourse.html.twig';
            $accompanyingPeriod = $entity->getAccompanyingPeriod();
        } else {
            $view = '@ChillActivity/Activity/editPerson.html.twig';
        }
        // TODO
        // $this->denyAccessUnlessGranted('CHILL_ACTIVITY_UPDATE', $entity);

        $form = $this->createForm(ActivityType::class, $entity, [
            'center' => $this->centerResolver->resolveCenters($entity)[0] ?? null,
            'role' => 'CHILL_ACTIVITY_UPDATE',
            'activityType' => $entity->getActivityType(),
            'accompanyingPeriod' => $accompanyingPeriod,
        ]);

        if ($form->has('documents')) {
            $form->add('gendocTemplateId', HiddenType::class, [
                'mapped' => false,
                'data' => null,
                'attr' => [
                    // required for js
                    'data-template-id' => 'data-template-id',
                ],
            ]);
        }

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->entityManager->persist($entity);
            $this->entityManager->flush();

            $params = $this->buildParamsToUrl($person, $accompanyingPeriod);
            $params['id'] = $entity->getId();

            if ($form->has('gendocTemplateId') && null !== $form['gendocTemplateId']->getData()) {
                return $this->redirectToRoute(
                    'chill_docgenerator_generate_from_template',
                    [
                        'template' => $form->get('gendocTemplateId')->getData(),
                        'entityClassName' => Activity::class,
                        'entityId' => $entity->getId(),
                        'returnPath' => $this->generateUrl('chill_activity_activity_edit', $params),
                    ]
                );
            }

            $this->addFlash('success', $this->translator->trans('Success : activity updated!'));

            return $this->redirectToRoute('chill_activity_activity_show', $params);
        }

        $deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod);

        /*
         * TODO
        $event = new PrivacyEvent($person, array(
            'element_class' => Activity::class,
            'element_id' => $entity->getId(),
            'action' => 'edit'
        ));
        $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
         */

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

        return $this->render($view, [
            'entity' => $entity,
            'edit_form' => $form->createView(),
            'delete_form' => $deleteForm->createView(),
            'person' => $person,
            'accompanyingCourse' => $accompanyingPeriod,
            'activity_json' => $activity_array,
        ]);
    }

    /**
     * Lists all Activity entities.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/activity/', name: 'chill_activity_activity_list')]
    public function listAction(Request $request): Response
    {
        $view = null;
        $activities = [];

        [$person, $accompanyingPeriod] = $this->getEntity($request);
        $filter = $this->buildFilterOrder($person ?? $accompanyingPeriod);

        $filterArgs = [
            'my_activities' => $filter->getSingleCheckboxData('my_activities'),
            'types' => $filter->hasEntityChoice('activity_types') ? $filter->getEntityChoiceData('activity_types') : [],
            'jobs' => $filter->hasEntityChoice('jobs') ? $filter->getEntityChoiceData('jobs') : [],
            'before' => $filter->getDateRangeData('activity_date')['to'],
            'after' => $filter->getDateRangeData('activity_date')['from'],
        ];

        if ($person instanceof Person) {
            $this->denyAccessUnlessGranted(ActivityVoter::SEE, $person);
            $count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE, $filterArgs);
            $paginator = $this->paginatorFactory->create($count);
            $activities = $this->activityACLAwareRepository
                ->findByPerson(
                    $person,
                    ActivityVoter::SEE,
                    $paginator->getCurrentPageFirstItemNumber(),
                    $paginator->getItemsPerPage(),
                    ['date' => 'DESC', 'id' => 'DESC'],
                    $filterArgs
                );

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

            $view = '@ChillActivity/Activity/listPerson.html.twig';
        } elseif ($accompanyingPeriod instanceof AccompanyingPeriod) {
            $this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod);

            $count = $this->activityACLAwareRepository->countByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, $filterArgs);
            $paginator = $this->paginatorFactory->create($count);
            $activities = $this->activityACLAwareRepository
                ->findByAccompanyingPeriod(
                    $accompanyingPeriod,
                    ActivityVoter::SEE,
                    $paginator->getCurrentPageFirstItemNumber(),
                    $paginator->getItemsPerPage(),
                    ['date' => 'DESC', 'id' => 'DESC'],
                    $filterArgs
                );

            $view = '@ChillActivity/Activity/listAccompanyingCourse.html.twig';
        } else {
            throw new \LogicException('Unsupported');
        }

        return $this->render(
            $view,
            [
                'activities' => $activities,
                'person' => $person,
                'accompanyingCourse' => $accompanyingPeriod,
                'filter' => $filter,
                'paginator' => $paginator,
            ]
        );
    }

    private function buildFilterOrder(AccompanyingPeriod|Person $associated): FilterOrderHelper
    {
        $filterBuilder = $this->filterOrderHelperFactory->create(self::class);
        $types = $this->activityACLAwareRepository->findActivityTypeByAssociated($associated);
        $jobs = $this->activityACLAwareRepository->findUserJobByAssociated($associated);

        $filterBuilder
            ->addDateRange('activity_date', 'activity.date')
            ->addSingleCheckbox('my_activities', 'activity_filter.My activities');

        if (1 < count($types)) {
            $filterBuilder
                ->addEntityChoice('activity_types', 'activity_filter.Types', \Chill\ActivityBundle\Entity\ActivityType::class, $types, [
                    'choice_label' => function (\Chill\ActivityBundle\Entity\ActivityType $activityType) {
                        $text = match ($activityType->hasCategory()) {
                            true => $this->translatableStringHelper->localize($activityType->getCategory()->getName()).' > ',
                            false => '',
                        };

                        return $text.$this->translatableStringHelper->localize($activityType->getName());
                    },
                ]);
        }

        if (1 < count($jobs)) {
            $filterBuilder
                ->addEntityChoice('jobs', 'activity_filter.Jobs', UserJob::class, $jobs, [
                    'choice_label' => fn (UserJob $u) => $this->translatableStringHelper->localize($u->getLabel()),
                ]);
        }

        return $filterBuilder->build();
    }

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/activity/new', name: 'chill_activity_activity_new', methods: ['POST', 'GET'])]
    public function newAction(Request $request): Response
    {
        $view = null;

        [$person, $accompanyingPeriod] = $this->getEntity($request);

        if ($accompanyingPeriod instanceof AccompanyingPeriod) {
            $view = '@ChillActivity/Activity/newAccompanyingCourse.html.twig';
        } elseif ($person instanceof Person) {
            $view = '@ChillActivity/Activity/newPerson.html.twig';
        }

        $activityType_id = $request->get('activityType_id', 0);
        $activityType = $this->activityTypeRepository->find($activityType_id);

        if (isset($activityType) && !$activityType->isActive()) {
            throw new \InvalidArgumentException('Activity type must be active');
        }

        $activityData = null;

        if ($request->query->has('activityData')) {
            $activityData = $request->query->get('activityData');
        }

        if (
            !$activityType instanceof \Chill\ActivityBundle\Entity\ActivityType
            || !$activityType->isActive()
        ) {
            $params = $this->buildParamsToUrl($person, $accompanyingPeriod);

            if (null !== $activityData) {
                $params['activityData'] = $activityData;
            }

            return $this->redirectToRoute('chill_activity_activity_select_type', $params);
        }

        $entity = new Activity();
        $entity->setUser($this->security->getUser());
        $entity->addUser($this->security->getUser());

        if ($person instanceof Person) {
            $entity->setPerson($person);
            $entity->getPersons()->add($person);
        }

        if ($accompanyingPeriod instanceof AccompanyingPeriod) {
            $entity->setAccompanyingPeriod($accompanyingPeriod);
        }

        $entity->setActivityType($activityType);
        $entity->setDate(new \DateTime('now'));

        if ($request->query->has('activityData')) {
            $activityData = $request->query->all('activityData');

            if (\array_key_exists('durationTime', $activityData) && $activityType->getDurationTimeVisible() > 0) {
                $durationTimeInMinutes = $activityData['durationTime'];
                $hours = floor($durationTimeInMinutes / 60);
                $minutes = $durationTimeInMinutes % 60;
                $duration = \DateTime::createFromFormat('H:i', $hours.':'.$minutes);

                if ($duration) {
                    $entity->setDurationTime($duration);
                }
            }

            if (\array_key_exists('date', $activityData)) {
                $date = \DateTime::createFromFormat('Y-m-d', $activityData['date']);

                if ($date) {
                    $entity->setDate($date);
                }
            }

            if (\array_key_exists('personsId', $activityData) && $activityType->getPersonsVisible() > 0) {
                foreach ($activityData['personsId'] as $personId) {
                    $concernedPerson = $this->personRepository->find($personId);
                    $entity->addPerson($concernedPerson);
                }
            }

            if (\array_key_exists('professionalsId', $activityData) && $activityType->getThirdPartiesVisible() > 0) {
                foreach ($activityData['professionalsId'] as $professionalsId) {
                    $professional = $this->thirdPartyRepository->find($professionalsId);
                    $entity->addThirdParty($professional);
                }
            }

            if (\array_key_exists('usersId', $activityData) && $activityType->getUsersVisible() > 0) {
                foreach ($activityData['usersId'] as $userId) {
                    $user = $this->userRepository->find($userId);

                    if (null !== $user) {
                        $entity->addUser($user);
                    }
                }
            }

            if (\array_key_exists('location', $activityData) && $activityType->getLocationVisible() > 0) {
                $location = $this->locationRepository->find($activityData['location']);
                $entity->setLocation($location);
            }

            if (\array_key_exists('comment', $activityData) && $activityType->getCommentVisible() > 0) {
                $comment = new CommentEmbeddable();
                $comment->setComment($activityData['comment']);
                $comment->setUserId($this->security->getUser()->getId());
                $comment->setDate(new \DateTime('now'));
                $entity->setComment($comment);
            }
        }

        $this->denyAccessUnlessGranted(ActivityVoter::CREATE, $entity);

        $form = $this->createForm(ActivityType::class, $entity, [
            'center' => $this->centerResolver->resolveCenters($entity)[0] ?? null,
            'role' => 'CHILL_ACTIVITY_CREATE',
            'activityType' => $entity->getActivityType(),
            'accompanyingPeriod' => $accompanyingPeriod,
        ]);

        if ($form->has('documents')) {
            $form->add('gendocTemplateId', HiddenType::class, [
                'mapped' => false,
                'data' => null,
                'attr' => [
                    // required for js
                    'data-template-id' => 'data-template-id',
                ],
            ]);
        }

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->entityManager->persist($entity);
            $this->entityManager->flush();

            if ($form->has('gendocTemplateId') && null !== $form['gendocTemplateId']->getData()) {
                return $this->redirectToRoute(
                    'chill_docgenerator_generate_from_template',
                    [
                        'template' => $form->get('gendocTemplateId')->getData(),
                        'entityClassName' => Activity::class,
                        'entityId' => $entity->getId(),
                        'returnPath' => $this->generateUrl('chill_activity_activity_edit', [
                            'id' => $entity->getId(),
                        ]),
                    ]
                );
            }

            $this->addFlash('success', $this->translator->trans('Success : activity created!'));

            $params = $this->buildParamsToUrl($person, $accompanyingPeriod);

            $params['id'] = $entity->getId();

            return $this->redirectToRoute('chill_activity_activity_show', $params);
        }

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

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

        $defaultLocation = $this->security->getUser()->getCurrentLocation();

        return $this->render($view, [
            'person' => $person,
            'accompanyingCourse' => $accompanyingPeriod,
            'entity' => $entity,
            'form' => $form->createView(),
            'activity_json' => $activity_array,
            'default_location' => $defaultLocation,
        ]);
    }

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/activity/select-type', name: 'chill_activity_activity_select_type')]
    public function selectTypeAction(Request $request): Response
    {
        $view = null;

        [$person, $accompanyingPeriod] = $this->getEntity($request);

        if ($accompanyingPeriod instanceof AccompanyingPeriod) {
            $view = '@ChillActivity/Activity/selectTypeAccompanyingCourse.html.twig';
        } elseif ($person instanceof Person) {
            $view = '@ChillActivity/Activity/selectTypePerson.html.twig';
        }

        $data = [];

        $activityTypeCategories = $this
            ->activityTypeCategoryRepository
            ->findBy(['active' => true], ['ordering' => 'ASC']);

        foreach ($activityTypeCategories as $activityTypeCategory) {
            $activityTypes = $this
                ->activityTypeRepository
                ->findBy(
                    ['active' => true, 'category' => $activityTypeCategory],
                    ['ordering' => 'ASC']
                );

            $data[] = [
                'activityTypeCategory' => $activityTypeCategory,
                'activityTypes' => $activityTypes,
            ];
        }

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

        return $this->render($view, [
            'person' => $person,
            'accompanyingCourse' => $accompanyingPeriod,
            'data' => $data,
            'activityData' => $request->query->all('activityData'),
        ]);
    }

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/activity/{id}/show', name: 'chill_activity_activity_show')]
    public function showAction(Request $request, int $id): Response
    {
        $entity = $this->activityRepository->find($id);

        if (null === $entity) {
            throw $this->createNotFoundException('Unable to find Activity entity.');
        }

        $accompanyingPeriod = $entity->getAccompanyingPeriod();
        $person = $entity->getPerson();

        if ($accompanyingPeriod instanceof AccompanyingPeriod) {
            $view = '@ChillActivity/Activity/showAccompanyingCourse.html.twig';
        } elseif ($person instanceof Person) {
            $view = '@ChillActivity/Activity/showPerson.html.twig';
        } else {
            throw new \RuntimeException('the activity should be linked with a period or person');
        }

        if (null !== $accompanyingPeriod) {
            // @TODO: Properties created dynamically.
            $entity->personsAssociated = $entity->getPersonsAssociated();
            $entity->personsNotAssociated = $entity->getPersonsNotAssociated();
        }

        $this->denyAccessUnlessGranted(ActivityVoter::SEE, $entity);

        $deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod);

        // TODO
        /*
        $event = new PrivacyEvent($person, array(
            'element_class' => Activity::class,
            'element_id' => $entity->getId(),
            'action' => 'show'
        ));
        $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
         */

        return $this->render($view, [
            'person' => $person,
            'accompanyingCourse' => $accompanyingPeriod,
            'entity' => $entity,
            'delete_form' => $deleteForm->createView(),
        ]);
    }

    private function buildParamsToUrl(?Person $person, ?AccompanyingPeriod $accompanyingPeriod): array
    {
        $params = [];

        if (null !== $person) {
            $params['person_id'] = $person->getId();
        }

        if (null !== $accompanyingPeriod) {
            $params['accompanying_period_id'] = $accompanyingPeriod->getId();
        }

        return $params;
    }

    /**
     * Creates a form to delete a Activity entity by id.
     */
    private function createDeleteForm(int $id, ?Person $person, ?AccompanyingPeriod $accompanyingPeriod): FormInterface
    {
        $params = $this->buildParamsToUrl($person, $accompanyingPeriod);
        $params['id'] = $id;

        return $this->createFormBuilder()
            ->setAction($this->generateUrl('chill_activity_activity_delete', $params))
            ->add('submit', SubmitType::class, ['label' => 'Delete'])
            ->getForm();
    }

    private function getEntity(Request $request): array
    {
        $person = $accompanyingPeriod = null;

        if ($request->query->has('person_id')) {
            $person_id = $request->get('person_id');
            $person = $this->personRepository->find($person_id);

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

            $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
        } elseif ($request->query->has('accompanying_period_id')) {
            $accompanying_period_id = $request->get('accompanying_period_id');
            $accompanyingPeriod = $this->accompanyingPeriodRepository->find($accompanying_period_id);

            if (null === $accompanyingPeriod) {
                throw $this->createNotFoundException('Accompanying Period not found');
            }

            // TODO Add permission
            // $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
        } else {
            throw $this->createNotFoundException('Person or Accompanying Period not found');
        }

        return [
            $person,
            $accompanyingPeriod,
        ];
    }
}
