<?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\CRUD\Controller\CRUDController;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\GroupCenter;
use Chill\MainBundle\Entity\PermissionsGroup;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ComposedGroupCenterType;
use Chill\MainBundle\Form\UserCurrentLocationType;
use Chill\MainBundle\Form\UserPasswordType;
use Chill\MainBundle\Form\UserType;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Security\ChillSecurity;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

class UserController extends CRUDController
{
    final public const FORM_GROUP_CENTER_COMPOSED = 'composed_groupcenter';

    public function __construct(
        private readonly LoggerInterface $logger,
        private readonly ValidatorInterface $validator,
        private readonly \Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface $passwordEncoder,
        private readonly UserRepository $userRepository,
        protected ParameterBagInterface $parameterBag,
        private readonly TranslatorInterface $translator,
        private readonly ChillSecurity $security,
        private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
    ) {}

    #[Route(path: '/{_locale}/admin/main/user/{uid}/add_link_groupcenter', name: 'admin_user_add_groupcenter')]
    public function addLinkGroupCenterAction(Request $request, mixed $uid): Response
    {
        $em = $this->managerRegistry->getManager();

        $user = $em->getRepository(User::class)->find($uid);

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

        $form = $this->createAddLinkGroupCenterForm($user, $request);
        $form->handleRequest($request);

        if ($form->isValid()) {

            $formData = $form[self::FORM_GROUP_CENTER_COMPOSED]->getData();
            $selectedCenters = $formData['center'];

            foreach ($selectedCenters as $center) {
                $groupCenter = $this->getPersistedGroupCenter($center, $formData['permissionsgroup']);
                $user->addGroupCenter($groupCenter);
            }

            if (0 === $this->validator->validate($user)->count()) {
                $em->flush();

                $this->addFlash('success', $this->translator->trans('The '
                    .'permissions have been successfully added to the user'));

                $returnPathParams = $request->query->has('returnPath') ?
                    ['returnPath' => $request->query->get('returnPath')] : [];

                return $this->redirectToRoute('chill_crud_admin_user_edit', array_merge(['id' => $uid], $returnPathParams));
            }

            foreach ($this->validator->validate($user) as $error) {
                $this->addFlash('error', $error->getMessage());
            }
        }

        return $this->render('@ChillMain/User/edit.html.twig', [
            'entity' => $user,
            'access_permissions_group_list' => $this->parameterBag->get('chill_main.access_permissions_group_list'),
            'edit_form' => $this->createEditForm($user)->createView(),
            'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user, $request)->createView(),
            'delete_groupcenter_form' => array_map(
                static fn (Form $form) => $form->createView(),
                iterator_to_array($this->getDeleteLinkGroupCenterByUser($user, $request), true)
            ),
        ]);
    }

    #[Route(path: '/{_locale}/admin/main/user/{uid}/delete_link_groupcenter/{gcid}', name: 'admin_user_delete_groupcenter')]
    public function deleteLinkGroupCenterAction(mixed $uid, mixed $gcid, Request $request): RedirectResponse
    {
        $em = $this->managerRegistry->getManager();

        $user = $em->getRepository(User::class)->find($uid);

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

        $groupCenter = $em->getRepository(GroupCenter::class)
            ->find($gcid);

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

        try {
            $user->removeGroupCenter($groupCenter);
        } catch (\RuntimeException $ex) {
            $this->addFlash('error', $this->translator->trans($ex->getMessage()));

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

        $em->flush();

        $this->addFlash('success', $this->translator
            ->trans('The permissions where removed.'));

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

    public function edit(Request $request, $id): Response
    {
        $action = 'edit';
        $entity = $this->getEntity($action, $id, $request);

        if (null === $entity) {
            throw $this->createNotFoundException(sprintf('The %s with id %s is not found', $this->getCrudName(), $id));
        }

        $response = $this->checkACL($action, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        $response = $this->onPostCheckACL($action, $request, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        $form = $this->createFormFor($action, $entity);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->onFormValid($action, $entity, $form, $request);
            $em = $this->managerRegistry->getManager();

            $this->onPreFlush($action, $entity, $form, $request);
            $em->flush();
            $this->onPostFlush($action, $entity, $form, $request);

            $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));

            $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);

            if ($result instanceof Response) {
                return $result;
            }

            return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index');
        }

        if ($form->isSubmitted()) {
            $this->addFlash('error', $this->generateFormErrorMessage($action, $form));
        }

        $defaultTemplateParameters = [
            'form' => $form->createView(),
            'entity' => $entity,
            'crud_name' => $this->getCrudName(),
            'access_permissions_group_list' => $this->parameterBag->get('chill_main.access_permissions_group_list'),
        ];

        return $this->render(
            $this->getTemplateFor($action, $entity, $request),
            $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
        );
    }

    /**
     * Displays a form to edit the user current location.
     */
    #[Route(path: '/{_locale}/main/user/current-location/edit', name: 'chill_main_user_currentlocation_edit')]
    public function editCurrentLocationAction(Request $request)
    {
        $user = $this->security->getUser();
        $form = $this->createForm(UserCurrentLocationType::class, $user)
            ->add('submit', SubmitType::class, ['label' => 'Save'])
            ->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $currentLocation = $form->get('currentLocation')->getData();

            $user->setCurrentLocation($currentLocation);

            $this->managerRegistry->getManager()->flush();
            $this->addFlash('success', $this->translator->trans('Current location successfully updated'));

            return $this->redirect(
                $request->query->has('returnPath') ? $request->query->get('returnPath') :
                $this->generateUrl('chill_main_homepage')
            );
        }

        return $this->render('@ChillMain/User/edit_current_location.html.twig', [
            'entity' => $user,
            'edit_form' => $form->createView(),
        ]);
    }

    /**
     * Displays a form to edit the user password.
     */
    #[Route(path: '/{_locale}/admin/user/{id}/edit_password', name: 'admin_user_edit_password')]
    public function editPasswordAction(User $user, Request $request)
    {
        $editForm = $this->createEditPasswordForm($user);
        $editForm->handleRequest($request);

        if ($editForm->isSubmitted() && $editForm->isValid()) {
            $password = $editForm->get('new_password')->getData();

            // logging for prod
            $this->logger->info('update password for an user', [
                'by' => $this->getUser()->getUsername(),
                'user' => $user->getUsername(),
            ]);

            $user->setPassword($this->passwordEncoder->hashPassword($user, $password));

            $this->managerRegistry->getManager()->flush();
            $this->addFlash('success', $this->translator->trans('Password successfully updated!'));

            return $this->redirect(
                $request->query->has('returnPath') ? $request->query->get('returnPath') :
                $this->generateUrl('chill_crud_admin_user_edit', ['id' => $user->getId()])
            );
        }

        return $this->render('@ChillMain/User/edit_password.html.twig', [
            'entity' => $user,
            'edit_form' => $editForm->createView(),
        ]);
    }

    protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper
    {
        return $this->getFilterOrderHelperFactory()
            ->create(self::class)
            ->addSearchBox(['label'])
            ->addCheckbox('activeFilter', [true => 'Active', false => 'Inactive'], ['Active'])
            ->build();
    }

    protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int
    {
        if (!$filterOrder instanceof FilterOrderHelper) {
            return parent::countEntities($action, $request, $filterOrder);
        }

        return $this->userRepository->countFilteredUsers($filterOrder->getQueryString(), $filterOrder->getCheckboxData('activeFilter'));
    }

    protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface
    {
        // for "new", add special config
        if ('new' === $action) {
            return $this->createForm(UserType::class, $entity, [
                'is_creation' => true,
            ]);
        }

        // default behaviour
        return parent::createFormFor($action, $entity, $formClass, $formOptions);
    }

    protected function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = [])
    {
        // add mini-forms for edit action
        if ('edit' === $action) {
            return array_merge(
                $defaultTemplateParameters,
                [
                    'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($entity, $request)->createView(),
                    'delete_groupcenter_form' => array_map(
                        static fn (Form $form) => $form->createView(),
                        iterator_to_array($this->getDeleteLinkGroupCenterByUser($entity, $request), true)
                    ),
                ]
            );
        }
        if ('index' === $action) {
            return array_merge(
                ['allow_change_password' => $this->parameterBag->get('chill_main.access_user_change_password')],
                $defaultTemplateParameters
            );
        }

        // default behaviour
        return parent::generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters);
    }

    protected function getQueryResult(
        string $action,
        Request $request,
        int $totalItems,
        PaginatorInterface $paginator,
        ?FilterOrderHelper $filterOrder = null,
    ) {
        if (0 === $totalItems) {
            return [];
        }

        if (!$filterOrder instanceof FilterOrderHelper) {
            return parent::getQueryResult($action, $request, $totalItems, $paginator, $filterOrder);
        }

        $queryString = $filterOrder->getQueryString();
        $activeFilter = $filterOrder->getCheckboxData('activeFilter');
        $nb = $this->userRepository->countFilteredUsers($queryString, $activeFilter);

        $paginator = $this->getPaginatorFactory()->create($nb);

        return $this->userRepository->findFilteredUsers($queryString, $activeFilter, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage());
    }

    protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request)
    {
        // for "new", encode the password
        if ('new' === $action && $this->parameterBag->get('chill_main.access_user_change_password')) {
            $entity->setPassword($this->passwordEncoder
                ->hashPassword($entity, $form['plainPassword']->getData()));
        }

        // default behaviour
        parent::onPrePersist($action, $entity, $form, $request);
    }

    protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
    {
        $query
            ->addOrderBy('e.usernameCanonical', 'ASC');

        return parent::orderQuery($action, $query, $request, $paginator);
    }

    /**
     * Create a form to add a link to a groupcenter.
     */
    private function createAddLinkGroupCenterForm(User $user, Request $request): FormInterface
    {
        $returnPathParams = $request->query->has('returnPath') ? ['returnPath' => $request->query->get('returnPath')] : [];

        return $this->createFormBuilder()
            ->setAction(
                $this->generateUrl(
                    'admin_user_add_groupcenter',
                    array_merge($returnPathParams, ['uid' => $user->getId()])
                )
            )
            ->setMethod('POST')
            ->add(self::FORM_GROUP_CENTER_COMPOSED, ComposedGroupCenterType::class)
            ->add('submit', SubmitType::class, ['label' => 'Add a new groupCenter'])
            ->getForm();
    }

    /**
     * Creates a form to delete a link to a GroupCenter.
     */
    private function createDeleteLinkGroupCenterForm(User $user, GroupCenter $groupCenter, mixed $request): FormInterface
    {
        $returnPathParams = $request->query->has('returnPath') ? ['returnPath' => $request->query->get('returnPath')] : [];

        return $this->createFormBuilder()
            ->setAction(
                $this->generateUrl(
                    'admin_user_delete_groupcenter',
                    array_merge($returnPathParams, ['uid' => $user->getId(), 'gcid' => $groupCenter->getId()])
                )
            )
            ->setMethod('DELETE')
            ->add('submit', SubmitType::class, ['label' => 'Delete'])
            ->getForm();
    }

    private function createEditPasswordForm(User $user): FormInterface
    {
        return $this->createForm(
            UserPasswordType::class,
            null,
            [
                'user' => $user,
            ]
        )
            ->add('submit', SubmitType::class, ['label' => 'Change password'])
            ->remove('actual_password');
    }

    private function getDeleteLinkGroupCenterByUser(User $user, Request $request)
    {
        foreach ($user->getGroupCenters() as $groupCenter) {
            yield $groupCenter->getId() => $this->createDeleteLinkGroupCenterForm($user, $groupCenter, $request);
        }
    }

    private function getPersistedGroupCenter(Center $center, PermissionsGroup $permissionsGroup)
    {
        $em = $this->managerRegistry->getManager();

        $groupCenterManaged = $em->getRepository(GroupCenter::class)
            ->findOneBy([
                'center' => $center,
                'permissionsGroup' => $permissionsGroup,
            ]);

        if (!$groupCenterManaged) {
            $groupCenter = new GroupCenter();
            $groupCenter->setCenter($center);
            $groupCenter->setPermissionsGroup($permissionsGroup);

            $em->persist($groupCenter);

            return $groupCenter;
        }

        return $groupCenterManaged;
    }
}
