<?php
/**
 * BookiFlex Notification Extension
 *
 * @package    BookiFlex
 * @subpackage Notification
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */


declare(strict_types=1);
namespace BookiFlex\Extensions\Notification\Listener;

use BookiFlex\Core\Model\Reservation\Event\ReservationStatusWasChanged;
use BookiFlex\Core\Model\Reservation\Event\ReservationWasCreated;
use BookiFlex\Core\Model\Reservation\Reservation;
use BookiFlex\Core\Repository\ReservationRepository;
use BookiFlex\Core\Service\ConfigurationService;
use BookiFlex\Extensions\Notification\Model\Notification;
use BookiFlex\Extensions\Notification\Model\NotificationEvent;
use BookiFlex\Extensions\Notification\Model\NotificationStatus;
use BookiFlex\Extensions\Notification\Model\Recipient;
use BookiFlex\Extensions\Notification\Repository\NotificationRepository;
use BookiFlex\Extensions\Notification\Service\Configuration;
use BookiFlex\Extensions\Notification\Service\EventNormalizer;
use BookiFlex\Extensions\Notification\Service\NotificationSnapshotService;
use Cycle\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Uid\Uuid;

defined( 'ABSPATH' ) || exit;

class ReservationSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private readonly ReservationRepository $reservationRepository,
        private readonly ConfigurationService $configurationService,
        private readonly EntityManagerInterface $entityManager,
        private readonly EventNormalizer $eventNormalizer,
        private readonly NotificationRepository $notificationRepository,
        private readonly NotificationSnapshotService $snapshotService,
        private readonly ?LoggerInterface $logger = null
    ) {
    }

    public static function getSubscribedEvents(): array
    {
        return [
            ReservationWasCreated::class       => 'onReservationWasCreated',
            ReservationStatusWasChanged::class => 'onReservationStatusWasChanged',
        ];
    }

    public function onReservationWasCreated(ReservationWasCreated $event): void
    {
        $this->logger?->debug('Reservation created', ['sid' => $event->getIdentifier(), ...$event->getPayload()]);
        $this->makeNotification($event);
    }

    public function onReservationStatusWasChanged(ReservationStatusWasChanged $event): void
    {
        // Handle only last status. Because the event can be triggered multiple times (for example if enabled Auto confirmation setting).
        if (!$event->isLastEvent()) {
            return;
        }

        $payload = $event->getPayload();

        // Check if the payload contains the 'id' key. If 'id' is not set, it's a new reservation.
        if (!isset($payload['id'])) {
            return;
        }

        $this->logger?->debug('Reservation status changed', ['payload' => $payload]);

        /** @var Reservation|null $reservation */
        $reservation = $this->reservationRepository->findOne(['sid' => $event->getIdentifier()]);
        if ($reservation) {
            $this->cancelPendingNotifications($reservation);
        }

        $this->makeNotification($event);
    }

    /**
     * Cancel all pending notifications for a reservation when its status changes.
     * This ensures delayed notifications that are no longer relevant are not sent.
     */
    private function cancelPendingNotifications(Reservation $reservation): void
    {
        $pending = $this->notificationRepository->findPendingByReservation($reservation);
        if (empty($pending)) {
            return;
        }

        $this->logger?->debug('Cancelling pending notifications', [
            'reservation_sid' => $reservation->getSid(),
            'count'           => count($pending),
        ]);

        foreach ($pending as $notification) {
            $notification->markAsCancelled();
            $this->entityManager->persist($notification);
        }

        $this->entityManager->run();
    }

    private function makeNotification(ReservationWasCreated|ReservationStatusWasChanged $event): void
    {
        $normalizedEvent = $this->normalizeEvent($event);
        if (empty($normalizedEvent)) {
            return;
        }

        /** @var Reservation|null $reservation */
        $reservation = $this->reservationRepository->findOne(['sid' => $event->getIdentifier()]);
        if (!$reservation) {
            return;
        }

        $stakeholders = $this->getStakeholders($normalizedEvent);
        if (empty($stakeholders)) {
            return;
        }

        foreach ($stakeholders as $stakeholder) {
            $sendTo = $stakeholder->isEqual(Recipient::Client)
                ? $reservation->getCustomer()->getEmail()
                : $this->configurationService->getMainEmail();

            $notification = new Notification(
                (string) Uuid::v4(),
                $normalizedEvent->value,
                $stakeholder,
                $sendTo,
                'email',
                new \DateTimeImmutable(),
                $reservation,
            );

            $snapshot = $this->snapshotService->build($reservation, $normalizedEvent->value, $stakeholder);
            if ($snapshot !== null) {
                $notification->setDetails(['snapshot' => $snapshot]);
            }

            $this->entityManager->persist($notification);
        }

        // Notifications are persisted and will be picked up by the polling job
        $this->entityManager->run();
    }

    private function normalizeEvent(object $event): ?NotificationEvent
    {
        $normalizedEvents = $this->eventNormalizer->fromDomainEvent($event);
        if (empty($normalizedEvents)) {
            return null;
        }

        // Assuming the first normalized event is the one we want to use
        return $normalizedEvents[0];
    }

    /**
     * @param NotificationEvent $notificationEvent
     * @return Recipient[]
     */
    private function getStakeholders(NotificationEvent $notificationEvent): array
    {
        $stakeholders = [];
        if (in_array($notificationEvent->value, Configuration::getCustomerEvents(), true)) {
            $stakeholders[] = Recipient::Client;
        }

        if (in_array($notificationEvent->value, Configuration::getManagerEvents(), true)) {
            $stakeholders[] = Recipient::Manager;
        }

        return $stakeholders;
    }
}
