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

use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\NotificationComment;
use Chill\MainBundle\Form\NotificationCommentType;
use Chill\MainBundle\Form\NotificationType;
use Chill\MainBundle\Notification\Exception\NotificationHandlerNotFound;
use Chill\MainBundle\Notification\FlagProviders\NotificationByUserFlagProvider;
use Chill\MainBundle\Notification\NotificationHandlerManager;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\NotificationRepository;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Security\Authorization\NotificationVoter;
use Chill\MainBundle\Security\ChillSecurity;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
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\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;

#[Route(path: '/{_locale}/notification')]
class NotificationController extends AbstractController
{
    public function __construct(private readonly EntityManagerInterface $em, private readonly LoggerInterface $chillLogger, private readonly LoggerInterface $logger, private readonly ChillSecurity $security, private readonly NotificationRepository $notificationRepository, private readonly NotificationHandlerManager $notificationHandlerManager, private readonly PaginatorFactory $paginatorFactory, private readonly TranslatorInterface $translator, private readonly UserRepository $userRepository, private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry) {}

    #[Route(path: '/create', name: 'chill_main_notification_create')]
    public function createAction(Request $request): Response
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        if (!$request->query->has('entityClass')) {
            throw new BadRequestHttpException('Missing entityClass parameter');
        }

        if (!$request->query->has('entityId')) {
            throw new BadRequestHttpException('missing entityId parameter');
        }

        $notification = new Notification();
        $notification
            ->setRelatedEntityClass($request->query->get('entityClass'))
            ->setRelatedEntityId($request->query->getInt('entityId'))
            ->setSender($this->security->getUser())
            ->setType(NotificationByUserFlagProvider::FLAG);

        $tos = $request->query->all('tos');

        foreach ($tos as $toId) {
            if (null === $to = $this->userRepository->find($toId)) {
                throw new NotFoundHttpException("user with id {$toId} is not found");
            }
            $notification->addAddressee($to);
        }

        try {
            $handler = $this->notificationHandlerManager->getHandler($notification);
        } catch (NotificationHandlerNotFound) {
            throw new BadRequestHttpException('no handler for this notification');
        }

        $form = $this->createForm(NotificationType::class, $notification);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->em->persist($notification);
            $this->em->flush();

            $this->addFlash('success', $this->translator->trans('notification.Notification created'));

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

            return $this->redirectToRoute('chill_main_homepage');
        }

        return $this->render('@ChillMain/Notification/create.html.twig', [
            'form' => $form->createView(),
            'handler' => $handler,
            'notification' => $notification,
        ]);
    }

    #[Route(path: '/{id}/edit', name: 'chill_main_notification_edit')]
    public function editAction(Notification $notification, Request $request): Response
    {
        $this->denyAccessUnlessGranted(NotificationVoter::NOTIFICATION_UPDATE, $notification);

        $form = $this->createForm(NotificationType::class, $notification);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->em->flush();

            $this->addFlash('success', $this->translator->trans('notification.Notification updated'));

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

            return $this->redirectToRoute('chill_main_notification_my');
        }

        return $this->render('@ChillMain/Notification/edit.html.twig', [
            'form' => $form->createView(),
            'handler' => $this->notificationHandlerManager->getHandler($notification),
            'notification' => $notification,
        ]);
    }

    #[Route(path: '/{id}/access_key', name: 'chill_main_notification_grant_access_by_access_key')]
    public function getAccessByAccessKey(Notification $notification, Request $request): Response
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        foreach (['accessKey'/* , 'email' */] as $param) {
            if (!$request->query->has($param)) {
                throw new BadRequestHttpException("Missing {$param} parameter");
            }
        }

        if ($notification->getAccessKey() !== $request->query->getAlnum('accessKey')) {
            throw new AccessDeniedHttpException('access key is invalid');
        }

        /*
         desactivated due to escaped '&' in email links
        if (!in_array($request->query->get('email'), $notification->getAddressesEmails(), true)) {
            return (new Response('The email address is no more associated with this notification'))
                ->setStatusCode(Response::HTTP_FORBIDDEN);
        }
         */

        $notification->addAddressee($this->security->getUser());

        $this->managerRegistry->getManager()->flush();

        $logMsg = '[Notification] a user is granted access to notification trough an access key';
        $context = [
            'notificationId' => $notification->getId(),
            'email' => $request->query->get('email'),
            'user' => $this->security->getUser()->getId(),
        ];

        $this->logger->info($logMsg, $context);
        $this->chillLogger->info($logMsg, $context);

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

    #[Route(path: '/inbox', name: 'chill_main_notification_my')]
    public function inboxAction(): Response
    {
        $this->denyAccessUnlessGranted('ROLE_USER');
        $currentUser = $this->security->getUser();

        $notificationsNbr = $this->notificationRepository->countAllForAttendee($currentUser);
        $paginator = $this->paginatorFactory->create($notificationsNbr);

        $notifications = $this->notificationRepository->findAllForAttendee(
            $currentUser,
            $paginator->getItemsPerPage(),
            $paginator->getCurrentPage()->getFirstItemNumber()
        );

        return $this->render('@ChillMain/Notification/list.html.twig', [
            'datas' => $this->itemsForTemplate($notifications),
            'notifications' => $notifications,
            'paginator' => $paginator,
            'step' => 'inbox',
            'unreads' => $this->countUnread(),
        ]);
    }

    #[Route(path: '/sent', name: 'chill_main_notification_sent')]
    public function sentAction(): Response
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $currentUser = $this->security->getUser();

        $notificationsNbr = $this->notificationRepository->countAllForSender($currentUser);
        $paginator = $this->paginatorFactory->create($notificationsNbr);

        $notifications = $this->notificationRepository->findAllForSender(
            $currentUser,
            $limit = $paginator->getItemsPerPage(),
            $offset = $paginator->getCurrentPage()->getFirstItemNumber()
        );

        return $this->render('@ChillMain/Notification/list.html.twig', [
            'datas' => $this->itemsForTemplate($notifications),
            'notifications' => $notifications,
            'paginator' => $paginator,
            'step' => 'sent',
            'unreads' => $this->countUnread(),
        ]);
    }

    #[Route(path: '/{id}/show', name: 'chill_main_notification_show')]
    public function showAction(Notification $notification, Request $request): Response
    {
        $this->denyAccessUnlessGranted(NotificationVoter::NOTIFICATION_SEE, $notification);

        if ($request->query->has('edit')) {
            $commentId = $request->query->getInt('edit');
            $editedComment = $notification->getComments()->filter(static fn (NotificationComment $c) => $c->getId() === $commentId)->first();

            if (false === $editedComment) {
                throw $this->createNotFoundException("Comment with id {$commentId} does not exists nor belong to this notification");
            }

            $this->denyAccessUnlessGranted(NotificationVoter::COMMENT_EDIT, $editedComment);

            $editedCommentForm = $this->createForm(NotificationCommentType::class, $editedComment);

            if (Request::METHOD_POST === $request->getMethod() && 'edit' === $request->request->get('form')) {
                $editedCommentForm->handleRequest($request);

                if ($editedCommentForm->isSubmitted() && $editedCommentForm->isValid()) {
                    $this->em->flush();

                    $this->addFlash('success', $this->translator->trans('notification.comment_updated'));

                    return $this->redirectToRoute('chill_main_notification_show', [
                        'id' => $notification->getId(),
                        '_fragment' => 'comment-'.$commentId,
                    ]);
                }

                if ($editedCommentForm->isSubmitted() && !$editedCommentForm->isValid()) {
                    $this->addFlash('error', $this->translator->trans('This form contains errors'));
                }
            }
        }

        if ($this->isGranted(NotificationVoter::COMMENT_ADD, $notification)) {
            $appendComment = new NotificationComment();
            $appendCommentForm = $this->createForm(NotificationCommentType::class, $appendComment);

            if (Request::METHOD_POST === $request->getMethod() && 'append' === $request->request->get('form')) {
                $appendCommentForm->handleRequest($request);

                if ($appendCommentForm->isSubmitted() && $appendCommentForm->isValid()) {
                    $notification->addComment($appendComment);
                    $this->em->persist($appendComment);
                    $this->em->flush();

                    $this->addFlash('success', $this->translator->trans('notification.comment_appended'));

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

                if ($appendCommentForm->isSubmitted() && !$appendCommentForm->isValid()) {
                    $this->addFlash('error', $this->translator->trans('This form contains errors'));
                }
            }
        }

        $response = $this->render('@ChillMain/Notification/show.html.twig', [
            'notification' => $notification,
            'handler' => $this->notificationHandlerManager->getHandler($notification),
            'appendCommentForm' => isset($appendCommentForm) ? $appendCommentForm->createView() : null,
            'editedCommentForm' => isset($editedCommentForm) ? $editedCommentForm->createView() : null,
            'editedCommentId' => $commentId ?? null,
        ]);

        // we mark the notification as read after having computed the response
        if (!$notification->isReadBy($this->security->getUser())) {
            $notification->markAsReadBy($this->security->getUser());
            $this->em->flush();
        }

        return $response;
    }

    private function countUnread(): array
    {
        return [
            'sent' => $this->notificationRepository->countUnreadByUserWhereSender($this->security->getUser()),
            'inbox' => $this->notificationRepository->countUnreadByUserWhereAddressee($this->security->getUser()),
        ];
    }

    private function itemsForTemplate(array $notifications): array
    {
        $templateData = [];

        foreach ($notifications as $notification) {
            $templateData[] = [
                'template' => $this->notificationHandlerManager->getTemplate($notification),
                'template_data' => $this->notificationHandlerManager->getTemplateData($notification),
                'handler' => $this->notificationHandlerManager->getHandler($notification),
                'notification' => $notification,
            ];
        }

        return $templateData;
    }
}
