<?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\Config\ConfigPersonAltNamesHelper;
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\Form\PersonType;
use Chill\PersonBundle\Privacy\PrivacyEvent;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Search\SimilarPersonMatcher;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function hash;

final class PersonController extends AbstractController
{
    public function __construct(
        private readonly AuthorizationHelperInterface $authorizationHelper,
        private readonly SimilarPersonMatcher $similarPersonMatcher,
        private readonly TranslatorInterface $translator,
        private readonly EventDispatcherInterface $eventDispatcher,
        private readonly PersonRepository $personRepository,
        private readonly ConfigPersonAltNamesHelper $configPersonAltNameHelper,
        private readonly ValidatorInterface $validator,
        private readonly EntityManagerInterface $em,
    ) {}

    #[\Symfony\Component\Routing\Attribute\Route(path: '/{_locale}/person/{person_id}/general/edit', name: 'chill_person_general_edit')]
    public function editAction(int $person_id, Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
    {
        $person = $this->_getPerson($person_id);

        if (null === $person) {
            throw $this->createNotFoundException();
        }

        $this->denyAccessUnlessGranted(
            'CHILL_PERSON_UPDATE',
            $person,
            'You are not allowed to edit this person'
        );

        $form = $this->createForm(
            PersonType::class,
            $person,
            [
                'cFGroup' => $this->getCFGroup(),
            ]
        );

        $form->handleRequest($request);

        if ($form->isSubmitted() && !$form->isValid()) {
            $this->get('session')
                ->getFlashBag()->add('error', $this->translator
                ->trans('This form contains errors'));
        } elseif ($form->isSubmitted() && $form->isValid()) {
            $this->em->flush();

            $this->get('session')->getFlashBag()
                ->add(
                    'success',
                    $this->translator
                        ->trans('The person data has been updated')
                );

            return $this->redirectToRoute('chill_person_view', [
                'person_id' => $person->getId(),
            ]);
        }

        return $this->render(
            '@ChillPerson/Person/edit.html.twig',
            ['person' => $person, 'form' => $form]
        );
    }

    public function getCFGroup()
    {
        $cFGroup = null;

        $cFDefaultGroup = $this->em->getRepository(\Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup::class)
            ->findOneByEntity(Person::class);

        if ($cFDefaultGroup) {
            $cFGroup = $cFDefaultGroup->getCustomFieldsGroup();
        }

        return $cFGroup;
    }

    /**
     * @ParamConverter("person", options={"id": "person_id"})
     */
    #[\Symfony\Component\Routing\Attribute\Route(path: '/{_locale}/person/household/{person_id}/history', name: 'chill_person_household_person_history', methods: ['GET', 'POST'])]
    public function householdHistoryByPerson(Request $request, Person $person): Response
    {
        $this->denyAccessUnlessGranted(
            'CHILL_PERSON_SEE',
            $person,
            'You are not allowed to see this person.'
        );

        $event = new PrivacyEvent($person);
        $this->eventDispatcher->dispatch($event, PrivacyEvent::PERSON_PRIVACY_EVENT);

        return $this->render(
            '@ChillPerson/Person/household_history.html.twig',
            [
                'person' => $person,
            ]
        );
    }

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

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

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

        $form = $this->createForm(CreationPersonType::class, $person)
            ->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();
        } elseif (
            Request::METHOD_POST === $request->getMethod()
            && $form->isValid()
        ) {
            $alternatePersons = $this->similarPersonMatcher
                ->matchPerson($person);

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

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

                $address = $form->get('address')->getData();
                $addressForm = (bool) $form->get('addressForm')->getData();

                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 ($form->get('createHousehold')->isClicked()) {
                        return $this->redirectToRoute('chill_person_household_members_editor', [
                            'persons' => [$person->getId()],
                            'household' => $household->getId(),
                        ]);
                    }
                }

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

                if ($form->get('createHousehold')->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,
                'alternatePersons' => $alternatePersons ?? [],
            ]
        );
    }

    #[\Symfony\Component\Routing\Attribute\Route(path: '/{_locale}/person/{person_id}/general', name: 'chill_person_view')]
    public function viewAction(int $person_id): \Symfony\Component\HttpFoundation\Response
    {
        $person = $this->_getPerson($person_id);

        if (null === $person) {
            throw $this->createNotFoundException("Person with id {$person_id} not".' found on this server');
        }

        $this->denyAccessUnlessGranted(
            'CHILL_PERSON_SEE',
            $person,
            'You are not allowed to see this person.'
        );

        $event = new PrivacyEvent($person);
        $this->eventDispatcher->dispatch($event, PrivacyEvent::PERSON_PRIVACY_EVENT);

        return $this->render(
            '@ChillPerson/Person/view.html.twig',
            [
                'person' => $person,
                'cFGroup' => $this->getCFGroup(),
                'alt_names' => $this->configPersonAltNameHelper->getChoices(),
            ]
        );
    }

    /**
     * easy getting a person by his id.
     *
     * @return Person
     */
    private function _getPerson(int $id): ?\Chill\PersonBundle\Entity\Person
    {
        return $this->personRepository->find($id);
    }

    /**
     * @return \Symfony\Component\Validator\ConstraintViolationListInterface
     */
    private function _validatePersonAndAccompanyingPeriod(Person $person)
    {
        $errors = $this->validator
            ->validate($person, null, ['creation']);

        // validate accompanying periods
        $periods = $person->getAccompanyingPeriods();

        foreach ($periods as $period) {
            $period_errors = $this->validator
                ->validate($period);

            // group errors :
            foreach ($period_errors as $error) {
                $errors->add($error);
            }
        }

        return $errors;
    }

    private function isLastPostDataChanges(Form $form, Request $request, bool $replace = false): bool
    {
        /** @var SessionInterface $session */
        $session = $this->get('session');

        if (!$session->has('last_person_data')) {
            return true;
        }

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

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

        if ($replace) {
            $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(Form $form, Request $request): string
    {
        $fields = [];
        $ignoredFields = ['form_status', '_token'];

        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(): void
    {
        $this->get('session')->set('last_person_data', '');
    }
}
