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

use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\MainBundle\Serializer\Model\Counter;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Chill\MainBundle\Timeline\TimelineBuilder;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Privacy\PrivacyEvent;
use Chill\TaskBundle\Entity\SingleTask;
use Chill\TaskBundle\Event\TaskEvent;
use Chill\TaskBundle\Event\UI\UIEvent;
use Chill\TaskBundle\Form\SingleTaskType;
use Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface;
use Chill\TaskBundle\Repository\SingleTaskRepository;
use Chill\TaskBundle\Repository\SingleTaskStateRepository;
use Chill\TaskBundle\Security\Authorization\TaskVoter;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;

final class SingleTaskController extends AbstractController
{
    public function __construct(
        private readonly CenterResolverDispatcherInterface $centerResolverDispatcher,
        private readonly PaginatorFactory $paginatorFactory,
        private readonly SingleTaskAclAwareRepositoryInterface $singleTaskAclAwareRepository,
        private readonly TranslatorInterface $translator,
        private readonly EventDispatcherInterface $eventDispatcher,
        private readonly TimelineBuilder $timelineBuilder,
        private readonly LoggerInterface $logger,
        private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
        private readonly SingleTaskStateRepository $singleTaskStateRepository,
        private readonly SingleTaskRepository $singleTaskRepository,
        private readonly Security $security,
        private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
    ) {}

    #[Route(path: '/{_locale}/task/single-task/{id}/delete', name: 'chill_task_single_task_delete', methods: ['GET', 'POST', 'DELETE'])]
    public function deleteAction(Request $request, mixed $id)
    {
        $course = null;
        $em = $this->managerRegistry->getManager();
        $task = $em->getRepository(SingleTask::class)->find($id);

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

        if (null !== $task->getPerson()) {
            $personId = $task->getPerson()->getId();

            if (null === $personId) {
                return new Response('You must provide a person_id', Response::HTTP_BAD_REQUEST);
            }

            $person = $this->managerRegistry->getManager()
                ->getRepository(Person::class)
                ->find($personId);

            if (null === $person) {
                throw $this->createNotFoundException('Invalid person id');
            }
        } else {
            $courseId = $task->getCourse()->getId();

            if (null === $courseId) {
                return new Response('You must provide a course_id', Response::HTTP_BAD_REQUEST);
            }

            $course = $this->managerRegistry->getManager()
                ->getRepository(AccompanyingPeriod::class)
                ->find($courseId);

            if (null === $course) {
                throw $this->createNotFoundException('Invalid accompanying period id');
            }
        }

        // TODO: reactivate right to delete

        // $this->denyAccessUnlessGranted(TaskVoter::DELETE, $task, 'You are not '
        //     . 'allowed to delete this task');

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

        if (Request::METHOD_POST === $request->getMethod()) {
            $form->handleRequest($request);
            if ($form->isSubmitted() && $form->isValid()) {
                $this->logger->notice('A task has been removed', [
                    'by_user' => $this->getUser()->getUsername(),
                    'task_id' => $task->getId(),
                    'description' => $task->getDescription(),
                    'assignee' => $task->getAssignee(),
                    //    TODO reimplement scope
                    //    'scope_id' => $task->getScope()->getId(),
                ]);

                $em = $this->managerRegistry->getManager();
                $em->remove($task);
                $em->flush();

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

                if ($task->getContext() instanceof Person) {
                    return $this->redirectToRoute('chill_task_singletask_by-person_list', ['id' => $task->getPerson()->getId()]);
                }

                return $this->redirectToRoute('chill_task_singletask_by-course_list', ['id' => $task->getCourse()->getId()]);
            }
        }

        if ($task->getContext() instanceof Person) {
            return $this->render(
                '@ChillTask/SingleTask/Person/confirm_delete.html.twig',
                [
                    'task' => $task,
                    'delete_form' => $form->createView(),
                ]
            );
        }

        return $this->render(
            '@ChillTask/SingleTask/AccompanyingCourse/confirm_delete.html.twig',
            [
                'task' => $task,
                'delete_form' => $form->createView(),
                'accompanyingCourse' => $course,
            ]
        );
    }

    #[Route(path: '/{_locale}/task/single-task/{id}/edit', name: 'chill_task_single_task_edit')]
    public function editAction(
        SingleTask $task,
        Request $request,
    ) {
        $this->denyAccessUnlessGranted(TaskVoter::UPDATE, $task, 'You are not '
            .'allowed to edit this task');

        $event = (new UIEvent('single-task', $task))
            ->setForm($this->setCreateForm($task, TaskVoter::UPDATE));
        $this->eventDispatcher->dispatch($event, UIEvent::EDIT_FORM);

        $form = $event->getForm();

        $form->handleRequest($request);

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

                $em->flush();

                $this->addFlash('success', $this->translator
                    ->trans('The task has been updated'));

                if ($task->getContext() instanceof Person) {
                    $event = new PrivacyEvent($task->getPerson(), [
                        'element_class' => SingleTask::class,
                        'element_id' => $task->getId(),
                        'action' => 'update',
                    ]);
                    $this->eventDispatcher->dispatch($event, PrivacyEvent::PERSON_PRIVACY_EVENT);

                    if ($request->query->has('returnPath')) {
                        return $this->redirect($request->query->get('returnPath'));
                    }

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

                if ($request->query->has('returnPath')) {
                    return $this->redirect($request->query->get('returnPath'));
                }

                return $this->redirectToRoute(
                    'chill_task_singletask_by-course_list',
                    ['id' => $task->getCourse()->getId()]
                );
            }
            $this->addFlash('error', $this->translator->trans('This form contains errors'));
        }

        $this->eventDispatcher->dispatch($event, UIEvent::EDIT_PAGE);

        if ($event->hasResponse()) {
            return $event->getResponse();
        }

        if ($task->getContext() instanceof Person) {
            $event = new PrivacyEvent($task->getPerson(), [
                'element_class' => SingleTask::class,
                'element_id' => $task->getId(),
                'action' => 'edit',
            ]);
            $this->eventDispatcher->dispatch($event, PrivacyEvent::PERSON_PRIVACY_EVENT);

            return $this->render('@ChillTask/SingleTask/Person/edit.html.twig', [
                'task' => $task,
                'form' => $form->createView(),
            ]);
        }

        return $this->render('@ChillTask/SingleTask/AccompanyingCourse/edit.html.twig', [
            'task' => $task,
            'form' => $form->createView(),
            'accompanyingCourse' => $task->getCourse(),
        ]);
    }

    /**
     * Arguments:
     *  - user_id
     *  - scope_id
     *  - s
     *  - person_id
     *  - hide_form (hide the form to filter the tasks)
     *  - status: date state, amongst SingleTaskRepository::DATE_STATUSES, or 'closed'.
     */
    #[Route(path: '/{_locale}/task/single-task/list', name: 'chill_task_singletask_list')]
    public function listAction(
        Request $request,
    ) {
        $this->denyAccessUnlessGranted(TaskVoter::SHOW, null);

        $showMissionTypeFilter = $this->singleTaskRepository->countByDistinctTypes() > 1;

        $filterOrder = $this->buildFilterOrder(true, $showMissionTypeFilter);

        $filteredUsers = $filterOrder->getUserPickerData('userPicker');

        $flags = \array_merge(
            $filterOrder->getCheckboxData('status'),
            \array_map(static fn ($i) => 'state_'.$i, $filterOrder->hasCheckboxData('states') ? $filterOrder->getCheckboxData('states') : [])
        );

        if ($showMissionTypeFilter) {
            $types = $filterOrder->getCheckboxData('missionTypePicker');
        }

        $nb = $this->singleTaskAclAwareRepository->countByAllViewable(
            $filterOrder->getQueryString(),
            $flags,
            $types ?? [],
            $filteredUsers
        );
        $paginator = $this->paginatorFactory->create($nb);

        if (0 < $nb) {
            $tasks = $this->singleTaskAclAwareRepository->findByAllViewable(
                $filterOrder->getQueryString(),
                $flags,
                $types ?? [],
                $filteredUsers,
                $paginator->getCurrentPageFirstItemNumber(),
                $paginator->getItemsPerPage(),
                [
                    'startDate' => 'DESC',
                    'endDate' => 'DESC',
                ]
            );
        } else {
            $tasks = [];
        }

        return $this->render('@ChillTask/SingleTask/List/index.html.twig', [
            'tasks' => $tasks,
            'paginator' => $paginator,
            'filter_order' => $filterOrder,
        ]);
    }

    #[Route(path: '/{_locale}/task/single-task/by-course/{id}', name: 'chill_task_singletask_by-course_list')]
    public function listCourseTasks(
        AccompanyingPeriod $course,
        FormFactoryInterface $formFactory,
        Request $request,
    ): Response {
        $this->denyAccessUnlessGranted(TaskVoter::SHOW, $course);

        $filterOrder = $this->buildFilterOrder();
        $flags = \array_merge(
            $filterOrder->getCheckboxData('status'),
            \array_map(static fn ($i) => 'state_'.$i, $filterOrder->hasCheckboxData('states') ? $filterOrder->getCheckboxData('states') : [])
        );
        $nb = $this->singleTaskAclAwareRepository->countByCourse(
            $course,
            $filterOrder->getQueryString(),
            $flags
        );
        $paginator = $this->paginatorFactory->create($nb);

        if (0 < $nb) {
            $tasks = $this->singleTaskAclAwareRepository->findByCourse(
                $course,
                $filterOrder->getQueryString(),
                $flags,
                $paginator->getCurrentPageFirstItemNumber(),
                $paginator->getItemsPerPage(),
                [
                    'startDate' => 'DESC',
                    'endDate' => 'DESC',
                ]
            );
        } else {
            $tasks = [];
        }

        return $this->render(
            '@ChillTask/SingleTask/AccompanyingCourse/list.html.twig',
            [
                'tasks' => $tasks,
                'accompanyingCourse' => $course,
                'paginator' => $paginator,
                'filter_order' => $filterOrder,
            ]
        );
    }

    #[Route(path: '/{_locale}/task/single-task/by-person/{id}', name: 'chill_task_singletask_by-person_list')]
    public function listPersonTasks(
        Person $person,
    ): Response {
        $this->denyAccessUnlessGranted(TaskVoter::SHOW, $person);

        $filterOrder = $this->buildFilterOrder();
        $flags = \array_merge(
            $filterOrder->getCheckboxData('status'),
            \array_map(static fn ($i) => 'state_'.$i, $filterOrder->hasCheckboxData('states') ? $filterOrder->getCheckboxData('states') : [])
        );
        $nb = $this->singleTaskAclAwareRepository->countByPerson(
            $person,
            $filterOrder->getQueryString(),
            $flags
        );
        $paginator = $this->paginatorFactory->create($nb);

        if (0 < $nb) {
            $tasks = $this->singleTaskAclAwareRepository->findByPerson(
                $person,
                $filterOrder->getQueryString(),
                $flags,
                $paginator->getCurrentPageFirstItemNumber(),
                $paginator->getItemsPerPage(),
                [
                    'startDate' => 'DESC',
                    'endDate' => 'DESC',
                ]
            );
        } else {
            $tasks = [];
        }

        return $this->render(
            '@ChillTask/SingleTask/Person/list.html.twig',
            [
                'tasks' => $tasks,
                'person' => $person,
                'paginator' => $paginator,
                'filter_order' => $filterOrder,
            ]
        );
    }

    /**
     * @return Response
     */
    #[Route(path: '/{_locale}/task/single-task/list/my', name: 'chill_task_singletask_my_tasks', defaults: ['_format' => 'html'])]
    #[Route(path: '/api/1.0/task/single-task/list/my', defaults: ['_format' => 'json'])]
    public function myTasksAction(string $_format, Request $request)
    {
        $this->denyAccessUnlessGranted('ROLE_USER');

        $filterOrder = $this->buildFilterOrder(false);
        $flags = \array_merge(
            $filterOrder->getCheckboxData('status'),
            \array_map(static fn ($i) => 'state_'.$i, $filterOrder->hasCheckboxData('states') ? $filterOrder->getCheckboxData('states') : [])
        );
        $nb = $this->singleTaskAclAwareRepository->countByCurrentUsersTasks(
            $filterOrder->getQueryString(),
            $flags
        );

        if ('json' === $_format && $request->query->getBoolean('countOnly')) {
            return $this->json(
                new Counter($nb),
            );
        }

        $paginator = $this->paginatorFactory->create($nb);
        $tasks = $this->singleTaskAclAwareRepository->findByCurrentUsersTasks(
            $filterOrder->getQueryString(),
            $flags,
            $paginator->getCurrentPageFirstItemNumber(),
            $paginator->getItemsPerPage(),
            [
                'startDate' => 'DESC',
                'endDate' => 'DESC',
            ]
        );

        switch ($_format) {
            case 'html':
                return $this->render('@ChillTask/SingleTask/List/index_my_tasks.html.twig', [
                    'tasks' => $tasks,
                    'paginator' => $paginator,
                    'filter_order' => $filterOrder,
                ]);

            case 'json':
                $collection = new Collection($tasks, $paginator);

                return $this->json(
                    $collection,
                    JsonResponse::HTTP_OK,
                    [],
                    ['groups' => ['read']]
                );

            default:
                throw new BadRequestHttpException("format not supported: {$_format}");
        }
    }

    #[Route(path: '/{_locale}/task/single-task/new', name: 'chill_task_single_task_new')]
    public function newAction(Request $request)
    {
        $user = $this->security->getUser();

        if (!$user instanceof User) {
            throw new AccessDeniedHttpException();
        }

        $task = (new SingleTask())
            ->setAssignee($user)
            ->setType('task_default');

        $entityType = $this->getEntityContext($request);

        if (null === $entityType) {
            throw new BadRequestHttpException('You must provide a entity_type');
        }

        $entityId = $request->query->getInt("{$entityType}_id", 0);

        if (null === $entityId) {
            return new BadRequestHttpException("You must provide a {$entityType}_id");
        }

        switch ($entityType) {
            case 'person':
                $person = $this->managerRegistry->getManager()
                    ->getRepository(Person::class)
                    ->find($entityId);

                if (null === $person) {
                    $this->createNotFoundException('Invalid person id');
                }

                $task->setPerson($person);
                $role = TaskVoter::CREATE_PERSON;

                break;

            case 'course':
                $course = $this->managerRegistry->getManager()
                    ->getRepository(AccompanyingPeriod::class)
                    ->find($entityId);

                if (null === $course) {
                    $this->createNotFoundException('Invalid accompanying course id');
                }

                $task->setCourse($course);
                $role = TaskVoter::CREATE_COURSE;

                break;

            default:
                return new BadRequestHttpException("context with {$entityType} is not supported");
        }

        $this->denyAccessUnlessGranted($role, $task, 'You are not '
             .'allowed to create this task');

        $form = $this->setCreateForm($task, $role);

        $form->handleRequest($request);

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

                $this->eventDispatcher->dispatch(new TaskEvent($task), TaskEvent::PERSIST);

                $em->flush();

                $this->addFlash('success', $this->translator->trans('The task is created'));

                if ($request->query->has('returnPath')) {
                    return $this->redirect($request->query->get('returnPath'));
                }

                if ('person' === $entityType) {
                    return $this->redirectToRoute('chill_task_singletask_by-person_list', [
                        'id' => $task->getPerson()->getId(),
                    ]);
                }

                if ('course' === $entityType) {
                    return $this->redirectToRoute('chill_task_singletask_by-course_list', [
                        'id' => $task->getCourse()->getId(),
                    ]);
                }
            } else {
                $this->addFlash('error', $this->translator->trans('This form contains errors'));
            }
        }

        return match ($entityType) {
            'person' => $this->render('@ChillTask/SingleTask/Person/new.html.twig', [
                'form' => $form->createView(),
                'task' => $task,
                'person' => $task->getPerson(),
            ]),
            'course' => $this->render('@ChillTask/SingleTask/AccompanyingCourse/new.html.twig', [
                'form' => $form->createView(),
                'task' => $task,
                'accompanyingCourse' => $task->getCourse(),
            ]),
            default => throw new \LogicException('entity context not supported'),
        };
    }

    #[Route(path: '/{_locale}/task/single-task/{id}/show', name: 'chill_task_single_task_show')]
    public function showAction(SingleTask $task, Request $request)
    {
        $this->denyAccessUnlessGranted(TaskVoter::SHOW, $task);

        if ($task->getContext() instanceof Person) {
            $event = new PrivacyEvent($task->getContext(), [
                'element_class' => SingleTask::class,
                'element_id' => $task->getId(),
                'action' => 'show',
            ]);
            $this->eventDispatcher->dispatch($event, PrivacyEvent::PERSON_PRIVACY_EVENT);
        }

        $timeline = $this->timelineBuilder
            ->getTimelineHTML('task', ['task' => $task]);

        if ($task->getContext() instanceof Person) {
            return $this->render('@ChillTask/SingleTask/Person/show.html.twig', [
                'task' => $task,
                'timeline' => $timeline,
            ]);
        }

        return $this->render('@ChillTask/SingleTask/AccompanyingCourse/show.html.twig', [
            'task' => $task,
            'timeline' => $timeline,
        ]);
    }

    /**
     * @return FormInterface
     */
    protected function setCreateForm(SingleTask $task, string $role)
    {
        $form = $this->createForm(SingleTaskType::class, $task, [
            'role' => $role,
        ]);

        $form->add('submit', SubmitType::class);

        return $form;
    }

    private function buildFilterOrder($includeFilterByUser = true, $includeMissionType = false): FilterOrderHelper
    {
        $statuses = ['no-alert', 'warning', 'alert'];
        $statusTrans = [
            'Tasks without alert',
            'Tasks near deadline',
            'Tasks over deadline',
        ];
        $arrayOfTypes = [];

        $filterBuilder = $this->filterOrderHelperFactory
            ->create(self::class)
            ->addSearchBox()
            ->addCheckbox('status', $statuses, $statuses, $statusTrans);

        $states = $this->singleTaskStateRepository->findAllExistingStates();
        $checked = array_values(array_filter($states, fn (string $state) => !in_array($state, ['to_validate', 'in_progress', 'closed', 'canceled', 'validated'], true)));
        if ([] !== $states) {
            $filterBuilder
                ->addCheckbox('states', $states, $checked);
        }

        if ($includeFilterByUser) {
            $filterBuilder
                ->addUserPicker('userPicker', 'Filter by user', ['multiple' => true, 'required' => false]);
        }

        if ($includeMissionType) {
            $filterBuilder->addCheckbox(
                'missionTypePicker',
                \array_map(
                    fn ($row) => $row['type'],
                    $this->singleTaskRepository->findAllTaskDistinctTypes()
                )
            );
        }

        return $filterBuilder->build();
    }

    /**
     * Creates a form to delete a Task entity by id.
     */
    private function createDeleteForm(mixed $id): FormInterface
    {
        return $this->createFormBuilder()
            ->setAction($this->generateUrl(
                'chill_task_single_task_delete',
                ['id' => $id]
            ))
            ->add('submit', SubmitType::class, ['label' => 'Delete'])
            ->getForm();
    }

    private function getEntityContext(Request $request)
    {
        if ($request->query->has('person_id')) {
            return 'person';
        }

        if ($request->query->has('course_id')) {
            return 'course';
        }

        return null;
    }
}
