<?php

namespace AutoAdmin\Bundle\Content\Command;

use AutoAdmin\Bundle\BrickBuilder\Model\BrickInterface;
use AutoAdmin\Bundle\Content\Entity\Locale;
use AutoAdmin\Bundle\Content\Entity\Site;
use AutoAdmin\Bundle\Content\Model\TranslatableInterface;
use AutoAdmin\Bundle\Content\Translation\EntityTranslator;
use AutoAdmin\Bundle\Media\Model\ImageInterface;
use AutoAdmin\Bundle\Seo\Entity\SeoMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use ReflectionClass;
use ReflectionException;
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\Question\Question;

#[AsCommand(
    name: 'aa:translations:export',
    description: 'Export translations from entities and bricks',
    hidden: false
)]
class TranslationExportCommand extends Command
{
    private EntityManagerInterface $em;
    private EntityTranslator $entityTranslator;
    private Locale $locale;
    private Site $site;
    private Worksheet $sheet;

    const IGNORED_TYPE_FIELDS = [
        'null',
        'int',
        'integer',
        'float',
        'boolean',
        'bool',
    ];


    public function __construct(EntityManagerInterface $em, EntityTranslator $entityTranslator)
    {
        $this->em = $em;
        $this->entityTranslator = $entityTranslator;
        $this->locale = new Locale('fr');

        parent::__construct();
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln('Run export translations command');

        $helper = $this->getHelper('question');

        try {
            $siteSlug = $helper->ask(
                $input,
                $output,
                new Question('Website slug ?')
            );

            if (empty($siteSlug)) {
                $output->writeln('<error>' . 'You must provide all options. Aborting.' . '</error>');

                return self::FAILURE;
            }

            $site = $this->em->getRepository(Site::class)->findOneBy(['slug' => $siteSlug,]);

            if (!$site) {
                $output->writeln('<error>' . 'This slug not exists.' . '</error>');

                return self::FAILURE;
            }

            $this->site = $site;

            $classes = $this->getTranslatableClasses();
            $translationData = [];

            foreach ($classes as $class) {
                $repository = $this->em->getRepository($class);
                if (!$repository) {
                    continue;
                }

                $objects = $repository->findAll();
                $properties = $this->entityTranslator->getTranslatableFieldsFromClass($class);

                foreach ($objects as $object) {
                    $object = $object->getTranslations($this->locale->getCode());
                    $object = $object ? $object[0] : null;

                    $extractedData = $this->extractTranslationsData($properties, $object);
                    if (!empty($extractedData)) {
                        $translationData[$class][$object->getId()] = $extractedData;
                    }
                }
            }

            $this->generateExcelFromData($translationData);
        } catch (Exception $e) {
            $output->writeln('<error>' . $e->getMessage() . '</error>');

            return self::FAILURE;
        }

        $output->writeln('Success !');
        return self::SUCCESS;
    }

    private function getAllClassNames(): array
    {
        $classes = [];

        $metas = $this->em->getMetadataFactory()->getAllMetadata();
        foreach ($metas as $meta) {
            $classes[] = $meta->getName();
        }
        return $classes;
    }

    /**
     * @throws ReflectionException
     */
    private function getTranslatableClasses(): array
    {
        $classes = [];
        foreach ($this->getAllClassNames() as $className) {
            $reflectionClass = new ReflectionClass($className);
            if ($reflectionClass->implementsInterface(TranslatableInterface::class)) {
                if (str_starts_with($className, 'AutoAdmin\\Bundle')) {
                    continue;
                }
                $classes[] = $className;
            }
        }
        return $classes;
    }

    /**
     * @throws ReflectionException
     */
    public function extractTranslationsData(array $properties, $object): array
    {
        $objectData = $this->extractParameters($properties, $object);
        $objectObjectsData = $this->extractObjectsParameters($properties, $object);
        $objectData = array_merge($objectData, $objectObjectsData);
        $brickData = $this->extractBrickData($properties, $object);

        if (!empty($brickData)) {
            $objectData['bricks'] = $brickData;
        }

        return $objectData;
    }

    private function extractParameters(array $properties, $object): array
    {
        $objectData = [];

        foreach ($properties as $property) {
            $isClassOrInterface = class_exists($property->getType()->getName()) || interface_exists($property->getType()->getName());

            if ($isClassOrInterface) {
                continue;
            }

            if (in_array($property->getType()->getName(), $this::IGNORED_TYPE_FIELDS)) {
                continue;
            }

            $getter = 'get' . ucfirst($property->getName());

            if (method_exists($object, $getter)) {
                if ($object->$getter() !== null && !empty($object->$getter())) {
                    $objectData[$property->getName()] = $object->$getter();
                }
            }
        }

        return $objectData;
    }

    private function extractObject(string $className, array $properties, $object)
    {
        foreach ($properties as $property) {
            if ($property->getType()->getName() === $className) {
                $getter = 'get' . ucfirst($property->getName());
                if (method_exists($object, $getter)) {
                    return $object->$getter();
                }
            }
        }

        return null;
    }

    private function extractBricks(array $properties, $object): array
    {
        $rootBrick = $this->extractObject(BrickInterface::class, $properties, $object);

        if (!$rootBrick) {
            return [];
        }

        $brickSections = $rootBrick->getChildren()->toArray();

        $brickRows = [];
        foreach ($brickSections as $brickSection) {
            foreach ($brickSection->getChildren()->toArray() as $brickRow) {
                $brickRows[] = $brickRow;
            }
        }

        $brickColumns = [];
        foreach ($brickRows as $brickRow) {
            foreach ($brickRow->getChildren()->toArray() as $brickColumn) {
                $brickColumns[] = $brickColumn;
            }
        }

        $bricks = [];
        foreach ($brickColumns as $brickColumn) {
            foreach ($brickColumn->getChildren()->toArray() as $brick) {
                $bricks[] = $brick;
            }
        }

        return $bricks;
    }

    /**
     * @throws ReflectionException
     */
    private function extractObjectsParameters(array $properties, $object): array
    {
        $seoMetaDataObject = $this->extractObject(SeoMetadata::class, $properties, $object);
        if (!$seoMetaDataObject) {
            return [];
        }

        $reflectionClass = new ReflectionClass($seoMetaDataObject);
        $seoMetaDataProperties = $reflectionClass->getProperties();
        return $this->extractParameters($seoMetaDataProperties, $seoMetaDataObject);
    }

    private function extractBrickData(array $properties, $object): array
    {
        /** @var BrickInterface[] $bricks */
        $bricks = $this->extractBricks($properties, $object);

        $brickData = [];

        foreach ($bricks as $brick) {
            $brickData[$brick->getId()] = [];

            foreach ($brick->getSettings() as $name => $setting) {
                if ($setting instanceof ImageInterface) {
                    $imageData = $this->extractImageData($setting);
                    $brickData[$brick->getId()][$name] = $imageData;

                    continue;
                }

                if (!$this->isAllowedFieldType($setting)) {
                    continue;
                }

                if (is_array($setting)) {
                    $brickData[$brick->getId()][$name] = [];
                    foreach ($setting as $key => $value) {
                        if ($value instanceof ImageInterface) {
                            $imageData = $this->extractImageData($value);
                            $brickData[$brick->getId()][$name][$key] = $imageData;

                            unset($setting[$key]);
                            continue;
                        }

                        if (!$this->isAllowedFieldType($value)
                            || empty($value)) {
                            unset($setting[$key]);
                            continue;
                        }

                        $brickData[$brick->getId()][$name][$key] = !is_array($value) ? $this->cleanText($value) : $value;
                    }

                    if (empty($brickData[$brick->getId()][$name])) {
                        unset($brickData[$brick->getId()][$name]);
                    }
                }

                if (!empty($setting)) {
                    $brickData[$brick->getId()][$name] = !is_array($setting) ? $this->cleanText($setting) : $setting;
                }
            }

            if (empty($brickData[$brick->getId()])) {
                unset($brickData[$brick->getId()]);
            }
        }

        return $brickData;
    }

    private function isAllowedFieldType(mixed $element): bool
    {
        $typeOf = gettype($element);

        if (in_array(strtolower($typeOf), $this::IGNORED_TYPE_FIELDS)) {
            return false;
        }

        return true;
    }

    private function extractImageData(ImageInterface $image): array
    {
        $imageData = [];

        $imageData['id'] = $image->getId();
        $imageData['alt'] = $image->getAlt();
        $imageData['title'] = $image->getTitle();

        return $imageData;
    }

    /**
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     */
    private function generateExcelFromData(array $translationData): void
    {
        $baseUrl = 'https://' . $this->site->getDomains()[0]->getDomain();
        $baseAdminUrl = $baseUrl . '/admin/fr/entity/';

        $spreadsheet = new Spreadsheet();
        $this->sheet = $spreadsheet->getActiveSheet();

        $this->sheet->setCellValue('A1', 'entity');
        $this->sheet->setCellValue('B1', 'entity-id');
        $this->sheet->setCellValue('C1', 'brick-id');
        $this->sheet->setCellValue('D1', 'front-url');
        $this->sheet->setCellValue('E1', 'back-url');
        $this->sheet->setCellValue('F1', 'field-name');
        $this->sheet->setCellValue('G1', 'field-value');

        $row = 2;
        foreach ($translationData as $classNamespace => $entities) {
            $className = str_replace('App\\Entity\\', '', $classNamespace);
            foreach ($entities as $entityId => $entityData) {
//                className to snake case
                $classNameUrl = strtolower(preg_replace('/(?<!^)[A-Z]/', '-$0', $className));
                $frontUrl = '';
                $backUrl = $baseAdminUrl . $classNameUrl . '/update?sorting_order=&id=' . $entityId;

                foreach ($entityData as $fieldName => $fieldValue) {
                    $brickId = null;

                    if ($fieldName === 'bricks') {
                        foreach ($fieldValue as $brickId => $brickData) {
                            $brickId = (int)$brickId;
                            foreach ($brickData as $brickFieldName => $brickFieldValue) {

                                if (is_array($brickFieldValue)) {
                                    foreach ($brickFieldValue as $key => $value) {
                                        if (is_array($value)) {
                                            foreach ($value as $k => $v) {
                                                if (is_array($v)) {
                                                    continue;
                                                }
                                                $row = $this->addRow($row, $className, $entityId, $brickId, $frontUrl, $backUrl, $brickFieldName . ' - ' . $key, $v);
                                            }
                                            continue;
                                        }
                                        if (is_int($value) || is_bool($value) || is_null($value)) {
                                            continue;
                                        }
                                        $row = $this->addRow($row, $className, $entityId, $brickId, $frontUrl, $backUrl, $brickFieldName . ' - ' . $key, $value);
                                    }
                                    continue;

                                }

                                $row = $this->addRow($row, $className, $entityId, $brickId, $frontUrl, $backUrl, $brickFieldName, $brickFieldValue);
                            }
                        }
                        continue;
                    }

                    if (is_array($fieldValue)) {
                        foreach ($fieldValue as $fName => $fValue) {
                            $row = $this->addRow($row, $className, $entityId, $brickId, $frontUrl, $backUrl, $fieldName, $fValue);
                        }

                        continue;
                    }

                    if (is_int($fieldValue) || is_bool($fieldValue) || is_null($fieldValue)) {
                        continue;
                    }

                    $row = $this->addRow($row, $className, $entityId, $brickId, $frontUrl, $backUrl, $fieldName, $fieldValue);
                }
            }
        }

        $writer = new Xlsx($spreadsheet);
        $writer->save('translations.xlsx');
    }

    private function addRow(int $row, string $className, int $entityId, ?int $brickId, ?string $frontUrl, ?string $backUrl, string $fieldName, mixed $fieldValue): int
    {
        if (is_int($fieldValue) || is_bool($fieldValue) || is_null($fieldValue)) {
            return $row;
        }

        // encore utf8
        $fieldValue = html_entity_decode($fieldValue);

        $this->sheet->setCellValue('A' . $row, $className);
        $this->sheet->setCellValue('B' . $row, $entityId);
        $this->sheet->setCellValue('C' . $row, $brickId);
        $this->sheet->setCellValue('D' . $row, $frontUrl);
        $this->sheet->setCellValue('E' . $row, $backUrl);
        $this->sheet->setCellValue('F' . $row, $fieldName);
        $this->sheet->setCellValue('G' . $row, $fieldValue);

        return $row + 1;
    }

    public function cleanText(string $string): string
    {
        $string = preg_replace('/<\/[^>]+>/', "$0\n", $string);
        return strip_tags($string);
    }
}
