<?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\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\AccompanyingPeriodType;
use Chill\PersonBundle\Privacy\PrivacyEvent;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\DBAL\Exception;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_filter;

/**
 * Class AccompanyingPeriodController.
 */
class AccompanyingPeriodController extends AbstractController
{
    public function __construct(
        protected AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository,
        private readonly EventDispatcherInterface $eventDispatcher,
        private readonly ValidatorInterface $validator,
        private readonly TranslatorInterface $translator,
        private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
    ) {}

    /**
     * @throws \Exception
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/person/{person_id}/accompanying-period/close', name: 'chill_person_accompanying_period_close')]
    public function closeAction(int $person_id, Request $request): Response
    {
        $person = $this->_getPerson($person_id);

        $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, 'You are not allowed to update this person');

        if (false === $person->isOpen()) {
            $this->get('session')->getFlashBag()
                ->add('error', $this->translator
                    ->trans(
                        'Beware period is closed',
                        ['%name%' => $person->__toString()]
                    ));

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

        $current = $person->getCurrentAccompanyingPeriod();

        $form = $this->createForm(AccompanyingPeriodType::class, $current, [
            'period_action' => 'close',
            'center' => $person->getCenter(),
        ]);

        if ('POST' === $request->getMethod()) {
            $form->handleRequest($request);

            if ($form->isValid()) {
                $person->close($current);
                $errors = $this->_validatePerson($person);

                if (0 === \count($errors)) {
                    $this->get('session')->getFlashBag()
                        ->add('success', $this->translator
                            ->trans('An accompanying period has been closed.', [
                                '%name%' => $person->__toString(),
                            ]));

                    $this->managerRegistry->getManager()->flush();

                    return $this->redirectToRoute('chill_person_accompanying_period_list', [
                        'person_id' => $person->getId(),
                    ]);
                }
                $this->get('session')->getFlashBag()
                    ->add('error', $this->translator
                        ->trans('Error! Period not closed!'));

                foreach ($errors as $error) {
                    $this->get('session')->getFlashBag()
                        ->add('info', $error->getMessage());
                }
            } else { // if form is not valid
                $this->get('session')->getFlashBag()
                    ->add(
                        'error',
                        $this->translator
                            ->trans('Pediod closing form is not valid')
                    );

                foreach ($form->getErrors() as $error) {
                    $this->get('session')->getFlashBag()
                        ->add('info', $error->getMessage());
                }
            }
        }

        return $this->render('@ChillPerson/AccompanyingPeriod/form.html.twig', [
            'form' => $form->createView(),
            'person' => $person,
            'accompanying_period' => $current,
        ]);
    }

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/person/{person_id}/accompanying-period/create', name: 'chill_person_accompanying_period_create')]
    public function createAction(int $person_id, Request $request): Response
    {
        $person = $this->_getPerson($person_id);

        $this->denyAccessUnlessGranted(
            PersonVoter::UPDATE,
            $person,
            'You are not allowed to update this person'
        );

        $accompanyingPeriod = new AccompanyingPeriod(new \DateTime('now'));
        $accompanyingPeriod->setClosingDate(new \DateTime('now'));

        $accompanyingPeriod->addPerson($person);
        // or $person->addAccompanyingPeriod($accompanyingPeriod);

        $form = $this->createForm(
            AccompanyingPeriodType::class,
            $accompanyingPeriod,
            [
                'period_action' => 'create',
                'center' => $person->getCenter(),
            ]
        );

        if ('POST' === $request->getMethod()) {
            $form->handleRequest($request);
            $errors = $this->_validatePerson($person);
            $flashBag = $this->get('session')->getFlashBag();

            if (
                $form->isValid(['Default', 'closed'])
                && 0 === \count($errors)
            ) {
                $em = $this->managerRegistry->getManager();
                $em->persist($accompanyingPeriod);
                $em->flush();
                $flashBag->add(
                    'success',
                    $this->translator->trans(
                        'A period has been created.'
                    )
                );

                return $this->redirectToRoute('chill_person_accompanying_period_list', [
                    'person_id' => $person->getId(),
                ]);
            }
            $flashBag->add('error', $this->translator
                ->trans('Error! Period not created!'));

            foreach ($errors as $error) {
                $flashBag->add('info', $error->getMessage());
            }
        }

        return $this->render('@ChillPerson/AccompanyingPeriod/form.html.twig', [
            'form' => $form->createView(),
            'person' => $person,
            'accompanying_period' => $accompanyingPeriod,
        ]);
    }

    /**
     * @ParamConverter("person", options={"id"="person_id"})
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/person/{person_id}/accompanying-period', name: 'chill_person_accompanying_period_list')]
    public function listAction(Person $person): Response
    {
        $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $person);

        $event = new PrivacyEvent($person, [
            'element_class' => AccompanyingPeriod::class,
            'action' => 'list',
        ]);
        $this->eventDispatcher->dispatch($event, PrivacyEvent::PERSON_PRIVACY_EVENT);

        $accompanyingPeriods = $this->accompanyingPeriodACLAwareRepository
            ->findByPerson($person, AccompanyingPeriodVoter::SEE, ['openingDate' => 'DESC', 'id' => 'DESC']);

        // usort($accompanyingPeriodsRaw, static fn ($a, $b) => $b->getOpeningDate() <=> $a->getOpeningDate());

        // filter visible or not visible
        // $accompanyingPeriods = array_filter($accompanyingPeriodsRaw, fn (AccompanyingPeriod $ap) => $this->isGranted(AccompanyingPeriodVoter::SEE, $ap));

        return $this->render('@ChillPerson/AccompanyingPeriod/list.html.twig', [
            'accompanying_periods' => $accompanyingPeriods,
            'person' => $person,
        ]);
    }

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/person/{person_id}/accompanying-period/open', name: 'chill_person_accompanying_period_open')]
    public function openAction(int $person_id, Request $request): Response
    {
        $person = $this->_getPerson($person_id);

        $this->denyAccessUnlessGranted(
            PersonVoter::UPDATE,
            $person,
            'You are not allowed to update this person'
        );

        // in case the person is already open
        if ($person->isOpen()) {
            $this->get('session')->getFlashBag()
                ->add('error', $this->translator
                    ->trans(
                        'Error! Period %name% is not closed ; it can be open',
                        ['%name%' => $person->__toString()]
                    ));

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

        $accompanyingPeriod = new AccompanyingPeriod(new \DateTime());

        $form = $this->createForm(
            AccompanyingPeriodType::class,
            $accompanyingPeriod,
            [
                'period_action' => 'open',
                'center' => $person->getCenter(),
            ]
        );

        if ('POST' === $request->getMethod()) {
            $form->handleRequest($request);

            if ($form->isValid()) {
                $person->open($accompanyingPeriod);

                $errors = $this->_validatePerson($person);

                if (\count($errors) <= 0) {
                    $this->get('session')->getFlashBag()
                        ->add('success', $this->translator
                            ->trans(
                                'An accompanying period has been opened.',
                                ['%name%' => $person->__toString()]
                            ));

                    $this->managerRegistry->getManager()->flush();

                    return $this->redirectToRoute('chill_person_accompanying_period_list', [
                        'person_id' => $person->getId(),
                    ]);
                }
                $this->get('session')->getFlashBag()
                    ->add('error', $this->translator
                        ->trans('Period not opened'));

                foreach ($errors as $error) {
                    $this->get('session')->getFlashBag()
                        ->add('info', $error->getMessage());
                }
            } else { // if errors in forms
                $this->get('session')->getFlashBag()
                    ->add(
                        'error',
                        $this->translator
                            ->trans('Period not opened : form is invalid')
                    );
            }
        }

        return $this->render('@ChillPerson/AccompanyingPeriod/form.html.twig', [
            'form' => $form->createView(),
            'person' => $person,
            'accompanying_period' => $accompanyingPeriod,
        ]);
    }

    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/person/{person_id}/accompanying-period/{period_id}/re-open', name: 'chill_person_accompanying_period_re_open')]
    public function reOpenAction(int $person_id, int $period_id, Request $request): Response
    {
        /** @var Person $person */
        $person = $this->_getPerson($person_id);

        /** @var AccompanyingPeriod $period */
        $period = \array_filter(
            $person->getAccompanyingPeriods(),
            static fn (AccompanyingPeriod $p) => $p->getId() === $period_id
        )[0] ?? null;

        if (null === $period) {
            throw $this->createNotFoundException('period not found');
        }

        $confirm = $request->query->getBoolean('confirm', false);

        if (true === $confirm && $period->canBeReOpened($person)) {
            $period->reOpen();

            $this->_validatePerson($person);

            $this->managerRegistry->getManager()->flush();

            $this->addFlash('success', $this->translator->trans(
                'The period has been re-opened'
            ));

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

        if (false === $confirm && $period->canBeReOpened($person)) {
            return $this->render('@ChillPerson/AccompanyingPeriod/re_open.html.twig', [
                'period' => $period,
                'person' => $person,
            ]);
        }

        return (new Response())
            ->setStatusCode(Response::HTTP_BAD_REQUEST)
            ->setContent('You cannot re-open this period');
    }

    /**
     * @throws Exception
     */
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/person/{person_id}/accompanying-period/{period_id}/update', name: 'chill_person_accompanying_period_update')]
    public function updateAction(int $person_id, int $period_id, Request $request): Response
    {
        $em = $this->managerRegistry->getManager();

        /** @var AccompanyingPeriod $accompanyingPeriod */
        $accompanyingPeriod = $em->getRepository(AccompanyingPeriod::class)->find($period_id);

        if (null === $accompanyingPeriod) {
            throw $this->createNotFoundException('Period with id '.$period_id.' is not found');
        }

        /** @var Person $person */
        $person = $this->_getPerson($person_id);

        // CHECK
        if (!$accompanyingPeriod->containsPerson($person)) {
            throw new Exception('Accompanying period '.$period_id.' does not contain person '.$person_id);
        }

        $this->denyAccessUnlessGranted(
            PersonVoter::UPDATE,
            $person,
            'You are not allowed to update this person'
        );

        $form = $this->createForm(
            AccompanyingPeriodType::class,
            $accompanyingPeriod,
            [
                'period_action' => 'update',
                'center' => $person->getCenter(),
            ]
        );

        if ('POST' === $request->getMethod()) {
            $form->handleRequest($request);
            $errors = $this->_validatePerson($person);
            $flashBag = $this->get('session')->getFlashBag();

            if (
                $form->isValid(['Default', 'closed'])
                    && 0 === \count($errors)
            ) {
                $em->flush();

                $flashBag->add(
                    'success',
                    $this->translator->trans('An accompanying period has been updated.')
                );

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

            $flashBag->add('error', $this->translator
                ->trans('Error when updating the period'));

            foreach ($errors as $error) {
                $flashBag->add('info', $error->getMessage());
            }
        }

        return $this->render('@ChillPerson/AccompanyingPeriod/form.html.twig', [
            'form' => $form->createView(),
            'person' => $person,
            'accompanying_period' => $accompanyingPeriod,
        ]);
    }

    /**
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    private function _getPerson(int $id): Person
    {
        $person = $this->managerRegistry->getManager()
            ->getRepository(Person::class)->find($id);

        if (null === $person) {
            throw $this->createNotFoundException('Person not found');
        }

        $this->denyAccessUnlessGranted(
            PersonVoter::SEE,
            $person,
            'You are not allowed to see this person'
        );

        return $person;
    }

    private function _validatePerson(Person $person): ConstraintViolationListInterface
    {
        $errors = $this->validator->validate(
            $person,
            null,
            ['Default']
        );

        // Can be disabled with config
        if (false === $this->container->getParameter('chill_person.allow_multiple_simultaneous_accompanying_periods')) {
            $errors_accompanying_period = $this->validator->validate(
                $person,
                null,
                ['accompanying_period_consistent']
            );

            foreach ($errors_accompanying_period as $error) {
                $errors->add($error);
            }
        }

        return $errors;
    }
}
