<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\AdminBundle\Controller\Page;

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\AdminBundle\Breadcrumb\Breadcrumb;
use Ferienpass\AdminBundle\Controller\TagsResponses;
use Ferienpass\AdminBundle\Statistics\EditionStatsBuilder;
use Ferienpass\CmsBundle\Form\SimpleType\ContaoRequestTokenType;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Edition;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Export\Offer\OffersExportInterface;
use Ferienpass\CoreBundle\HttpCache\CacheTag;
use Ferienpass\CoreBundle\Message\LotEdition;
use Ferienpass\CoreBundle\Notification\EditionAwareNotificationInterface;
use Ferienpass\CoreBundle\Notifier\Notifier;
use Ferienpass\CoreBundle\Repository\EditionRepository;
use Ferienpass\CoreBundle\Repository\NotificationRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\UX\Turbo\TurboBundle;

#[IsGranted('ROLE_ADMIN')]
#[Route('/saisons')]
final class EditionsController extends AbstractController
{
    use TagsResponses;

    #[Route('/{alias}/freigeben', name: 'admin_offers_review')]
    public function __invoke(?Edition $edition, #[CurrentUser] User $user, Request $request, Breadcrumb $breadcrumb, MessageBusInterface $bus, #[TaggedLocator(OffersExportInterface::class, indexAttribute: 'name', defaultIndexMethod: 'getName')] ServiceLocator $exporters): Response
    {
        if ($edition instanceof Edition) {
            $this->tagResponse(CacheTag::edition($edition));
        }

        /** @noinspection FormViewTemplate `createView()` messes ups error handling/redirect */
        return $this->render('@FerienpassAdmin/page/offers/reviewAll.html.twig', [
            'edition' => $edition,
            'pdfs' => array_map(static fn ($v) => substr($v, 0, -4), array_filter(array_keys($exporters->getProvidedServices()), static fn (string $k) => str_ends_with($k, '.pdf'))),
            'breadcrumb' => $breadcrumb->generate(['editions.title', ['route' => 'admin_editions_index']], [$edition?->getName() ?? '', ['route' => 'admin_editions_show', 'routeParameters' => ['alias' => $edition?->getAlias()]]]),
        ]);
    }

    #[Route('/{alias}/bulk-status', name: 'admin_offers_bulk_status')]
    public function bulkStatus(Edition $edition): Response
    {
        $this->tagResponse(CacheTag::edition($edition));

        return $this->render('@FerienpassAdmin/page/offers/bulk_status.html.twig', [
            'edition' => $edition,
        ]);
    }

    #[Route('', name: 'admin_editions_index')]
    public function index(Breadcrumb $breadcrumb, EditionRepository $repository): Response
    {
        $qb = $repository->createQueryBuilder('i');

        // Join the holiday period so the list can be grouped (and ordered) by
        // the year it begins. Most recent year first; editions without a
        // holiday period sort last (NULL).
        $qb->leftJoin('i.tasks', 'holiday', 'WITH', "holiday.type = 'holiday'");
        $qb->addOrderBy('holiday.periodBegin', 'DESC');
        $qb->addOrderBy('i.name', 'ASC');

        return $this->render('@FerienpassAdmin/page/edition/index.html.twig', [
            'qb' => $qb,
            'createUrl' => $this->generateUrl('admin_editions_create'),
            'searchable' => ['name'],
            'breadcrumb' => $breadcrumb->generate('editions.title'),
        ]);
    }

    #[Route('/{alias}', name: 'admin_editions_show')]
    public function show(Edition $edition, Breadcrumb $breadcrumb, EntityManagerInterface $em): Response
    {
        $this->tagResponse(CacheTag::edition($edition));

        // Statistics
        $hostsCount = $edition->getHosts()->count();
        $offersCount = $edition->getOffers()->count();

        $attendancesCount = $em->getRepository(Attendance::class)->createQueryBuilder('a')
            ->select('COUNT(a.id)')
            ->innerJoin('a.offer', 'o')
            ->where('o.edition = :edition')
            ->setParameter('edition', $edition)
            ->getQuery()
            ->getSingleScalarResult();

        // Average age
        $avgAge = $em->getRepository(Attendance::class)->createQueryBuilder('a')
            ->select('AVG(a.age)')
            ->innerJoin('a.offer', 'o')
            ->where('o.edition = :edition')
            ->andWhere('a.age IS NOT NULL')
            ->setParameter('edition', $edition)
            ->getQuery()
            ->getSingleScalarResult();

        return $this->render('@FerienpassAdmin/page/edition/show.html.twig', [
            'edition' => $edition,
            'hostsCount' => $hostsCount,
            'offersCount' => $offersCount,
            'attendancesCount' => $attendancesCount,
            'avgAge' => $avgAge ? round((float) $avgAge, 1) : null,
            'offers' => $edition->getOffers(),
            'breadcrumb' => $breadcrumb->generate(['editions.title', ['route' => 'admin_editions_index']], $edition->getName()),
        ]);
    }

    #[Route('/{alias}/statistik', name: 'admin_editions_stats')]
    public function stats(Edition $edition, Request $request, Breadcrumb $breadcrumb, EditionStatsBuilder $statsBuilder, EditionRepository $editionRepository): Response
    {
        // Optional `?compare=<alias>` selects a comparison edition. We resolve
        // it here rather than in the builder so the controller stays the
        // single place URL parameters are interpreted.
        $compareTo = null;
        if ($compareAlias = $request->query->get('compare')) {
            $compareTo = $editionRepository->findOneBy(['alias' => $compareAlias]);
        }

        $this->tagResponse(CacheTag::edition($edition));
        // Tag the comparison edition too, so updates to either side invalidate
        // the side-by-side comparison view.
        if ($compareTo instanceof Edition) {
            $this->tagResponse(CacheTag::edition($compareTo));
        }

        return $this->render('@FerienpassAdmin/page/edition/stats.html.twig', [
            'edition' => $edition,
            'stats' => $statsBuilder->build($edition, $compareTo),
            'breadcrumb' => $breadcrumb->generate(['editions.title', ['route' => 'admin_editions_index']], [$edition->getName(), ['route' => 'admin_editions_show', 'routeParameters' => ['alias' => $edition->getAlias()]]]),
        ]);
    }

    #[Route('/{alias}/rundmail', name: 'admin_editions_mailing')]
    #[IsGranted('mailing', subject: 'edition')]
    public function mailing(Edition $edition, Breadcrumb $breadcrumb): Response
    {
        $this->tagResponse(CacheTag::edition($edition));

        return $this->render('@FerienpassAdmin/page/edition/mailing.html.twig', [
            'edition' => $edition,
            'breadcrumb' => $breadcrumb->generate(['editions.title', ['route' => 'admin_editions_index']], [$edition->getName(), ['route' => 'admin_editions_show', 'routeParameters' => ['alias' => $edition->getAlias()]]]),
        ]);
    }

    #[Route('/{alias}/export', name: 'admin_editions_export')]
    #[IsGranted('export', subject: 'edition')]
    public function export(Edition $edition, Breadcrumb $breadcrumb): Response
    {
        $this->tagResponse(CacheTag::edition($edition));

        return $this->render('@FerienpassAdmin/page/edition/export.html.twig', [
            'edition' => $edition,
            'breadcrumb' => $breadcrumb->generate(['editions.title', ['route' => 'admin_editions_index']], [$edition->getName(), ['route' => 'admin_editions_show', 'routeParameters' => ['alias' => $edition->getAlias()]]]),
        ]);
    }

    #[Route('/{alias}/benachrichtigungen', name: 'admin_editions_notifications')]
    public function notifications(Edition $edition, Breadcrumb $breadcrumb, Notifier $notifier, NotificationRepository $repository, TranslatorInterface $translator): Response
    {
        $types = $notifier->types();
        $overridden = $repository->findBy(['edition' => $edition]);

        $notifications = [];
        foreach ($types as $type) {
            $notificationClass = $notifier->getClass($type);
            if (!is_subclass_of($notificationClass, EditionAwareNotificationInterface::class)) {
                continue;
            }

            $notification = array_filter($overridden, static fn ($n) => $n->getType() === $type);
            $notification = reset($notification) ?: null;

            $getTransKey = static fn (string $transKey): string => \sprintf('defaults.%s.%s', $type, $transKey);

            $emailSubject = $notification?->getEmailSubject();
            if (null === $emailSubject) {
                $transKey = $getTransKey('emailSubject');
                if ($transKey !== ($val = $translator->trans($transKey, domain: 'notifications'))) {
                    $emailSubject = $val;
                }
            }

            $emailText = $notification?->getEmailText();
            if (null === $emailText) {
                $transKey = $getTransKey('emailText');
                if ($transKey !== ($val = $translator->trans($transKey, domain: 'notifications'))) {
                    $emailText = $val;
                }
            }

            $smsText = $notification?->getSmsText();
            if (null === $smsText) {
                $transKey = $getTransKey('smsText');
                if ($transKey !== ($val = $translator->trans($transKey, domain: 'notifications'))) {
                    $smsText = $val;
                }
            }

            $notifications[] = [
                'type' => $type,
                'is_overridden' => null !== $notification,
                'notification' => $notification,
                'emailSubject' => $emailSubject,
                'emailText' => $emailText,
                'smsText' => $smsText,
            ];
        }

        $this->tagResponse(CacheTag::edition($edition));

        return $this->render('@FerienpassAdmin/page/edition/notifications.html.twig', [
            'edition' => $edition,
            'notifications' => $notifications,
            'breadcrumb' => $breadcrumb->generate(['editions.title', ['route' => 'admin_editions_index']], [$edition->getName(), ['route' => 'admin_editions_show', 'routeParameters' => ['alias' => $edition->getAlias()]]]),
        ]);
    }

    #[Route('/{alias}/löschen', name: 'admin_editions_delete')]
    public function delete(Edition $edition, Request $request, EntityManagerInterface $entityManager): Response
    {
        $this->denyAccessUnlessGranted('delete', $edition);

        $form = $this->createForm(FormType::class, options: [
            'action' => $this->generateUrl('admin_editions_delete', ['alias' => $edition->getAlias()]),
        ]);

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $deletedId = $edition->getId();

            $entityManager->remove($edition);
            $entityManager->flush();

            if (TurboBundle::STREAM_FORMAT === $request->getPreferredFormat()) {
                // If the request comes from Turbo, set the content type as text/vnd.turbo-stream.html and only send the HTML to update
                $request->setRequestFormat(TurboBundle::STREAM_FORMAT);

                return $this->renderBlock('@FerienpassAdmin/page/edition/index.html.twig', 'deleted_stream', ['id' => $deletedId]);
            }

            return $this->redirectToRoute('admin_editions_index');
        }

        $this->tagResponse(CacheTag::edition($edition));

        return $this->render('@FerienpassAdmin/page/edition/delete.html.twig', [
            'item' => $edition,
            'form' => $form,
        ]);
    }

    #[Route('/{alias}/zuordnen', name: 'admin_editions_assign')]
    public function assign(Edition $edition, LockFactory $lockFactory, Breadcrumb $breadcrumb): Response
    {
        $lock = $lockFactory->createLock('lotting.'.$edition->getId());

        $this->tagResponse(CacheTag::edition($edition));

        /** @noinspection FormViewTemplate `createView()` messes ups error handling/redirect */
        return $this->render('@FerienpassAdmin/page/offers/assignAll.html.twig', [
            'locked' => !$lock->acquire(),
            'edition' => $edition,
            'breadcrumb' => $breadcrumb->generate(['editions.title', ['route' => 'admin_editions_index']], [$edition->getName() ?? '', ['route' => 'admin_editions_show', 'routeParameters' => ['alias' => $edition->getAlias()]]]),
        ]);
    }

    #[Route('/{alias}/zuordnen/auto', name: 'admin_editions_lotting')]
    public function dispatchLotting(Edition $edition, Request $request, MessageBusInterface $commandBus): Response
    {
        $this->denyAccessUnlessGranted('lotting', $edition);

        $form = $this->createFormBuilder(options: [
            'action' => $this->generateUrl('admin_editions_lotting', ['alias' => $edition->getAlias()]),
        ]);

        $form->add('requestToken', ContaoRequestTokenType::class);
        $form->add('waitlist', ChoiceType::class, [
            'label' => 'Überhang-Teilnehmende sollen…',
            'choices' => [
                '…auf die Warteliste' => 'waitlist',
                '…abgelehnt werden' => 'reject',
            ]])
        ;
        $form->add('ignoreMinParticipants', CheckboxType::class, [
            'label' => 'Auch Angebote zulosen, die die Mindestteilnahmezahl nicht erreichen',
            'required' => false,
        ]);

        $form = $form->getForm();
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $commandBus->dispatch(new LotEdition((string) $edition->getUuid(), 'waitlist' === $form->get('waitlist')->getData(), (bool) $form->get('ignoreMinParticipants')->getData()));

            if (TurboBundle::STREAM_FORMAT === $request->getPreferredFormat()) {
                $request->setRequestFormat(TurboBundle::STREAM_FORMAT);

                return $this->renderBlock('@FerienpassAdmin/page/offers/assignAll.html.twig', 'auto_success_stream');
            }

            return $this->redirectToRoute('admin_editions_assign', ['alias' => $edition->getAlias()]);
        }

        $this->tagResponse(CacheTag::edition($edition));

        return $this->render('@FerienpassAdmin/page/edition/lotting.html.twig', [
            'edition' => $edition,
            'form' => $form,
        ]);
    }
}
