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

use BookiFlex\Core\Api\Transformer\ReservationGroupTransformer;
use BookiFlex\Core\Model\Reservation\Reservation;
use BookiFlex\Core\Service\ConfigurationService;
use BookiFlex\Core\Service\Reservation\ReservationDetails;
use BookiFlex\Extensions\Notification\Contract\NotificationEventProviderInterface;
use BookiFlex\Extensions\Notification\Model\Recipient;
use BookiFlex\Extensions\Notification\Service\Configuration;
use Brick\Math\Exception\MathException;

defined( 'ABSPATH' ) || exit;

class NotificationContextProvider
{
    private Reservation $reservation;
    private string $notificationEvent;

    /** @var NotificationEventProviderInterface[] */
    private array $eventProviders = [];

    public function __construct(
        private readonly ReservationDetails $reservationDetails,
        private readonly ConfigurationService $configurationService,
    ) {
    }

    public function addEventProvider(NotificationEventProviderInterface $provider): void
    {
        $this->eventProviders[] = $provider;
    }

    /**
     * Set a reservation for getting data
     *
     * @param Reservation|string $reservation Reservation object or reservation sid
     * @param string $notificationEvent
     * @return self
     */
    public function setReservation(Reservation|string $reservation, string $notificationEvent): self
    {
        $this->notificationEvent = $notificationEvent;
        if ($reservation instanceof Reservation) {
            $this->reservation = $reservation;
        } else {
            $reservations      = $this->reservationDetails->getReservationGroup($reservation);
            $this->reservation = $reservations[0];
        }

        return $this;
    }

    /**
     * Change a reservation status (for ex: when we need test)
     *
     * @return self
     */
    public function changeReservationStatus(): self
    {
        try {
            $provider = $this->findProvider($this->notificationEvent);
            $provider?->prepareForPreview($this->notificationEvent, $this->reservation);
        } catch (\Exception $e) {
            //
        }

        return $this;
    }

    /**
     * Return context of the reservation
     *
     * @return array
     * @throws MathException
     * @throws \DateMalformedStringException
     */
    public function getContext(): array
    {
        $paymentAllocations = $this->reservationDetails->getPaymentAllocations([$this->reservation]);
        $transformer = new ReservationGroupTransformer();

        ['isGroup' => $isGroup, 'group' => $group, 'reservations' => $reservations, 'totals' => $totals] = $transformer->transform(
            [$this->reservation],
            $this->reservationDetails->getRatePlanNames(),
            $this->reservationDetails->getAccommodationTypes(),
            $paymentAllocations,
        );

        $context = [
            'isGroup' => $isGroup,
            'group' => $group,
            'reservation' => $reservations[0],
            'totals' => $totals,
            'notification' => [
                'greeting'  => $this->applyPlaceholders(Configuration::getMailGreeting()),
                'signature' => Configuration::getMailSignature(),
                'logo'      => Configuration::getMailLogo(),
            ],
            'contacts' => [
                'name'        => $this->configurationService->getHotelName(),
                'address'     => $this->configurationService->getAddress(),
                'phone'       => $this->configurationService->getPhone(),
                'email'       => $this->configurationService->getMainEmail(),
                'bankAccount' => $this->configurationService->getBankAccount(),
                'url'         => $this->configurationService->getSiteUrl(),
            ],
            'policies' => [
                'checkInTime'  => $this->configurationService->getCheckInTime(),
                'checkOutTime' => $this->configurationService->getCheckOutTime(),
            ],
        ];

        foreach ($this->eventProviders as $provider) {
            $context = $provider->modifyContext($context, $this->notificationEvent);
        }

        return $context;
    }

    public function getTemplate(Recipient $recipient): string
    {
        $provider = $this->findProvider($this->notificationEvent);
        $language = $this->resolveLanguage($recipient);

        return $provider?->getTemplateName($this->notificationEvent, $recipient, $language) ?? '';
    }

    private function resolveLanguage(Recipient $recipient): string
    {
        if ($recipient->isEqual(Recipient::Manager)) {
            $lang = substr(get_locale(), 0, 2) ?: 'en';
        } else {
            $lang = $this->reservation->getLanguage() ?: 'en';
        }

        return $this->isSupportedLanguage($lang) ? $lang : 'en';
    }

    private function isSupportedLanguage(string $lang): bool
    {
        return in_array($lang, ConfigurationService::SUPPORTED_LANGUAGES, true);
    }

    public function getSubject(string $event, Recipient $recipient): string
    {
        $provider = $this->findProvider($event);
        $subject = $provider?->getSubject($event, $recipient)
            ?: __('New mail about your reservation #{{number}}', 'bookiflex');

        return $this->prepareSubject($subject);
    }

    /** @return string[] */
    public function getSupportedEvents(): array
    {
        $events = [];
        foreach ($this->eventProviders as $provider) {
            $events = array_merge($events, $provider->supportedEvents());
        }

        return array_unique($events);
    }

    private function findProvider(string $event): ?NotificationEventProviderInterface
    {
        foreach ($this->eventProviders as $provider) {
            if (in_array($event, $provider->supportedEvents(), true)) {
                return $provider;
            }
        }

        return null;
    }

    private function prepareSubject(mixed $subject): string
    {
        return $this->applyPlaceholders($subject);
    }

    private function applyPlaceholders(string $text): string
    {
        if ($text === '') {
            return $text;
        }

        $placeholders = [
            '{{number}}'    => $this->reservation->getNumber(),
            '{{firstName}}' => $this->reservation->getCustomer()->getFirstName(),
            '{{lastName}}'  => $this->reservation->getCustomer()->getLastName(),
        ];

        return str_replace(array_keys($placeholders), array_values($placeholders), $text);
    }
}
