<?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\CmsBundle\Application\ExclusionReasons;

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Ferienpass\CmsBundle\Application\ExclusionReasonEvent;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\OfferDate;
use Ferienpass\CoreBundle\Enum\AttendanceStatus;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Translation\TranslatableMessage;

#[AsEventListener(priority: 70)]
class OverlappingDatesListener
{
    public function __construct(private readonly EntityManagerInterface $doctrine)
    {
    }

    public function __invoke(ExclusionReasonEvent $event): void
    {
        $offer = $event->getOffer();
        $participant = $event->getParticipant();

        /** @var EntityRepository $repo */
        $repo = $this->doctrine->getRepository(OfferDate::class);

        // All dates of offers the participant is participating (expect current offer)
        /** @var OfferDate[] $participatingDates */
        $participatingDates = $repo->createQueryBuilder('d')
            ->innerJoin(OfferInterface::class, 'o', Join::WITH, 'o.id = d.offer')
            ->innerJoin(Attendance::class, 'a', Join::WITH, 'a.offer = o.id')
            ->where('a.participant = :participant')
            ->andWhere('a.offer <> :offer')
            ->andWhere('a.status <> :status_withdrawn')
            ->andWhere('a.status <> :status_unfulfilled')
            ->andWhere('a.status <> :status_rejected')
            ->setParameter('participant', $participant->getId(), Types::INTEGER)
            ->setParameter('offer', $offer->getId(), Types::INTEGER)
            ->setParameter('status_withdrawn', AttendanceStatus::WITHDRAWN, Types::STRING)
            ->setParameter('status_unfulfilled', AttendanceStatus::UNFULFILLED, Types::STRING)
            ->setParameter('status_rejected', AttendanceStatus::REJECTED, Types::STRING)
            ->getQuery()
            ->getResult()
        ;

        // Only dates that are no longer than 1 day
        $participatingDates = array_filter($participatingDates, static fn (OfferDate $d) => $d->getBegin() && $d->getEnd() && $d->getEnd()->diff($d->getBegin())->days <= 1);

        // Walk every date the participant is already attending to…
        foreach ($offer->getDates() as $currentDate) {
            // If current date is longer than 1 day, it shall not block booking new offers
            if ($currentDate->getBegin() && $currentDate->getEnd() && $currentDate->getEnd()->diff($currentDate->getBegin())->days > 1) {
                continue;
            }

            foreach ($participatingDates as $participatingDate) {
                // …check for an overlap
                if (($participatingDate->getEnd() > $currentDate->getBegin()) && ($currentDate->getEnd() > $participatingDate->getBegin())) {
                    throw new OverlappingDates($offer, $participant, new TranslatableMessage('ineligible.overlap', ['name' => $participant->getFirstname(), 'offer' => $participatingDate->getOffer()->getName()]));
                }
            }
        }
    }
}
