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

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\MessengerLog;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Enum\AttendanceStatus;
use Ferienpass\CoreBundle\Facade\AttendanceFacade;
use Ferienpass\CoreBundle\Notification\AttendanceNotificationChecker;
use Ferienpass\CoreBundle\Repository\OfferRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
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')]
final class TransferParticipants extends AbstractController
{
    use DefaultActionTrait;
    use ValidatableComponentTrait;

    #[LiveProp]
    public OfferInterface $sourceOffer;

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

    #[LiveProp(writable: true)]
    public string $direction = 'to'; // 'to' means FROM source TO target, 'from' means FROM target TO source

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

    #[LiveProp(writable: true)]
    public array $selectedAttendanceIds = [];

    #[LiveProp(writable: true, onUpdated: 'updatedStatus')]
    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]
    public ?string $notificationUuid = null;

    public bool $notificationSent = false;

    public ?OfferInterface $successOffer = null;

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

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

    #[LiveAction]
    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');
        $qb
            ->innerJoin('o.edition', 'e')
            ->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', is_numeric($this->offerSearch) ? (int) $this->offerSearch : 0)
        ;

        if ('to' === $this->direction) {
            $qb->innerJoin('o.dates', 'dfuture')
                ->andWhere('dfuture.begin > :now')
                ->setParameter('now', new \DateTimeImmutable());
        }

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

        $qb->andWhere('o.id <> :sourceId')
            ->setParameter('sourceId', $this->sourceOffer->getId());

        $qb->groupBy('o.id');
        $qb->setMaxResults(20);

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

    #[LiveAction]
    public function selectOffer(#[LiveArg] int $id): void
    {
        $this->targetOffer = $this->offerRepository->find($id);
        $this->offerSearch = '';
    }

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

    #[LiveAction]
    public function swapDirection(): void
    {
        $this->direction = 'to' === $this->direction ? 'from' : 'to';
        $this->selectedAttendanceIds = [];
    }

    #[ExposeInTemplate]
    public function getSelectedAttendances(): array
    {
        return $this->entityManager->getRepository(Attendance::class)->findBy(['id' => $this->selectedAttendanceIds]);
    }

    #[ExposeInTemplate]
    public function getFromOffer(): ?OfferInterface
    {
        return 'to' === $this->direction ? $this->sourceOffer : $this->targetOffer;
    }

    #[ExposeInTemplate]
    public function getToOffer(): ?OfferInterface
    {
        return 'to' === $this->direction ? $this->targetOffer : $this->sourceOffer;
    }

    public function isParticipantAlreadyInTarget(Attendance $attendance): bool
    {
        $toOffer = $this->getToOffer();
        if (!$toOffer) {
            return false;
        }

        foreach ($toOffer->getAttendances() as $toAttendance) {
            if ($toAttendance->getParticipant() === $attendance->getParticipant()) {
                return true;
            }
        }

        return false;
    }

    #[LiveAction]
    public function toggleAll(): void
    {
        $fromOffer = $this->getFromOffer();
        if (!$fromOffer) {
            return;
        }

        $transferableIds = [];
        foreach ($fromOffer->getAttendances() as $attendance) {
            if (!$this->isParticipantAlreadyInTarget($attendance)) {
                $transferableIds[] = (string) $attendance->getId();
            }
        }

        if (\count($this->selectedAttendanceIds) >= \count($transferableIds) && \count($transferableIds) > 0) {
            $this->selectedAttendanceIds = [];
        } else {
            $this->selectedAttendanceIds = $transferableIds;
        }
    }

    #[LiveAction]
    public function submit(): void
    {
        $fromOffer = $this->getFromOffer();
        $toOffer = $this->getToOffer();

        if (!$fromOffer || !$toOffer) {
            throw new \RuntimeException('Source or target offer not found.');
        }

        $attendances = $this->entityManager->getRepository(Attendance::class)->findBy(['id' => $this->selectedAttendanceIds]);

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

        foreach ($attendances as $attendance) {
            if ($attendance->getOffer() !== $fromOffer) {
                continue;
            }

            if ($this->isParticipantAlreadyInTarget($attendance)) {
                continue;
            }

            $this->attendanceFacade->create(
                offer: $toOffer,
                participant: $attendance->getParticipant(),
                status: $this->status,
                notify: $this->notify,
                commit: $this->commit,
                group: $this->notificationUuid ? Uuid::fromString($this->notificationUuid) : null
            );

            $this->entityManager->remove($attendance);
        }

        $this->notificationSent = $this->notify;
        $this->successOffer = $toOffer;
        $this->entityManager->flush();
    }
}
