<?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\Service\DocGenerator;

use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Entity\DocumentCategory;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Person\PersonResource;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Person\ResidentialAddress;
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Service\DocGenerator\PersonContextTest;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * @see PersonContextTest
 */
final readonly class PersonContext implements PersonContextInterface
{
    private bool $showScopes;

    public function __construct(
        private AuthorizationHelperInterface $authorizationHelper,
        private BaseContextData $baseContextData,
        private CenterResolverManagerInterface $centerResolverManager,
        private DocumentCategoryRepository $documentCategoryRepository,
        private EntityManagerInterface $em,
        private NormalizerInterface $normalizer,
        ParameterBagInterface $parameterBag,
        private ScopeRepositoryInterface $scopeRepository,
        private Security $security,
        private TranslatorInterface $translator,
        private TranslatableStringHelperInterface $translatableStringHelper,
        private ThirdPartyRender $thirdPartyRender,
        private ThirdPartyRepository $thirdPartyRepository,
        private ResidentialAddressRepository $residentialAddressRepository,
    ) {
        $this->showScopes = $parameterBag->get('chill_main')['acl']['form_show_scopes'];
    }

    public function adminFormReverseTransform(array $data): array
    {
        if (\array_key_exists('category', $data)) {
            $data['category'] = [
                'idInsideBundle' => $data['category']->getIdInsideBundle(),
                'bundleId' => $data['category']->getBundleId(),
            ];
        }

        return $data;
    }

    public function adminFormTransform(array $data): array
    {
        $r = [
            'mainPerson' => $data['mainPerson'] ?? false,
            'mainPersonLabel' => $data['mainPersonLabel'] ?? $this->translator->trans('docgen.Main person'),
            'thirdParty' => $data['thirdParty'] ?? false,
            'thirdPartyLabel' => $data['thirdPartyLabel'] ?? $this->translator->trans('Third party'),
        ];

        if (\array_key_exists('category', $data)) {
            $r['category'] = $this->documentCategoryRepository->find($data['category']);
        }

        return $r;
    }

    public function buildAdminForm(FormBuilderInterface $builder): void
    {
        $builder
            ->add('category', EntityType::class, [
                'placeholder' => 'Choose a document category',
                'class' => DocumentCategory::class,
                'query_builder' => static fn (EntityRepository $er) => $er->createQueryBuilder('c')
                    ->where('c.documentClass = :docClass')
                    ->setParameter('docClass', PersonDocument::class),
                'choice_label' => fn ($entity = null) => $entity ? $this->translatableStringHelper->localize($entity->getName()) : '',
                'required' => true,
            ])
            ->add('thirdParty', CheckboxType::class, [
                'required' => false,
                'label' => 'docgen.Ask for thirdParty',
            ])
            ->add('thirdPartyLabel', TextType::class, [
                'label' => 'docgen.thirdParty label',
                'required' => true,
            ]);
    }

    /**
     * @param Person $entity
     */
    public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
    {
        $options = $template->getOptions();

        $builder->add('title', TextType::class, [
            'required' => true,
            'label' => 'docgen.Document title',
            'data' => $this->translatableStringHelper->localize($template->getName()),
        ]);

        $thirdParties = [...array_values(
            array_filter(
                array_map(
                    fn (ResidentialAddress $r): ?ThirdParty => $r->getHostThirdParty(),
                    $this
                        ->residentialAddressRepository
                        ->findCurrentResidentialAddressByPerson($entity)
                )
            )
        ), ...array_values(
            array_filter(
                array_map(
                    fn (PersonResource $r): ?ThirdParty => $r->getThirdParty(),
                    $entity->getResources()->filter(
                        static fn (PersonResource $r): bool => null !== $r->getThirdParty()
                    )->toArray()
                )
            )
        )];

        if ($options['thirdParty'] ?? false) {
            $builder->add('thirdParty', EntityType::class, [
                'class' => ThirdParty::class,
                'choices' => $thirdParties,
                'choice_label' => fn (ThirdParty $p) => $this->thirdPartyRender->renderString($p, []),
                'multiple' => false,
                'required' => false,
                'expanded' => true,
                'label' => $options['thirdPartyLabel'],
                'placeholder' => $this->translator->trans('Any third party selected'),
            ]);
        }

        if ($this->isScopeNecessary($entity)) {
            $builder->add('scope', ScopePickerType::class, [
                'role' => PersonDocumentVoter::CREATE,
                'subject' => $entity,
            ]);
        }
    }

    public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
    {
        $data = [];
        $data = array_merge($data, $this->baseContextData->getData($contextGenerationData['creator'] ?? null));
        $data['person'] = $this->normalizer->normalize($entity, 'docgen', [
            'docgen:expects' => Person::class,
            'groups' => ['docgen:read'],
            'docgen:person:with-household' => true,
            'docgen:person:with-relations' => true,
            'docgen:person:with-budget' => true,
        ]);

        if ($template->getOptions()['thirdParty']) {
            $data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'], 'docgen', [
                'docgen:expects' => ThirdParty::class,
                'groups' => 'docgen:read',
            ]);
        }

        return $data;
    }

    public function getDescription(): string
    {
        return 'docgen.A basic context for person';
    }

    public function getEntityClass(): string
    {
        return Person::class;
    }

    public function getFormData(DocGeneratorTemplate $template, $entity): array
    {
        return [
            'person' => $entity,
        ];
    }

    public static function getKey(): string
    {
        return self::class;
    }

    public function getName(): string
    {
        return 'docgen.Person basic';
    }

    public function hasAdminForm(): bool
    {
        return true;
    }

    /**
     * @param Person $entity
     */
    public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
    {
        return true;
    }

    /**
     * @param Person $entity
     */
    public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
    {
        $scope = $data['scope'] ?? null;

        return [
            'title' => $data['title'] ?? '',
            'scope_id' => $scope instanceof Scope ? $scope->getId() : null,
            'thirdParty' => ($data['thirdParty'] ?? null)?->getId(),
        ];
    }

    /**
     * @param Person $entity
     */
    public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
    {
        if (!isset($data['scope'])) {
            $scope = null;
        } else {
            if (null === $scope = $this->scopeRepository->find($data['scope'])) {
                throw new \UnexpectedValueException('scope not found');
            }
        }

        return [
            'title' => $data['title'] ?? '',
            'scope' => $scope,
            'thirdParty' => null !== ($id = ($data['thirdParty'] ?? null)) ? $this->thirdPartyRepository->find($id) : null,
        ];
    }

    public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
    {
        $doc = new PersonDocument();
        $doc->setTemplate($template)
            ->setTitle(
                $contextGenerationData['title'] ?? $this->translatableStringHelper->localize($template->getName())
            )
            ->setDate(new \DateTime())
            ->setDescription($this->translatableStringHelper->localize($template->getName()))
            ->setPerson($entity)
            ->setObject($storedObject);

        if (\array_key_exists('category', $template->getOptions())) {
            $doc
                ->setCategory(
                    $this->documentCategoryRepository->find(
                        $template->getOptions()['category']
                    )
                );
        }

        if ($this->isScopeNecessary($entity)) {
            $doc->setScope($contextGenerationData['scope']);
        } elseif ($this->showScopes) {
            // in this case, it should have only one scope possible, we get it through AuthorizationHelper::getReachableScopes
            $scopes = $this->authorizationHelper->getReachableScopes(
                $this->security->getUser(),
                PersonDocumentVoter::CREATE,
                $this->centerResolverManager->resolveCenters($entity)
            );

            if (1 !== \count($scopes)) {
                throw new \LogicException('at this step, it should have only one scope');
            }

            $doc->setScope($scopes[0]);
        }

        $this->em->persist($doc);
    }

    private function isScopeNecessary(Person $person): bool
    {
        if ($this->showScopes && 1 < \count(
            $this->authorizationHelper->getReachableScopes(
                $this->security->getUser(),
                PersonDocumentVoter::CREATE,
                $this->centerResolverManager->resolveCenters($person)
            )
        )) {
            return true;
        }

        return false;
    }
}
