<?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\CoreBundle\Notification;

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Host;
use Ferienpass\CoreBundle\Entity\Offer\Base as OfferBase;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\Participant\BaseParticipant;
use Ferienpass\CoreBundle\Entity\Participant\ParticipantInterface;
use Ferienpass\CoreBundle\Entity\Payment;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Twig\SandboxedTextRenderer;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Contracts\Service\Attribute\SubscribedService;
use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Twig\Error\Error;

abstract class AbstractNotification extends Notification implements ServiceSubscriberInterface
{
    use AttachmentTrait;
    use EditionEntityTrait;
    use LoggableNotificationTrait;
    use ReplyToTrait;
    use ServiceMethodsSubscriberTrait;

    private string $smsText = '';

    public function getSubject(): string
    {
        try {
            return $this->sandboxedTextRenderer()->render(parent::getSubject(), $this->getContext());
        } catch (Error) {
            return parent::getSubject();
        }
    }

    public function getContent(): string
    {
        try {
            return $this->sandboxedTextRenderer()->render(parent::getContent(), $this->getContext());
        } catch (Error) {
            return parent::getContent();
        }
    }

    /**
     * @return $this
     */
    public function smsText(string $smsText): static
    {
        $this->smsText = $smsText;

        return $this;
    }

    public function getSmsText(): string
    {
        try {
            return $this->sandboxedTextRenderer()->render($this->smsText, $this->getContext());
        } catch (Error) {
            return $this->smsText;
        }
    }

    public function getReplyTo(): ?string
    {
        return $this->replyTo;
    }

    /**
     * The context is used to replace placeholders in texts and templates.
     */
    public function getContext(): array
    {
        return [
            'baseUrl' => ($this->requestStack()->getCurrentRequest()?->getSchemeAndHttpHost() ?? '').($this->requestStack()->getCurrentRequest()?->getBaseUrl() ?? ''),
        ];
    }

    public static function getAvailableTokens(): array
    {
        return ['baseUrl'];
    }

    /**
     * Build a notification populated with hardcoded sample data for previewing
     * texts and rendering the email mock. Does NOT touch the database — so the
     * preview works on a fresh installation where no entities exist yet.
     */
    public function createMock(string $offerClass = OfferBase::class, string $participantClass = BaseParticipant::class): static
    {
        $notification = clone $this;

        $user = self::sampleUser();
        $offer = self::sampleOffer($offerClass);
        $participant = self::sampleParticipant($participantClass);

        if (method_exists($notification, 'user')) {
            $notification->user($user);
        }
        if (method_exists($notification, 'offer')) {
            $notification->offer($offer);
        }
        if (method_exists($notification, 'host')) {
            $notification->host(self::sampleHost());
        }
        if (method_exists($notification, 'payment')) {
            $notification->payment(new Payment(null, '2026/0001', $user));
        }
        if (method_exists($notification, 'attendance')) {
            $attendances = \in_array('attendances', $notification::getAvailableTokens(), true) ? 2 : 1;
            for ($i = 0; $i < $attendances; ++$i) {
                $notification->attendance(new Attendance($offer, $participant));
            }
        }
        if (method_exists($notification, 'token')) {
            $notification->token('example-token');
        }
        if (method_exists($notification, 'inviteeEmail')) {
            $notification->inviteeEmail('example@example.org');
        }

        return $notification;
    }

    private static function sampleUser(): User
    {
        $user = new User();
        $user->setFirstname('Erika');
        $user->setLastname('Mustermann');
        $user->setEmail('erika.mustermann@example.com');
        $user->setPhone('+49 30 1234567');
        self::assignSampleId($user, 1);

        return $user;
    }

    /**
     * @param class-string<OfferInterface> $offerClass
     */
    private static function sampleOffer(string $offerClass = OfferBase::class): OfferInterface
    {
        $offer = new $offerClass();
        $offer->setName('Schwimmkurs für Anfänger');
        $offer->setDescription('Ein Beispielangebot zur Veranschaulichung.');
        self::assignSampleId($offer, 1);

        return $offer;
    }

    /**
     * @param class-string<ParticipantInterface> $participantClass
     */
    private static function sampleParticipant(string $participantClass = BaseParticipant::class): ParticipantInterface
    {
        $participant = new $participantClass();
        $participant->setFirstname('Lena');
        $participant->setLastname('Mustermann');
        self::assignSampleId($participant, 1);

        return $participant;
    }

    private static function sampleHost(): Host
    {
        $host = new Host();
        $host->setName('Volkshochschule Beispielstadt');
        $host->setEmail('info@example.com');
        self::assignSampleId($host, 1);

        return $host;
    }

    /**
     * Force an integer `id` on a sample entity so consumers that key arrays by
     * id (e.g. `$arr[$entity->getId()]`) end up with int keys — not the empty
     * string PHP coerces null into, which would later break `array_merge(...)`.
     */
    private static function assignSampleId(object $entity, int $id): void
    {
        $class = new \ReflectionClass($entity);
        while ($class && !$class->hasProperty('id')) {
            $class = $class->getParentClass() ?: null;
        }
        $class?->getProperty('id')->setValue($entity, $id);
    }

    #[SubscribedService]
    private function doctrine(): EntityManagerInterface
    {
        return $this->container->get(__METHOD__);
    }

    #[SubscribedService]
    private function requestStack(): RequestStack
    {
        return $this->container->get(__METHOD__);
    }

    #[SubscribedService]
    private function sandboxedTextRenderer(): SandboxedTextRenderer
    {
        return $this->container->get(__METHOD__);
    }
}
