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

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CoreBundle\Entity\MessengerLog;
use Ferienpass\CoreBundle\Repository\MessengerLogRepository;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Level;
use Monolog\LogRecord;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;

class MessengerLogHandler extends AbstractProcessingHandler
{
    public function __construct(private readonly MessengerLogRepository $logs, private readonly EntityManagerInterface $entityManager, int|string|Level $level = Level::Debug, bool $bubble = true)
    {
        parent::__construct($level, $bubble);
    }

    protected function write(LogRecord $record): void
    {
        $context = $record->context;
        if (!($context['uuid'] ?? null) instanceof Uuid) {
            return;
        }

        $logEntity = $this->logs->findOneBy(['uuid' => $context['uuid']]);
        if (null !== $logEntity) {
            return;
        }

        $related = [];
        foreach ($context['related'] as $entity => $ids) {
            foreach ((array) $ids as $id) {
                if (null !== $resolved = $this->resolveRelated($entity, $id)) {
                    $related[] = $resolved;
                }
            }
        }

        $logEntity = new MessengerLog($context['class'], $related, uuid: $context['uuid']);

        if ($context['group'] ?? null) {
            $logEntity->setGroup($this->logs->findOneBy(['uuid' => $context['group']]));
        }

        $this->entityManager->persist($logEntity);

        if ($this->entityManager->getConnection()->isTransactionActive()) {
            return;
        }

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

    /**
     * Resolve a related entity from the (class => id) pair carried in the log
     * context. The id may be:
     *  - a scalar primary key (e.g. an auto-increment integer), or
     *  - an RFC4122 UUID, which is either the entity's own primary key (e.g.
     *    Wishlist, ConsentForm) or a separate unique `uuid` column on an
     *    int-keyed entity (e.g. User, Offer, Edition).
     */
    private function resolveRelated(string $entity, int|string $id): ?object
    {
        if (!\is_string($id) || !str_contains($id, '-')) {
            // Scalar primary key.
            return $this->entityManager->getReference($entity, $id);
        }

        if (!Uuid::isValid($id)) {
            return null;
        }

        $uuid = Uuid::fromString($id);
        $metadata = $this->entityManager->getClassMetadata($entity);

        // The UUID is the entity's own primary key.
        if (UuidType::NAME === $metadata->getTypeOfField($metadata->getSingleIdentifierFieldName())) {
            return $this->entityManager->getReference($entity, $uuid);
        }

        // Int-keyed entity with a separate, unique `uuid` column.
        // Use the resolved concrete class (the related key may be an interface
        // such as OfferInterface, mapped via resolve_target_entities).
        if ($metadata->hasField('uuid')) {
            return $this->entityManager->getRepository($metadata->getName())->findOneBy(['uuid' => $uuid]);
        }

        return null;
    }
}
