<?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\Form;

use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\DataTransformer\PersonToIdTransformer;
use Chill\PersonBundle\Form\Type\PickPersonType;
use Chill\TaskBundle\Entity\SingleTask;
use Chill\TaskBundle\Repository\SingleTaskRepository;
use Chill\TaskBundle\Security\Authorization\TaskVoter;
use Chill\TaskBundle\Workflow\TaskWorkflowManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class SingleTaskListType extends AbstractType
{
    /**
     * @var AuthorizationHelper
     */
    protected $authorizationHelper;

    /**
     * @var EntityManagerInterface
     */
    protected $em;

    /**
     * @var TaskWorkflowManager
     */
    protected $taskWorkflowManager;

    /**
     * @var TokenStorageInterface
     */
    protected $tokenStorage;

    public function __construct(
        EntityManagerInterface $em,
        TokenStorageInterface $tokenStorage,
        AuthorizationHelper $authorizationHelper,
        TaskWorkflowManager $taskWorkflowManager,
    ) {
        $this->em = $em;
        $this->tokenStorage = $tokenStorage;
        $this->authorizationHelper = $authorizationHelper;
        $this->taskWorkflowManager = $taskWorkflowManager;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $statuses = [
            'Tasks not started' => SingleTaskRepository::DATE_STATUS_NOT_STARTED,
            'Tasks with expired deadline' => SingleTaskRepository::DATE_STATUS_ENDED,
            'Tasks with warning deadline reached' => SingleTaskRepository::DATE_STATUS_WARNING,
            'Current tasks' => SingleTaskRepository::DATE_STATUS_CURRENT,
            'Closed tasks' => 'closed',
        ];

        $builder
            ->add('user_id', ChoiceType::class, [
                'choices' => $this->getUserChoices($options),
                'placeholder' => 'Any user',
                'required' => false,
                'label' => 'Assignee',
            ]);

        if ($options['add_status']) {
            $builder
                ->add('status', ChoiceType::class, [
                    'choices' => $statuses,
                    'expanded' => true,
                    'multiple' => true,
                    'label' => 'status',
                ]);
        }

        if ($options['add_type']) {
            $types = $this->getTaskTypesChoices($options);

            if (\count($types) > 0) {
                $builder->add('types', ChoiceType::class, [
                    'choices' => $types,
                    'required' => false,
                    'expanded' => true,
                    'multiple' => true,
                    'label' => 'Task types',
                ]);
            }
        }

        if (null === $options['person']) {
            $builder
                ->add('person_id', PickPersonType::class, [
                    'centers' => $this->authorizationHelper
                        ->getReachableCenters(
                            $this->tokenStorage->getToken()->getUser(),
                            TaskVoter::SHOW
                        ),
                    'required' => false,
                    'label' => 'Associated person',
                ]);
            $reachablesCenters = $this->getReachablesCenters();

            if (\count($reachablesCenters) > 1) {
                $builder
                    ->add('center_id', EntityType::class, [
                        'class' => \Chill\MainBundle\Entity\Center::class,
                        'choices' => $reachablesCenters,
                        'label' => 'Center',
                        'required' => false,
                        'placeholder' => 'All centers',
                    ]);
            }
        } else {
            // add a hidden field
            $builder
                ->add('person_id', HiddenType::class);
            $builder->get('person_id')
                ->addModelTransformer(new PersonToIdTransformer($this->em));
        }
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver
            ->setDefined('person')
            ->setDefault('person', null)
            ->setAllowedTypes('person', [Person::class, 'null'])
            ->setDefined('accompanyingCourse')
            ->setDefined('add_status')
            ->setDefault('add_status', false)
            ->setAllowedTypes('add_status', ['bool'])
            ->setDefined('add_type')
            ->setDefault('add_type', false)
            ->setAllowedTypes('add_type', ['bool']);
    }

    protected function getReachablesCenters()
    {
        $user = $this->tokenStorage->getToken()->getUser();
        $role = TaskVoter::SHOW;

        return $this->authorizationHelper->getReachableCenters($user, $role);
    }

    protected function getTaskTypesChoices($options)
    {
        $qb = $this->em->createQueryBuilder();
        $user = $this->tokenStorage->getToken()->getUser();
        $role = TaskVoter::SHOW;
        $centers = $this->authorizationHelper->getReachableCenters($user, $role);

        $qb->select('DISTINCT task.type AS type')
            ->from(SingleTask::class, 'task')
            ->join('task.person', 'person');

        $i = 0;
        $orCenters = $qb->expr()->orX();

        foreach ($centers as $center) {
            $circles = $this->authorizationHelper->getReachableCircles($user, $role, $center);

            if (\count($circles) > 0) {
                $andX = $qb->expr()->andX();
                $andX
                    ->add($qb->expr()->eq('person.center', ':center_'.$i))
                    ->add($qb->expr()->in('task.circle', ':circles_'.$i));
                $orCenters->add($andX);

                $qb
                    ->setParameter('center_'.$i, $center)
                    ->setParameter('circles_'.$i, $circles);
                ++$i;
            }
        }

        if (0 < $i) {
            $qb->where($orCenters);
        }

        $types = $qb->getQuery()->getResult();

        $choices = [];

        foreach ($types as $row) {
            $fake = (new SingleTask())->setType($row['type']);
            $label = $this->taskWorkflowManager->getWorkflowMetadata($fake, 'definition.name');
            $choices[$label] = $row['type'];
        }

        return $choices;
    }

    protected function getUserChoices($options)
    {
        $users = $this->getUsersAssigneedToTask($options);
        $choices = \array_combine(
            // get usernames
            \array_map(static fn (User $user) => $user->getUsername(), $users),
            // get ids
            \array_map(static fn (User $user) => $user->getId(), $users)
        );
        $choices['Unassigned'] = '_unassigned';

        return $choices;
    }

    /**
     * Return a list of user having a task assigned.
     *
     * @return User[]
     */
    protected function getUsersAssigneedToTask(mixed $options)
    {
        $qb = $this->em->createQueryBuilder();
        $user = $this->tokenStorage->getToken()->getUser();
        $role = TaskVoter::SHOW;
        $centers = $this->authorizationHelper->getReachableCenters($user, $role);

        $qb->select('DISTINCT user')
            ->from(User::class, 'user')
            ->join(SingleTask::class, 'task', \Doctrine\ORM\Query\Expr\Join::WITH, 'task.assignee = user')
            ->join('task.person', 'person')
            ->where("user.enabled = 'TRUE'");

        if (null !== $options['person']) {
            $qb
                ->andWhere($qb->expr()->eq('task.person', ':person'))
                ->setParameter('person', $options['person']);
        }

        $i = 0;
        $circleCenterCond = $qb->expr()->orX();

        foreach ($centers as $center) {
            $circles = $this->authorizationHelper->getReachableCircles($user, $role, $center);
            // add condition about person and circle
            $circleCenterCond->add(
                $qb->expr()->andX()
                    ->add($qb->expr()->eq('person.center', ':center_'.$i))
                    ->add($qb->expr()->in('task.circle', ':circles_'.$i))
            );

            $qb->setParameter('center_'.$i, $center)
                ->setParameter('circles_'.$i, $circles);
            // increase counter
            ++$i;
        }
        $qb->andWhere($circleCenterCond);

        return $qb->getQuery()->getResult();
    }
}
