<?php

/*
 * This file is part of the Serenity package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Command;

use CpCreation\VitiCore\Contact\Repository\ContactRepository;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\EntityManagerInterface;
use Nelmio\Alice\FilesLoaderInterface;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Finder\Finder;

#[AsCommand(name: 'viti-core:fixture-dev:create')]
class FixtureDevCommand
{
    public function __construct(
        private ContactRepository $contactRepository,
        private readonly FilesLoaderInterface $fixturesLoader,
        private readonly EntityManagerInterface $entityManager,
    ) {
    }

    public function __invoke(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        $contact = $this->contactRepository->findOne();

        if (null !== $contact) {
            $io->success('Fixtures déjà chargées (Contact trouvé).');

            return Command::SUCCESS;
        }

        $fixturesDir = \dirname(__DIR__, 4).'/fixtures';

        $finder = Finder::create()
            ->files()
            ->depth('== 0')
            ->name(['*.yaml', '*.yml'])
            ->sortByName()
            ->in($fixturesDir);

        $files = [];
        foreach ($finder as $file) {
            $files[] = $file->getRealPath();
        }

        if ([] === $files) {
            $io->warning(sprintf('Aucune fixture Alice trouvée dans "%s".', $fixturesDir));

            return Command::SUCCESS;
        }

        $this->disableFixtureUnsafeDoctrineListeners();
        $this->purgeDatabase();

        $objects = $this->fixturesLoader->loadFiles($files)->getObjects();
        $persistedObjects = $this->persistGeneratedObjects($objects);

        $io->success(sprintf(
            'First launch Dev: %d fichier(s) de fixtures chargé(s), %d objet(s) généré(s), %d objet(s) persisté(s).',
            \count($files),
            \count($objects),
            $persistedObjects,
        ));

        return Command::SUCCESS;
    }

    /**
     * @param array<string, object> $objects
     */
    private function persistGeneratedObjects(array $objects): int
    {
        $persistedObjects = 0;
        $metadataFactory = $this->entityManager->getMetadataFactory();

        foreach ($objects as $object) {
            if ($metadataFactory->isTransient($object::class)) {
                continue;
            }

            $this->initializeUuidIdentifier($object);
            $this->entityManager->persist($object);
            ++$persistedObjects;
        }

        $this->entityManager->flush();

        return $persistedObjects;
    }

    private function purgeDatabase(): void
    {
        $purger = new ORMPurger($this->entityManager);
        $purger->purge();
        $this->entityManager->clear();
    }

    private function disableFixtureUnsafeDoctrineListeners(): void
    {
        $eventManager = $this->entityManager->getEventManager();
        $unsafeListenerPrefixes = [
            'ApiPlatform\\Symfony\\Doctrine\\EventListener\\',
            'DH\\Auditor\\',
            'Symfony\\UX\\Turbo\\Doctrine\\',
        ];

        foreach ($eventManager->getAllListeners() as $eventName => $listeners) {
            foreach ($listeners as $listener) {
                foreach ($unsafeListenerPrefixes as $unsafeListenerPrefix) {
                    if (str_starts_with($listener::class, $unsafeListenerPrefix)) {
                        $eventManager->removeEventListener($eventName, $listener);
                        break;
                    }
                }
            }
        }
    }

    private function initializeUuidIdentifier(object $object): void
    {
        $reflectionClass = new \ReflectionClass($object);

        do {
            if (!$reflectionClass->hasProperty('id')) {
                $reflectionClass = $reflectionClass->getParentClass();
                continue;
            }

            $property = $reflectionClass->getProperty('id');
            $type = $property->getType();
            $typeName = $type instanceof \ReflectionNamedType ? $type->getName() : null;

            if (UuidInterface::class !== $typeName || $property->isInitialized($object)) {
                return;
            }

            $property->setValue($object, Uuid::uuid4());

            return;
        } while (false !== $reflectionClass);
    }
}
