<?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\Security\Authorization\AuthorizationHelperInterface;
use Chill\PersonBundle\Actions\PersonCreate\Service\PersonCreateDTOFactory;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\CreationPersonType;
use Chill\PersonBundle\Search\SimilarPersonMatcher;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\ClickableInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;

final class PersonCreateController extends AbstractController
{
    public function __construct(
        private readonly AuthorizationHelperInterface $authorizationHelper,
        private readonly SimilarPersonMatcher $similarPersonMatcher,
        private readonly TranslatorInterface $translator,
        private readonly EntityManagerInterface $em,
        private readonly PersonCreateDTOFactory $personCreateDTOFactory,
    ) {}

    /**
     * Method for creating a new person.
     *
     * The controller registers data from a previous post on the form and
     * registers it in the session.
     *
     * The next post compares the data with the previous one and, if yes, shows a
     * review page if there are "alternate persons".
     */
    #[Route(path: '/{_locale}/person/new', name: 'chill_person_new')]
    public function newAction(Request $request, SessionInterface $session): Response
    {
        $person = new Person();

        $authorizedCenters = $this->authorizationHelper->getReachableCenters($this->getUser(), PersonVoter::CREATE);

        $dto = $this->personCreateDTOFactory->createPersonCreateDTO($person);

        if (1 === \count($authorizedCenters)) {
            $dto->center = $authorizedCenters[0];
        }

        $form = $this->createForm(CreationPersonType::class, $dto)
            ->add('editPerson', SubmitType::class, [
                'label' => 'Add the person',
            ])->add('createPeriod', SubmitType::class, [
                'label' => 'Add the person and create an accompanying period',
            ])->add('createHousehold', SubmitType::class, [
                'label' => 'Add the person and create a household',
            ]);

        $form->handleRequest($request);

        if (Request::METHOD_GET === $request->getMethod()) {
            $this->lastPostDataReset($session);
        } elseif (
            Request::METHOD_POST === $request->getMethod()
            && $form->isValid()
        ) {
            $alternatePersons = $this->similarPersonMatcher
                ->matchPerson($person);

            $createHouseholdButton = $form->get('createHousehold');
            $createPeriodButton = $form->get('createPeriod');
            $editPersonButton = $form->get('editPerson');

            if (!$createHouseholdButton instanceof ClickableInterface) {
                throw new \UnexpectedValueException();
            }
            if (!$createPeriodButton instanceof ClickableInterface) {
                throw new \UnexpectedValueException();
            }
            if (!$editPersonButton instanceof ClickableInterface) {
                throw new \UnexpectedValueException();
            }

            if (
                false === $this->isLastPostDataChanges($form, $request, $session)
                || 0 === \count($alternatePersons)
            ) {
                $this->personCreateDTOFactory->mapPersonCreateDTOtoPerson($dto, $person);
                $this->em->persist($person);

                $this->em->flush();
                $this->lastPostDataReset($session);

                $address = $dto->address;
                $addressForm = $dto->addressForm;

                if (null !== $address && $addressForm) {
                    $household = new Household();

                    $member = new HouseholdMember();
                    $member->setPerson($person);
                    $member->setStartDate(new \DateTimeImmutable());

                    $household->addMember($member);
                    $household->setForceAddress($address);

                    $this->em->persist($member);
                    $this->em->persist($household);
                    $this->em->flush();

                    if ($createHouseholdButton->isClicked()) {
                        return $this->redirectToRoute('chill_person_household_members_editor', [
                            'persons' => [$person->getId()],
                            'household' => $household->getId(),
                        ]);
                    }
                }

                if ($createPeriodButton->isClicked()) {
                    return $this->redirectToRoute('chill_person_accompanying_course_new', [
                        'person_id' => [$person->getId()],
                    ]);
                }

                if ($createHouseholdButton->isClicked()) {
                    return $this->redirectToRoute('chill_person_household_members_editor', [
                        'persons' => [$person->getId()],
                    ]);
                }

                return $this->redirectToRoute(
                    'chill_person_general_edit',
                    ['person_id' => $person->getId()]
                );
            }
        } elseif (Request::METHOD_POST === $request->getMethod() && !$form->isValid()) {
            $this->addFlash('error', $this->translator->trans('This form contains errors'));
        }

        return $this->render(
            '@ChillPerson/Person/create.html.twig',
            [
                'form' => $form->createView(),
                'alternatePersons' => $alternatePersons ?? [],
            ]
        );
    }

    private function isLastPostDataChanges(FormInterface $form, Request $request, SessionInterface $session): bool
    {
        if (!$session->has('last_person_data')) {
            return true;
        }

        $newPost = $this->lastPostDataBuildHash($form, $request);

        $isChanged = $session->get('last_person_data') !== $newPost;
        $session->set('last_person_data', $newPost);

        return $isChanged;
    }

    /**
     * build the hash for posted data.
     *
     * For privacy reasons, the data are hashed using sha512
     */
    private function lastPostDataBuildHash(FormInterface $form, Request $request): string
    {
        $fields = [];
        $ignoredFields = ['form_status', '_token', 'identifiers'];

        foreach ($request->request->all()[$form->getName()] as $field => $value) {
            if (\in_array($field, $ignoredFields, true)) {
                continue;
            }
            $fields[$field] = \is_array($value) ?
                \implode(',', $value) : $value;
        }
        ksort($fields);

        return \hash('sha512', \implode('&', $fields));
    }

    private function lastPostDataReset(SessionInterface $session): void
    {
        $session->set('last_person_data', '');
    }
}
