<?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\DocGeneratorBundle\Controller;

use Chill\DocGeneratorBundle\Context\ContextManager;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
use Chill\DocGeneratorBundle\Service\Messenger\RequestGenerationMessage;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Security\ChillSecurity;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;

final class DocGeneratorTemplateController extends AbstractController
{
    public function __construct(
        private readonly ContextManager $contextManager,
        private readonly DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
        private readonly MessageBusInterface $messageBus,
        private readonly PaginatorFactory $paginatorFactory,
        private readonly EntityManagerInterface $entityManager,
        private readonly ClockInterface $clock,
        private readonly ChillSecurity $security,
    ) {}

    #[Route(path: '{_locale}/admin/doc/gen/generate/test/from/{template}/for/{entityClassName}/{entityId}', name: 'chill_docgenerator_test_generate_from_template')]
    public function adminTestGenerateDocFromTemplateAction(
        DocGeneratorTemplate $template,
        string $entityClassName,
        int $entityId,
        Request $request,
    ): Response {
        return $this->generateDocFromTemplate(
            $template,
            $entityId,
            $request,
            true
        );
    }

    #[Route(path: '{_locale}/doc/gen/generate/from/{template}/for/{entityClassName}/{entityId}', name: 'chill_docgenerator_generate_from_template')]
    public function generateDocFromTemplateAction(
        DocGeneratorTemplate $template,
        string $entityClassName,
        int $entityId,
        Request $request,
    ): Response {
        return $this->generateDocFromTemplate(
            $template,
            $entityId,
            $request,
            false
        );
    }

    #[Route(path: '/api/1.0/docgen/templates/by-entity/{entityClassName}', name: 'chill_docgenerator_templates_for_entity_api')]
    public function listTemplateApiAction(string $entityClassName): Response
    {
        $nb = $this->docGeneratorTemplateRepository->countByEntity($entityClassName);
        $paginator = $this->paginatorFactory->create($nb);
        $entities = $this->docGeneratorTemplateRepository->findByEntity(
            $entityClassName,
            $paginator->getCurrentPageFirstItemNumber(),
            $paginator->getItemsPerPage()
        );

        return $this->json(
            new Collection($entities, $paginator),
            Response::HTTP_OK,
            [],
            [AbstractNormalizer::GROUPS => ['read']]
        );
    }

    /**
     * @return void
     */
    #[Route(path: '{_locale}/admin/doc/gen/generate/test/redirect', name: 'chill_docgenerator_test_generate_redirect')]
    public function redirectToTestGenerate(Request $request): RedirectResponse
    {
        $template = $request->query->getInt('template');

        if (null === $template) {
            throw new BadRequestHttpException('template parameter is missing');
        }

        $entityClassName = $request->query->get('entityClassName');

        if (null === $entityClassName) {
            throw new BadRequestHttpException('entityClassName is missing');
        }

        $entityId = $request->query->get('entityId');

        if (null === $entityId) {
            throw new BadRequestHttpException('entityId is missing');
        }

        return $this->redirectToRoute(
            'chill_docgenerator_test_generate_from_template',
            [
                'template' => $template, 'entityClassName' => $entityClassName, 'entityId' => $entityId,
                'returnPath' => $request->query->get('returnPath', '/'),
            ]
        );
    }

    private function generateDocFromTemplate(
        DocGeneratorTemplate $template,
        int $entityId,
        Request $request,
        bool $isTest,
    ): Response {
        try {
            $context = $this->contextManager->getContextByDocGeneratorTemplate($template);
        } catch (ContextNotFoundException $e) {
            throw new NotFoundHttpException('Context not found.', $e);
        }

        $entity = $this
            ->entityManager
            ->getRepository($context->getEntityClass())
            ->find($entityId);

        if (null === $entity) {
            throw new NotFoundHttpException(sprintf('Entity with classname %s and id %s is not found', $context->getEntityClass(), $entityId));
        }

        $contextGenerationData = [];

        if (
            $context instanceof DocGeneratorContextWithPublicFormInterface
            && $context->hasPublicForm($template, $entity) || $isTest
        ) {
            if ($context instanceof DocGeneratorContextWithPublicFormInterface && $context->hasPublicForm($template, $entity)) {
                $builder = $this->createFormBuilder(
                    array_merge(
                        $context->getFormData($template, $entity),
                        $isTest ? ['creator' => null, 'dump_only' => false, 'send_result_to' => ''] : []
                    )
                );

                $context->buildPublicForm($builder, $template, $entity);
            } else {
                $builder = $this->createFormBuilder(
                    ['creator' => null, 'show_data' => false, 'send_result_to' => '']
                );
            }

            if ($isTest) {
                $builder->add('dump_only', CheckboxType::class, [
                    'label' => 'docgen.Show data instead of generating',
                    'required' => false,
                ]);
                $builder->add('send_result_to', EmailType::class, [
                    'label' => 'docgen.Send report to',
                    'help' => 'docgen.Send report errors to this email address',
                    'empty_data' => '',
                    'required' => true,
                    'constraints' => [
                        new NotBlank(),
                        new NotNull(),
                    ],
                ]);
                $builder->add('creator', PickUserDynamicType::class, [
                    'label' => 'docgen.Generate as creator',
                    'help' => 'docgen.The document will be generated as the given creator',
                    'multiple' => false,
                    'constraints' => [
                        new NotNull(),
                    ],
                ]);
            }

            $form = $builder->getForm()->handleRequest($request);

            if ($form->isSubmitted() && $form->isValid()) {
                $contextGenerationData = $form->getData();
            } elseif (!$form->isSubmitted() || ($form->isSubmitted() && !$form->isValid())) {
                $templatePath = '@ChillDocGenerator/Generator/basic_form.html.twig';
                $templateOptions = [
                    'entity' => $entity,
                    'form' => $form->createView(),
                    'template' => $template,
                    'context' => $context,
                ];

                return $this->render($templatePath, $templateOptions);
            }
        }

        // transform context generation data
        $contextGenerationDataSanitized =
            $context instanceof DocGeneratorContextWithPublicFormInterface ?
                $context->contextGenerationDataNormalize($template, $entity, $contextGenerationData)
                : [];

        // we prepare the object to store the document
        $storedObject = (new StoredObject())
            ->setStatus(StoredObject::STATUS_PENDING)
        ;

        if ($isTest) {
            // document will be stored during 15 days, if generation is a test
            $storedObject->setDeleteAt($this->clock->now()->add(new \DateInterval('P15D')));
        }

        $this->entityManager->persist($storedObject);

        // we store the generated document (associate with the original entity, etc.)
        // but only if this is not a test
        if (!$isTest) {
            $context
                ->storeGenerated(
                    $template,
                    $storedObject,
                    $entity,
                    $contextGenerationData
                );
        }

        $this->entityManager->flush();

        if ($isTest) {
            $creator = $contextGenerationData['creator'];
            $sendResultTo =  ($form ?? null)?->get('send_result_to')?->getData() ?? null;
            $dumpOnly = ($form ?? null)?->get('dump_only')?->getData() ?? false;
        } else {
            if (!$this->isGranted('ROLE_USER')) {
                throw new AccessDeniedHttpException('only authenticated user can request a generation');
            }

            $creator = $this->security->getUser();
            $sendResultTo = null;
            $dumpOnly = false;
        }

        $this->messageBus->dispatch(
            new RequestGenerationMessage(
                $creator,
                $template,
                $entityId,
                $storedObject,
                $contextGenerationDataSanitized,
                $isTest,
                $sendResultTo,
                $dumpOnly,
            )
        );

        return $this
            ->redirectToRoute(
                'chill_wopi_file_edit',
                [
                    'fileId' => $storedObject->getUuid(),
                    'returnPath' => $request->query->get('returnPath', '/'),
                ]
            );
    }
}
