<?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\PermissionsGroup;
use Chill\MainBundle\Entity\RoleScope;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Form\PermissionsGroupType;
use Chill\MainBundle\Form\Type\ComposedRoleScopeType;
use Chill\MainBundle\Repository\PermissionsGroupRepository;
use Chill\MainBundle\Repository\RoleScopeRepository;
use Chill\MainBundle\Security\RoleProvider;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
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\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * Class PermissionsGroupController.
 */
final class PermissionsGroupController extends AbstractController
{
    /**
     * PermissionsGroupController constructor.
     */
    public function __construct(
        private readonly TranslatableStringHelper $translatableStringHelper,
        private readonly RoleProvider $roleProvider,
        private readonly RoleHierarchyInterface $roleHierarchy,
        private readonly TranslatorInterface $translator,
        private readonly ValidatorInterface $validator,
        private readonly EntityManagerInterface $em,
        private readonly PermissionsGroupRepository $permissionsGroupRepository,
        private readonly RoleScopeRepository $roleScopeRepository,
    ) {}

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/permissionsgroup/{id}/add_link_role_scope', name: 'admin_permissionsgroup_add_role_scope', methods: ['POST'])]
    public function addLinkRoleScopeAction(Request $request, int $id): Response
    {
        $permissionsGroup = $this->permissionsGroupRepository->find($id);

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

        $form = $this->createAddRoleScopeForm($permissionsGroup);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $roleScope = $this->getPersistentRoleScopeBy(
                $form['composed_role_scope']->getData()->getRole(),
                $form['composed_role_scope']->getData()->getScope()
            );

            $permissionsGroup->addRoleScope($roleScope);
            $violations = $this->validator->validate($permissionsGroup);

            if (0 === $violations->count()) {
                $this->em->flush();

                $this->addFlash(
                    'success',
                    $this->translator->trans('The permissions have been added')
                );

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

            foreach ($violations as $error) {
                $this->addFlash('error', $error->getMessage());
            }
        } else {
            foreach ($form->getErrors() as $error) {
                $this->addFlash('error', $error->getMessage());
            }
        }

        $editForm = $this->createEditForm($permissionsGroup);

        $deleteRoleScopesForm = [];

        foreach ($permissionsGroup->getRoleScopes() as $roleScope) {
            $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm(
                $permissionsGroup,
                $roleScope
            );
        }

        $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup);

        // sort role scope by title

        $roleProvider = $this->roleProvider;
        $roleScopesSorted = [];

        foreach ($permissionsGroup->getRoleScopes()->toArray() as $roleScope) {
            /** @var RoleScope $roleScope */
            $title = $roleProvider->getRoleTitle($roleScope->getRole());
            $roleScopesSorted[$title][] = $roleScope;
        }
        ksort($roleScopesSorted);

        return $this->render('@ChillMain/PermissionsGroup/edit.html.twig', [
            'entity' => $permissionsGroup,
            'edit_form' => $editForm->createView(),
            'role_scopes_sorted' => $roleScopesSorted,
            'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()),
            'delete_role_scopes_form' => array_map(static fn ($form) => $form->createView(), $deleteRoleScopesForm),
            'add_role_scopes_form' => $addRoleScopesForm->createView(),
        ]);
    }

    /**
     * Creates a new PermissionsGroup entity.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/permissionsgroup/create', name: 'admin_permissionsgroup_create', methods: ['POST'])]
    public function createAction(Request $request): Response
    {
        $permissionsGroup = new PermissionsGroup();
        $form = $this->createCreateForm($permissionsGroup);
        $form->handleRequest($request);

        if ($form->isValid()) {
            $this->em->persist($permissionsGroup);
            $this->em->flush();

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

        return $this->render('@ChillMain/PermissionsGroup/new.html.twig', [
            'entity' => $permissionsGroup,
            'form' => $form->createView(),
        ]);
    }

    /**
     * remove an association between permissionsGroup and roleScope.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/permissionsgroup/{pgid}/delete_link_role_scope/{rsid}', name: 'admin_permissionsgroup_delete_role_scope', methods: ['POST'])]
    public function deleteLinkRoleScopeAction(int $pgid, int $rsid): Response
    {
        $permissionsGroup = $this->permissionsGroupRepository->find($pgid);
        $roleScope = $this->roleScopeRepository->find($rsid);

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

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

        try {
            $permissionsGroup->removeRoleScope($roleScope);
        } catch (\RuntimeException) {
            $this->addFlash(
                'notice',
                $this->translator->trans("The role '%role%' and circle "
                        ."'%scope%' is not associated with this permission group", [
                            '%role%' => $this->translator->trans($roleScope->getRole()),
                            '%scope%' => $this->translatableStringHelper
                                ->localize($roleScope->getScope()->getName()),
                        ])
            );

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

        $this->em->flush();

        if (null !== $roleScope->getScope()) {
            $this->addFlash(
                'notice',
                $this->translator->trans("The role '%role%' on circle "
                        ."'%scope%' has been removed", [
                            '%role%' => $this->translator->trans($roleScope->getRole()),
                            '%scope%' => $this->translatableStringHelper
                                ->localize($roleScope->getScope()->getName()),
                        ])
            );
        } else {
            $this->addFlash(
                'notice',
                $this->translator->trans("The role '%role%' has been removed", [
                    '%role%' => $this->translator->trans($roleScope->getRole()),
                ])
            );
        }

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

    /**
     * Displays a form to edit an existing PermissionsGroup entity.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/permissionsgroup/{id}/edit', name: 'admin_permissionsgroup_edit')]
    public function editAction(int $id): Response
    {
        $permissionsGroup = $this->permissionsGroupRepository->find($id);

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

        // create all the forms
        $editForm = $this->createEditForm($permissionsGroup);

        $deleteRoleScopesForm = [];

        foreach ($permissionsGroup->getRoleScopes() as $roleScope) {
            $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm(
                $permissionsGroup,
                $roleScope
            );
        }

        $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup);

        // sort role scope by title
        $roleProvider = $this->roleProvider;
        $roleScopesSorted = [];

        foreach ($permissionsGroup->getRoleScopes()->toArray() as $roleScope) {
            /** @var RoleScope $roleScope */
            $title = $roleProvider->getRoleTitle($roleScope->getRole());
            $roleScopesSorted[$title][] = $roleScope;
        }
        ksort($roleScopesSorted);

        return $this->render('@ChillMain/PermissionsGroup/edit.html.twig', [
            'entity' => $permissionsGroup,
            'role_scopes_sorted' => $roleScopesSorted,
            'edit_form' => $editForm->createView(),
            'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()),
            'delete_role_scopes_form' => array_map(static fn ($form) => $form->createView(), $deleteRoleScopesForm),
            'add_role_scopes_form' => $addRoleScopesForm->createView(),
        ]);
    }

    /**
     * Lists all PermissionsGroup entities.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/permissionsgroup/', name: 'admin_permissionsgroup')]
    public function indexAction(): Response
    {
        $entities = $this->permissionsGroupRepository->findAllOrderedAlphabetically();

        return $this->render('@ChillMain/PermissionsGroup/index.html.twig', [
            'entities' => $entities,
        ]);
    }

    /**
     * Displays a form to create a new PermissionsGroup entity.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/permissionsgroup/new', name: 'admin_permissionsgroup_new')]
    public function newAction(): Response
    {
        $permissionsGroup = new PermissionsGroup();
        $form = $this->createCreateForm($permissionsGroup);

        return $this->render('@ChillMain/PermissionsGroup/new.html.twig', [
            'entity' => $permissionsGroup,
            'form' => $form->createView(),
        ]);
    }

    /**
     * Finds and displays a PermissionsGroup entity.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/permissionsgroup/{id}/show', name: 'admin_permissionsgroup_show')]
    public function showAction(int $id): Response
    {
        $permissionsGroup = $this->permissionsGroupRepository->find($id);

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

        $translatableStringHelper = $this->translatableStringHelper;
        $roleScopes = $permissionsGroup->getRoleScopes()->toArray();

        // sort $roleScopes by name
        usort(
            $roleScopes,
            static function (RoleScope $a, RoleScope $b) use ($translatableStringHelper) {
                if (null === $a->getScope()) {
                    return 1;
                }

                if (null === $b->getScope()) {
                    return +1;
                }

                return strcmp(
                    (string) $translatableStringHelper->localize($a->getScope()->getName()),
                    (string) $translatableStringHelper->localize($b->getScope()->getName())
                );
            }
        );

        // sort role scope by title
        $roleProvider = $this->roleProvider;
        $roleScopesSorted = [];

        foreach ($roleScopes as $roleScope) {
            /** @var RoleScope $roleScope */
            $title = $roleProvider->getRoleTitle($roleScope->getRole());
            $roleScopesSorted[$title][] = $roleScope;
        }
        ksort($roleScopesSorted);

        return $this->render('@ChillMain/PermissionsGroup/show.html.twig', [
            'entity' => $permissionsGroup,
            'role_scopes_sorted' => $roleScopesSorted,
            'expanded_roles' => $this->getExpandedRoles($roleScopes),
        ]);
    }

    /**
     * Edits an existing PermissionsGroup entity.
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/permissionsgroup/{id}/update', name: 'admin_permissionsgroup_update', methods: ['POST', 'PUT'])]
    public function updateAction(Request $request, int $id): Response
    {
        $permissionsGroup = $this->permissionsGroupRepository
            ->find($id);

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

        $editForm = $this->createEditForm($permissionsGroup);
        $editForm->handleRequest($request);

        if ($editForm->isValid()) {
            $this->em->flush();

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

        $deleteRoleScopesForm = [];

        foreach ($permissionsGroup->getRoleScopes() as $roleScope) {
            $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm(
                $permissionsGroup,
                $roleScope
            );
        }

        $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup);

        // sort role scope by title
        $roleProvider = $this->roleProvider;
        $roleScopesSorted = [];

        foreach ($permissionsGroup->getRoleScopes()->toArray() as $roleScope) {
            /** @var RoleScope $roleScope */
            $title = $roleProvider->getRoleTitle($roleScope->getRole());
            $roleScopesSorted[$title][] = $roleScope;
        }
        ksort($roleScopesSorted);

        return $this->render('@ChillMain/PermissionsGroup/edit.html.twig', [
            'entity' => $permissionsGroup,
            'role_scopes_sorted' => $roleScopesSorted,
            'edit_form' => $editForm->createView(),
            'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()),
            'delete_role_scopes_form' => array_map(static fn ($form) => $form->createView(), $deleteRoleScopesForm),
            'add_role_scopes_form' => $addRoleScopesForm->createView(),
        ]);
    }

    /**
     * get a role scope by his parameters. The role scope is persisted if it
     * doesn't exist in database.
     */
    protected function getPersistentRoleScopeBy(string $role, ?Scope $scope = null): RoleScope
    {
        $roleScope = $this->roleScopeRepository
            ->findOneBy(['role' => $role, 'scope' => $scope]);

        if (null === $roleScope) {
            $roleScope = (new RoleScope())
                ->setRole($role)
                ->setScope($scope);

            $this->em->persist($roleScope);
        }

        return $roleScope;
    }

    /**
     * creates a form to add a role scope to permissionsgroup.
     */
    private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup): FormInterface
    {
        return $this->createFormBuilder()
            ->setAction($this->generateUrl(
                'admin_permissionsgroup_add_role_scope',
                ['id' => $permissionsGroup->getId()]
            ))
            ->add('composed_role_scope', ComposedRoleScopeType::class)
            ->add('submit', SubmitType::class, ['label' => 'Add permission'])
            ->getForm();
    }

    /**
     * Creates a form to create a PermissionsGroup entity.
     *
     * @param PermissionsGroup $permissionsGroup The entity
     */
    private function createCreateForm(PermissionsGroup $permissionsGroup): FormInterface
    {
        $form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [
            'action' => $this->generateUrl('admin_permissionsgroup_create'),
            'method' => 'POST',
        ]);

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

        return $form;
    }

    /**
     * Creates a form to delete a link to roleScope.
     */
    private function createDeleteRoleScopeForm(
        PermissionsGroup $permissionsGroup,
        RoleScope $roleScope,
    ): FormInterface {
        return $this->createFormBuilder()
            ->setAction($this->generateUrl(
                'admin_permissionsgroup_delete_role_scope',
                ['pgid' => $permissionsGroup->getId(), 'rsid' => $roleScope->getId()]
            ))
            ->setMethod('DELETE')
            ->add('submit', SubmitType::class, ['label' => 'Delete'])
            ->getForm();
    }

    /**
     * Creates a form to edit a PermissionsGroup entity.
     */
    private function createEditForm(PermissionsGroup $permissionsGroup): FormInterface
    {
        $form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [
            'action' => $this->generateUrl('admin_permissionsgroup_update', ['id' => $permissionsGroup->getId()]),
            'method' => 'PUT',
        ]);

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

        return $form;
    }

    /**
     * expand roleScopes to be easily shown in template.
     */
    private function getExpandedRoles(array $roleScopes): array
    {
        $expandedRoles = [];

        foreach ($roleScopes as $roleScope) {
            if (!\array_key_exists($roleScope->getRole(), $expandedRoles)) {
                $expandedRoles[$roleScope->getRole()] =
                      array_map(
                          static fn ($role) => $role,
                          $this->roleHierarchy
                              ->getReachableRoleNames(
                                  [$roleScope->getRole()]
                              )
                      );
            }
        }

        return $expandedRoles;
    }
}
