<?php

/*
 * @author TRYDEA - 2024
 */

namespace Webapp\FileManagement\Service;

use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\Exception\InvalidArgumentException;
use ApiPlatform\Core\Exception\ItemNotFoundException;
use Shared\Authentication\Entity\Site;
use Shared\Authentication\Entity\User;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\SerializerInterface;
use Webapp\Core\Entity\AbstractVariable;
use Webapp\Core\Entity\Experiment;
use Webapp\Core\Entity\Platform;
use Webapp\Core\Entity\Project;
use Webapp\Core\Entity\SemiAutomaticVariable;
use Webapp\FileManagement\Dto\Mobile\XmlWithFiles;
use Webapp\FileManagement\Dto\Webapp\ExperimentDto;
use Webapp\FileManagement\Dto\Webapp\PlatformDto;
use Webapp\FileManagement\Dto\Webapp\VariableCollectionDto;
use Webapp\FileManagement\Service\XmlUtils\Normalizer\AbstractRootNormalizer;
use Webapp\FileManagement\Service\XmlUtils\Normalizer\AbstractXmlNormalizer;
use Webapp\FileManagement\Service\XmlUtils\WriterHelper;

final class WebappXmlWriterService
{
    private const EXPORT_LIBRARY_ALL_VARIABLES = 'variables';
    private const EXPORT_LIBRARY_SIMPLE_VARIABLES = 'ind_variables';
    private const EXPORT_LIBRARY_GENERATOR_VARIABLES = 'gen_variables';
    private const EXPORT_LIBRARY_SEMI_AUTO_VARIABLES = 'auto_variables';

    private SerializerInterface $serializer;
    private Security $security;
    private IriConverterInterface $iriConverter;

    public function __construct(SerializerInterface $serializer, Security $security, IriConverterInterface $iriConverter)
    {
        $this->serializer = $serializer;
        $this->security = $security;
        $this->iriConverter = $iriConverter;
    }

    public function generatePlatformWithData(Platform $platform): string
    {
        return $this->serialize(new PlatformDto($this->getUser(), $platform, $platform->getSite()->getOezNatures()->getValues(), true));
    }

    public function generatePlatform(Platform $platform): string
    {
        return $this->serialize(new PlatformDto($this->getUser(), $platform, $platform->getSite()->getOezNatures()->getValues()));
    }

    public function generateExperiment(Experiment $experiment): string
    {
        return $this->serialize(new ExperimentDto($this->getUser(), $experiment));
    }

    /**
     * @param string[] $datas
     */
    public function generateVariables(Site $site, array $datas, ?string $filesPath = null): XmlWithFiles
    {
        // Get library variables types to export
        $libraryAll = \in_array(self::EXPORT_LIBRARY_ALL_VARIABLES, $datas, true);
        $librarySimple = \in_array(self::EXPORT_LIBRARY_SIMPLE_VARIABLES, $datas, true);
        $libraryGenerator = \in_array(self::EXPORT_LIBRARY_GENERATOR_VARIABLES, $datas, true);
        $librarySemiAuto = \in_array(self::EXPORT_LIBRARY_SEMI_AUTO_VARIABLES, $datas, true);
        unset($datas[self::EXPORT_LIBRARY_ALL_VARIABLES]);
        unset($datas[self::EXPORT_LIBRARY_SIMPLE_VARIABLES]);
        unset($datas[self::EXPORT_LIBRARY_GENERATOR_VARIABLES]);
        unset($datas[self::EXPORT_LIBRARY_SEMI_AUTO_VARIABLES]);

        // Get entities from which variables should be exported
        $entities = array_map(function (string $data) {
            try {
                $entity = $this->iriConverter->getItemFromIri($data);
            } catch (InvalidArgumentException|ItemNotFoundException $e) {
                $entity = null;
            }

            return $entity;
        }, $datas);

        $variableCollectionDto = new VariableCollectionDto();

        // Add variables from library
        if ($libraryAll) {
            $variableCollectionDto->addVariables($site->getVariables());
            $variableCollectionDto->addMateriels($site->getDevices());
        } else {
            if ($librarySimple) {
                $variableCollectionDto->addVariables($site->getSimpleVariables());
            }
            if ($libraryGenerator) {
                $variableCollectionDto->addVariables($site->getGeneratorVariables());
            }
            if ($librarySemiAuto) {
                $variableCollectionDto->addMateriels($site->getDevices());
                foreach ($site->getDevices() as $device) {
                    $variableCollectionDto->addVariables($device->getManagedVariables());
                }
            }
        }

        // Add variables from selected projects
        $projects = array_filter($entities, fn (?object $entity) => $entity instanceof Project);
        foreach ($projects as $project) {
            /* @var Project $project */
            $variableCollectionDto->addMateriels($project->getDevices());
            $variableCollectionDto->addVariables($project->getVariables());
        }

        // Add selected variables
        $variables = array_filter($entities, fn (?object $entity) => $entity instanceof AbstractVariable);
        foreach ($variables as $variable) {
            $variableCollectionDto->addVariables([$variable]);
            if ($variable instanceof SemiAutomaticVariable && null !== $variable->getDevice()) {
                $variableCollectionDto->addMateriels([$variable->getDevice()]);
            }
        }

        $writerHelper = new WriterHelper(null);

        return new XmlWithFiles(
            $this->serialize($variableCollectionDto, $writerHelper, $filesPath),
            $writerHelper->getMarkFiles()
        );
    }

    private function getUser(): User
    {
        $user = $this->security->getUser();
        if (!$user instanceof User) {
            throw new \LogicException('User is not logged in');
        }

        return $user;
    }

    private function serialize(object $dto, ?WriterHelper $writerHelper = null, ?string $filesPath = null): string
    {
        return $this->serializer->serialize($dto, 'xml', [
            AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
            XmlEncoder::REMOVE_EMPTY_TAGS => true,
            XmlEncoder::ENCODING => 'UTF-8',
            XmlEncoder::FORMAT_OUTPUT => true,
            XmlEncoder::ROOT_NODE_NAME => 'xmi:XMI',
            XmlEncoder::VERSION => '1.0',
            DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339_EXTENDED,
            AbstractXmlNormalizer::EXPORT_XML => true,
            AbstractXmlNormalizer::WRITER_HELPER => $writerHelper,
            AbstractRootNormalizer::FILES_PATH => $filesPath,
        ]);
    }
}
