<?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\Components;

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\MessengerLog;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\Participant\ParticipantInterface;
use Ferienpass\CoreBundle\Enum\AttendanceStatus;
use Ferienpass\CoreBundle\Facade\AttendanceFacade;
use Ferienpass\CoreBundle\Notification\AttendanceNotificationChecker;
use Ferienpass\CoreBundle\Repository\OfferRepositoryInterface;
use Ferienpass\CoreBundle\Repository\ParticipantRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Uid\Uuid;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\LiveComponent\ValidatableComponentTrait;
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

#[AsLiveComponent(route: 'live_component_admin')]
class AddAttendance extends AbstractController
{
    use DefaultActionTrait;
    use ValidatableComponentTrait;

    #[LiveProp]
    #[Assert\NotNull(message: 'Bitte wählen Sie ein Angebot aus.')]
    public ?OfferInterface $offer = null;

    #[LiveProp]
    #[Assert\NotNull(message: 'Bitte wählen Sie einen Teilnehmer aus.')]
    public ?ParticipantInterface $participant = null;

    #[LiveProp]
    public bool $isNewParticipant = false;

    #[LiveProp(writable: true)]
    public string $status = AttendanceStatus::WAITING;

    #[LiveProp(writable: true)]
    #[Assert\Expression(
        'value == false or this.commit == true',
        message: 'attendances.error.notifyWithoutCommit'
    )]
    public bool $notify = false;

    #[LiveProp(writable: true)]
    public bool $commit = true;

    #[LiveProp(writable: true)]
    public string $offerSearch = '';

    #[LiveProp(writable: true)]
    public string $participantSearch = '';

    #[LiveProp]
    public ?string $notificationUuid = null;

    public bool $notificationSent = false;

    public ?Attendance $success = null;

    public function __construct(
        private readonly OfferRepositoryInterface $offerRepository,
        private readonly ParticipantRepositoryInterface $participantRepository,
        private readonly AttendanceFacade $attendanceFacade,
        private readonly AttendanceNotificationChecker $notificationChecker,
    ) {
    }

    #[ExposeInTemplate]
    public function isNotifiable(): bool
    {
        return $this->notificationChecker->shouldNotify($this->status, true);
    }

    #[ExposeInTemplate]
    public function isParticipantNotifiable(): bool
    {
        if (!$this->participant instanceof ParticipantInterface) {
            return true;
        }

        return (bool) ($this->participant->getEmail() || $this->participant->getMobile());
    }

    public function updatedStatus(): void
    {
        if (!$this->isNotifiable()) {
            $this->notify = false;
        }
    }

    public function getOffers(): array
    {
        if (\strlen($this->offerSearch) < 2) {
            return [];
        }

        $qb = $this->offerRepository->createQueryBuilder('o')
            ->innerJoin('o.edition', 'e')
            ->leftJoin('o.dates', 'd');

        $qb->andWhere($qb->expr()->orX(
            $qb->expr()->like('o.name', ':query'),
            $qb->expr()->eq('o.id', ':id'),
        ))
            ->andWhere('e.archived = :archived')
            ->setParameter('archived', false)
            ->setParameter('query', '%'.$this->offerSearch.'%')
            ->setParameter('id', $this->offerSearch)
            ->addSelect('MIN(d.begin) AS HIDDEN minBegin')
            ->groupBy('o.id')
            ->orderBy('minBegin', 'DESC')
            ->setMaxResults(20);

        if (!$this->isGranted('ROLE_ADMIN')) {
            $qb->innerJoin('o.hosts', 'h')
                ->innerJoin('h.memberAssociations', 'ma')
                ->andWhere('ma.user = :user')
                ->setParameter('user', $this->getUser());
        }

        return $qb->getQuery()->getResult();
    }

    public function getParticipants(): array
    {
        if (\strlen($this->participantSearch) < 2) {
            return [];
        }

        $qb = $this->participantRepository->createQueryBuilder('p');

        $qb->andWhere($qb->expr()->orX(
            $qb->expr()->like('p.firstname', ':query'),
            $qb->expr()->like('p.lastname', ':query'),
        ))
            ->setParameter('query', '%'.$this->participantSearch.'%')
            ->setMaxResults(10);

        if (!$this->isGranted('ROLE_ADMIN')) {
            $qb->innerJoin('p.attendances', 'a')
                ->innerJoin('a.offer', 'o')
                ->innerJoin('o.hosts', 'h')
                ->innerJoin('h.memberAssociations', 'ma')
                ->andWhere('ma.user = :user')
                ->setParameter('user', $this->getUser())
                ->groupBy('p.id');
        }

        return $qb->getQuery()->getResult();
    }

    #[LiveAction]
    public function selectOffer(#[LiveArg('id')] int $id): void
    {
        $offer = $this->offerRepository->find($id);
        if (!$offer instanceof OfferInterface) {
            return;
        }

        $this->denyAccessUnlessGranted('participants.add', $offer);

        $this->offer = $offer;
        $this->offerSearch = '';
        $this->preview();
    }

    #[LiveAction]
    public function selectParticipant(#[LiveArg('id')] int $id): void
    {
        $participant = $this->participantRepository->find($id);
        if (!$participant instanceof ParticipantInterface) {
            return;
        }

        $this->participant = $participant;
        $this->participantSearch = '';

        $this->preview();
    }

    #[LiveAction]
    public function newParticipant(): void
    {
        $this->isNewParticipant = true;
    }

    #[LiveAction]
    public function unselectOffer(): void
    {
        $this->offer = null;
    }

    #[LiveAction]
    public function unselectParticipant(): void
    {
        $this->participant = null;
    }

    #[LiveAction]
    public function preview(): void
    {
        $this->success = null;

        if (!$this->offer || !$this->participant) {
            return;
        }

        $preview = $this->attendanceFacade->preview($this->offer, $this->participant);
        $this->status = $preview->getStatus();
    }

    #[LiveAction]
    public function submit(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, #[LiveArg] string $redirect = 'new'): ?Response
    {
        $this->validate();

        if (!$this->offer instanceof OfferInterface) {
            throw $this->createAccessDeniedException();
        }

        $this->denyAccessUnlessGranted('participants.add', $this->offer);

        $this->notificationUuid = (string) Uuid::v7();
        if ($this->notify) {
            $masterLog = new MessengerLog(static::class, uuid: Uuid::fromString($this->notificationUuid));
            $entityManager->persist($masterLog);
        }

        if ($this->isNewParticipant && $this->participant instanceof ParticipantInterface) {
            $entityManager->persist($this->participant);
        }

        $this->success = $this->attendanceFacade->create(offer: $this->offer, participant: $this->participant, status: $this->status, notify: $this->notify, commit: $this->commit, group: $this->notificationUuid ? Uuid::fromString($this->notificationUuid) : null);
        $this->isNewParticipant = false;

        $this->notificationSent = $this->notify;

        if ('participant' === $redirect) {
            if ($this->notificationSent) {
                $this->addFlash('mail_sent', ['type' => 'mail_sent', 'title' => 'Teilnehmer:in wurde benachrichtigt', 'message' => $this->participant->getName().' wurde über die Änderung des Teilnahmestatus informiert', 'dismissAfter' => 0, 'primaryActionLink' => $urlGenerator->generate('admin_outbox', ['group' => $this->notificationUuid]), 'primaryActionTitle' => 'Nachricht anzeigen']);
            }

            return $this->redirectToRoute('admin_participants_attendances', ['uuid' => $this->success->getParticipant()->getUuid()->toBase58()]);
        }

        if ('offer' === $redirect) {
            if ($this->notificationSent) {
                $this->addFlash('mail_sent', ['type' => 'mail_sent', 'title' => 'Teilnehmer:in wurde benachrichtigt', 'message' => $this->participant->getName().' wurde über die Änderung des Teilnahmestatus informiert', 'dismissAfter' => 0, 'primaryActionLink' => $urlGenerator->generate('admin_outbox', ['group' => $this->notificationUuid]), 'primaryActionTitle' => 'Nachricht anzeigen']);
            }

            return $this->redirectToRoute('admin_offers_participants', ['uuid' => $this->success->getOffer()->getUuid()->toBase58()]);
        }

        // Reset simple fields for a fresh entry while keeping current offer/participant selection
        $this->status = AttendanceStatus::WAITING;
        $this->notify = false;
        $this->commit = true;

        return null;
    }
}
