<?php

namespace Comitium5\DesignerBundle\Command;

use Comitium5\DesignerBundle\Entity\Widget;
use Comitium5\DesignerBundle\Model\WidgetType;
use Comitium5\DesignerBundle\Validator\Widget\WidgetNameIsValid;
use Comitium5\DesignerBundle\ValueObjects\Common\ErrorObject;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;

/**
 * Class WidgetSyncCommand
 *
 * Joan Pont <joan.pont@bab-soft.com>
 * @package Comitium5\DesignerBundle\Command
 */
class WidgetSyncCommand extends Command implements ContainerAwareInterface
{
    use ContainerAwareTrait;

    const MYSQL_CONNECTION_TEMPLATE = 'mysql://%s:%s@%s:%s/%s';

    const DB_WIDGET_TABLE = 'cs_admin_widget';

    const WIDGET_TYPES_DICC = [
        "html"     => WidgetType::HTML,
        "cache"    => WidgetType::CACHE,
        "dynamic"  => WidgetType::DYNAMIC,
        "abstract" => WidgetType::ABSTRACTS,
    ];

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('comitium:designer:widgets:sync')
            ->setDescription('Synchronizes widgets folder with database widget table')
            ->addOption('widgets-folder-path', 'p', InputOption::VALUE_REQUIRED,
                'Path to the folder where the widgets are located', "src/ComitiumSuite/Bundle/CSBundle/Widgets")
            ->addOption('meta-filename', 'm', InputOption::VALUE_REQUIRED,
                "filename of widgets metadata file", "meta.yaml");
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     *
     * @return int|null|void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $widgetsFolderPath = $input->getOption('widgets-folder-path');

        $metadataFilename = $input->getOption('meta-filename');

        $widgetManager = $this->container->get('designer.manager.widget');

        $widgetRepository = $this->container->get('designer.repository.widget');

        $output->writeln("<info>[info] Finding widgets in BD</info>");

        $dbWidgets = $widgetRepository
            ->findAll()
            ->getQuery()
            ->execute();

        $output->writeln("<info>[info] Finding widgets in disk</info>");

        $widgetFolders = $this->getAllWidgetDirectories($widgetsFolderPath, $metadataFilename, $output);

        $output->writeln("<info>[info] Synchronizing widgets...</info>");

        foreach ($dbWidgets as $widgetTuple) {
            if ($this->isNotValidWidget($widgetTuple)) {
                $output->writeln("<info>[info] Ignoring ".$widgetTuple->getName()."</info>");
                continue;
            }

            if (array_key_exists($widgetTuple->getName(), $widgetFolders)) {

                $output->writeln("<info>[info] Updating ".$widgetTuple->getName()."</info>");

                $widgetMetaData = $widgetFolders[$widgetTuple->getName()]['metaFile'];

                $this->setWidgetNewData($widgetTuple, $widgetMetaData);

                $widgetManager->updateWidget($widgetTuple);
            } else {
                $output->writeln("<info>[info] Deleting ".$widgetTuple->getName()."</info>");

                $widgetManager->deleteWidget($widgetTuple);
            }
        }

        foreach ($widgetFolders as $widgetName => $widgetFolder) {
            $widgetMetaData = $widgetFolder['metaFile'];

            $result = $widgetRepository
                ->findAll()
                ->where("w.name = :widgetName")
                ->setParameter("widgetName", $widgetMetaData['name'])
                ->getQuery()
                ->getOneOrNullResult();

            if (empty($result)) {

                $widgetTitle = empty($widgetMetaData['title']) ? $widgetMetaData['name'] : $widgetMetaData['title'];

                $widgetType = empty($widgetMetaData['type']) ? "" : $widgetMetaData['type'];

                $newWidget = $widgetManager->createWidgetInstance(
                    $widgetName,
                    $widgetTitle,
                    $this->translateWidgetType($widgetType)
                );

                if ($this->isNotValidWidget($newWidget)) {
                    $output->writeln("<info>[info] Not creating widget ".$newWidget->getName()."</info>");
                    continue;
                }
                $output->writeln("<info>[info] Creating ".$widgetName."</info>");

                $widgetManager->save($newWidget);
            }
        }

        $output->writeln("<info>[info] Finished</info>");
    }

    /**
     * @param $widgetPath
     * @param $metadataFilename
     * @param OutputInterface $output
     *
     * @return array
     */
    private function getAllWidgetDirectories($widgetPath, $metadataFilename, OutputInterface $output)
    {
        $fileSystem = new Filesystem();

        $widgetFolders = [];

        if (!$fileSystem->exists($widgetPath)) {
            return $widgetFolders;
        }

        $finder = new Finder();

        $finder->files()->in($widgetPath);
        foreach ($finder->depth(0)->directories() as $widget) {
            $finderMetas = new Finder();

            $widgetName = $widget->getBaseName();

            $metaPath = $this->buildConfigPath($widgetPath, $widgetName);

            if ($fileSystem->exists($metaPath.DIRECTORY_SEPARATOR.$metadataFilename)) {
                $finderMetas->files()->in($metaPath)->name($metadataFilename);

                foreach ($finderMetas as $metaFile) {
                    try {
                        $metaData = Yaml::parse($metaFile->getContents());

                        $metaData['name'] = $widgetName;
                    } catch (ParseException $exception) {
                        $output->writeln(
                            "<error>[error] Unable to parse the YAML metadata file on widget ".$widgetName.", error: ".$exception->getMessage()." </error>");
                        continue;
                    } catch (\Exception $exception) {
                        $output->writeln(
                            "<error>[error] unexpected error has happened while processing ".$widgetName.": ".$exception->getMessage()."</error>"
                        );
                        continue;
                    }

                    $widgetFolders[$widgetName]["metaFile"] = $metaData;
                }
            } else {
                $output->writeln("<comment>[warning] meta.yaml not found in: ".$metaPath.DIRECTORY_SEPARATOR.$metadataFilename."</comment>");
            }
        }

        return $widgetFolders;
    }

    /**
     * @param Widget $widget
     * @param array $metaData
     */
    public function setWidgetNewData(Widget $widget, array $metaData)
    {
        $widget->setName($metaData['name']);

        $widgetTitle = empty($metaData['title']) ? $metaData['name'] : $metaData['title'];

        $widget->setTitle($widgetTitle);

        if (!empty($metaData['type'])) {
            $widget->setType($this->translateWidgetType($metaData['type']));
        }
    }

    /**
     * @param $type
     *
     * @return int|mixed
     */
    public function translateWidgetType($type)
    {
        return isset(self::WIDGET_TYPES_DICC[$type]) ? self::WIDGET_TYPES_DICC[$type] : WidgetType::DYNAMIC;
    }

    /**
     * @param $widgetPath
     * @param $widgetName
     *
     * @return string
     */
    public function buildConfigPath($widgetPath, $widgetName)
    {
        return $widgetPath.DIRECTORY_SEPARATOR.$widgetName.DIRECTORY_SEPARATOR."Config";
    }

    /**
     * @param Widget|null $widget
     *
     * @return bool
     */
    public function isNotValidWidget(Widget $widget = null): bool
    {
        $widgetValidator = $this->container->get('widget.resolver.widget_validator');

        /**
         * @var $result ErrorObject
         */
        $result = $widgetValidator($widget, [
            new WidgetNameIsValid()
        ]);

        return $result->isValid();
    }


}
