<?php

namespace AutoAdmin\Bundle\Admin\Factory;

use AutoAdmin\Bundle\Admin\Helper\EntityHelper;
use AutoAdmin\Bundle\Content\Entity\AbstractTranslatable;
use AutoAdmin\Bundle\Content\Entity\Locale;
use AutoAdmin\Bundle\Content\Model\LocaleInterface;
use AutoAdmin\Bundle\Content\Model\SiteInterface;
use AutoAdmin\Bundle\Content\Model\TranslatableInterface;
use AutoAdmin\Bundle\Content\Model\TranslationInterface;
use AutoAdmin\Bundle\Content\Site\SiteContextHelper;
use AutoAdmin\Bundle\Content\Translation\EntityTranslator;
use AutoAdmin\Bundle\Core\Doctrine\DoctrineInterfaceAliasResolver;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use ReflectionClass;
use RuntimeException;

class ContentFactory
{
    public function __construct(
        protected EntityTranslator $entityTranslator,
        protected EntityHelper $entityHelper,
        protected EntityManagerInterface $em,
        protected SiteContextHelper $siteContextHelper,
        protected DoctrineInterfaceAliasResolver $doctrineInterfaceAliasResolver,
    ) {
    }

    public function createEntity(string $class, $locale = null): object
    {
        $refl = $this->checkClass($class);

        if ($refl->isInterface()) {
            $class = $this->doctrineInterfaceAliasResolver->getClass($class);;
        }

        $entity = new $class();

        if ($entity instanceof SiteInterface) {
            $entity->setSite($this->siteContextHelper->getCurrentContext()->getSite());
        }

        if ($refl->isSubclassOf(AbstractTranslatable::class)) {
            $entity = $this->createTranslationForEntity($entity, $locale);
        }

        return $entity;
    }

    private function checkClass(string $class): ReflectionClass
    {
        $refl = new ReflectionClass($class);

        if (!$refl->isInterface() && !class_exists($class)) {
            throw new RuntimeException(
                sprintf("Class does not exist \"%s\" please implement it", $class)
            );
        }

        if ($refl->isInterface() && !$this->doctrineInterfaceAliasResolver->hasClass($class)) {
            throw new RuntimeException(
                sprintf("Interface has no doctrine alias mapping.", $class)
            );
        }

        return $refl;
    }

    public function createEntityWithAllTrans(string $class): ?object
    {
        $entity = $this->createEntity($class);
        $entityTransLocales = $entity->getTranslations()->map(fn($t) => $t->getLocale())->toArray();

        $currentSite = $this->siteContextHelper->getCurrentContext()->getSite();
        $siteConfigs = $this->siteContextHelper->getSiteLocalizedDomains($currentSite);
        foreach ($siteConfigs as $siteConfig) {
            if (in_array($siteConfig->getLocale(), $entityTransLocales)) {
                continue;
            }
            $this->createTranslationForEntity($entity, $siteConfig->getLocale());
        }

        $this->affectCurrentTranslation($entity);

        return $entity;
    }

    public function createTranslationForEntity(
        TranslatableInterface $translatable,
        $locale = null
    ): ?TranslatableInterface {
        $translation = $this->createTranslationEntityForLocale(get_class($translatable), $locale);
        // here we should merge translation with existing ones, maybe throw an exception if needed
        $translation->setTranslatable($translatable);
        $translatable->addTranslation($translation);

        $this->affectCurrentTranslation($translatable);

        return $translatable;
    }

    public function createTranslationEntityForLocale(string $translatableFqcn, $locale = null): ?TranslationInterface
    {
        $locale = $this->resolveLocale($locale);

        if (empty($locale)) {
            throw new RuntimeException("Locale is null, cannot create translation");
        }

        $translationClassSuffix = 'Translation';
        if (substr($translatableFqcn, -strlen($translationClassSuffix)) !== $translationClassSuffix) {
            $translatableFqcn .= $translationClassSuffix;
        }

        $translatedClassName = $translatableFqcn;
        if (!class_exists($translatedClassName)) {
            throw new RuntimeException(
                sprintf("Class does not exist \"%s\" please implement it", $translatedClassName)
            );
        }

        /** @var TranslationInterface $translation */
        $translation = new $translatedClassName();
        $translation->setLocale($locale);

        return $translation;
    }

    private function resolveLocale($locale)
    {
        if (!is_null($locale) && !is_string($locale) && !$locale instanceof LocaleInterface) {
            throw new Exception('Locale input seems incorrect. String or a LocaleInterface expected.');
        }

        if (is_null($locale)) {
            $locale = $this->entityTranslator->getLocale();
        }

        if (is_string($locale)) {
            $locale = $this->em->getRepository(Locale::class)->findOneBy(['code' => $locale]);
        }

        return $locale;
    }

    public function createCurrentTransIfMissing(TranslatableInterface $translatable)
    {
        $locale = $this->siteContextHelper->getLocale();
        $found = null;
        $translations = $translatable->getTranslations();
        /** @var TranslationInterface $translation */
        foreach ($translations as $translation) {
            if ($translation->getLocale()->getCode() == $locale->getCode()) {
                $found = $translation;
            }
        }

        if (!$found) {
            $translatable = $this->createTranslationForEntity($translatable, $locale->getCode());
            $this->em->persist($translatable);
            $this->em->flush();
        }

        return $translatable;
    }

    private function affectCurrentTranslation(TranslatableInterface $entity): void
    {
        $currentLocale = $this->siteContextHelper->getCurrentContext()->getLocale();
        $currentTranslation =
            $entity->getTranslations()->filter(fn($t) => $t->getLocale() == $currentLocale)->first() ?? null;

        if ($currentTranslation) {
            $entity->setCurrentTranslation($currentTranslation);
        }
    }
}
