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

use Chill\MainBundle\Entity\PostalCode;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\PickPostalCodeType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Constraints\NotIdenticalTo;
use Symfony\Component\Validator\Constraints\NotNull;

class ReassignAccompanyingPeriodController extends AbstractController
{
    public function __construct(private readonly AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository, private readonly UserRepository $userRepository, private readonly AccompanyingPeriodRepository $courseRepository, private readonly \Twig\Environment $engine, private readonly FormFactoryInterface $formFactory, private readonly PaginatorFactory $paginatorFactory, private readonly Security $security, private readonly UserRender $userRender, private readonly EntityManagerInterface $em) {}

    #[Route(path: '/{_locale}/person/accompanying-periods/reassign', name: 'chill_course_list_reassign')]
    public function listAction(Request $request): Response
    {
        if (!$this->security->isGranted(AccompanyingPeriodVoter::REASSIGN_BULK)) {
            throw new AccessDeniedHttpException('no right to reassign bulk');
        }

        $form = $this->buildFilterForm();

        $form->handleRequest($request);

        $userFrom = $form['user']->getData();
        $postalCodes = $form['postal_code']->getData() instanceof PostalCode ? [$form['postal_code']->getData()] : [];

        $total = $this->accompanyingPeriodACLAwareRepository->countByUserAndPostalCodesOpenedAccompanyingPeriod($userFrom, $postalCodes);
        $paginator = $this->paginatorFactory->create($total);
        $paginator->setItemsPerPage(100);
        $periods = $this->accompanyingPeriodACLAwareRepository
            ->findByUserAndPostalCodesOpenedAccompanyingPeriod(
                $userFrom,
                $postalCodes,
                ['openingDate' => 'ASC'],
                $paginator->getItemsPerPage(),
                $paginator->getCurrentPageFirstItemNumber()
            );

        $periodIds = [];

        foreach ($periods as $period) {
            $periodIds[] = $period->getId();
        }

        // Create an array of period id's to pass into assignForm hiddenfield
        $assignForm = $this->buildReassignForm($periodIds, $userFrom);

        $assignForm->handleRequest($request);

        if ($assignForm->isSubmitted() && $assignForm->isValid()) {
            $assignPeriodIds = json_decode((string) $assignForm->get('periods')->getData(), true, 512, JSON_THROW_ON_ERROR);
            /** @var User $userTo */
            $userTo = $assignForm->get('userTo')->getData();
            $userFrom = $assignForm->get('userFrom')->getData();

            foreach ($assignPeriodIds as $periodId) {
                $period = $this->courseRepository->find($periodId);

                if ($period->getUser() === $userFrom) {
                    $period->setUser($userTo, true);

                    if (null !== $userTo->getUserJob() && $period->getJob() !== $userTo->getUserJob()) {
                        $period->setJob($userTo->getUserJob());
                    }
                }
            }

            $this->em->flush();

            // redirect to the first page
            return $this->redirectToRoute('chill_course_list_reassign', $request->query->all());
        }

        return new Response(
            $this->engine->render('@ChillPerson/AccompanyingPeriod/reassign_list.html.twig', [
                'assignForm' => $assignForm->createView(),
                'form' => $form->createView(),
                'paginator' => $paginator,
                'periods' => $periods,
                'userFrom' => $userFrom,
            ])
        );
    }

    private function buildFilterForm(): FormInterface
    {
        $data = [
            'user' => null,
            'postal_code' => null,
        ];

        $builder = $this->formFactory->createBuilder(FormType::class, $data, [
            'method' => 'get', 'csrf_protection' => false, ]);

        $builder
            ->add('user', PickUserDynamicType::class, [
                'multiple' => false,
                'label' => 'reassign.Current user',
                'required' => false,
                'help' => 'reassign.Choose a user and click on "Filter" to apply',
            ])
            ->add('postal_code', PickPostalCodeType::class, [
                'label' => 'reassign.Filter by postal code',
                'required' => false,
                'help' => 'reassign.Filter course which are located inside a postal code',
            ]);

        return $builder->getForm();
    }

    private function buildReassignForm(array $periodIds, ?User $userFrom = null): FormInterface
    {
        $defaultData = [
            'userFrom' => $userFrom,
            'periods' => json_encode($periodIds, JSON_THROW_ON_ERROR),
        ];

        $builder = $this->formFactory->createNamedBuilder('reassign', FormType::class, $defaultData);

        if (null !== $userFrom) {
            $constraints = [new NotIdenticalTo(['value' => $userFrom])];
        } else {
            $constraints = [];
        }

        $builder
            ->add('periods', HiddenType::class)
            ->add('userFrom', HiddenType::class)
            ->add('userTo', PickUserDynamicType::class, [
                'multiple' => false,
                'label' => 'reassign.Next user',
                'required' => true,
                'help' => 'reassign.All periods on this list will be reassigned to this user, excepted the one you manually reassigned before',
                'constraints' => [new NotNull()],
            ]);

        $builder->get('userFrom')->addModelTransformer(new CallbackTransformer(
            static function (?User $user) {
                if (null === $user) {
                    return '';
                }

                return $user->getId();
            },
            function (?string $id) {
                if (null === $id) {
                    return null;
                }

                if (!\is_int((int) $id)) {
                    throw new TransformationFailedException('the user id is not a numeric');
                }

                return $this->userRepository->find((int) $id);
            }
        ));

        return $builder->getForm();
    }
}
