<?php declare(strict_types=1);

namespace Newland\Toubiz\Poi\Neos;

use Doctrine\ORM\EntityManagerInterface;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\Flow\Log\ThrowableStorageInterface;
use Neos\Flow\ObjectManagement\ObjectManager;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Fusion\Core\Cache\ContentCache;
use Newland\Contracts\Neos\Filter\FilterRoot;
use Newland\Toubiz\Poi\Neos\Filter\ArticleFilterFactory;
use Newland\Toubiz\Poi\Neos\Filter\Items\ArticleRoot;
use Newland\Toubiz\Sync\Neos\Command\Helper\ConfigurationHelper;
use Newland\Toubiz\Sync\Neos\Command\Task\SyncArticlesFromToubizApiV1;
use Newland\Toubiz\Sync\Neos\Command\Task\SyncArticlesFromToubizApiV2;
use Newland\Toubiz\Sync\Neos\Configuration\ConfigurationAction;
use Newland\Toubiz\Sync\Neos\Configuration\ConfigurationProvider as ConfigurationProviderInterface;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Enum\ArticleType;
use Neos\Flow\Annotations as Flow;
use Newland\Toubiz\Sync\Neos\Logging\LoggerFactory;
use Psr\Log\LoggerInterface;

class ConfigurationProvider implements ConfigurationProviderInterface
{

    /**
     * @var ContentCache
     * @Flow\Inject()
     */
    protected $contentCache;

    /** @var string */
    protected $type;

    /**
     * @var ConfigurationHelper
     * @Flow\Inject()
     */
    protected $configurationHelper;

    /**
     * @var PersistenceManagerInterface
     * @Flow\Inject()
     */
    protected $persistenceManager;

    /**
     * @var EntityManagerInterface
     * @Flow\Inject()
     */
    protected $entityManager;

    /**
     * @var ObjectManager
     * @Flow\Inject()
     */
    protected $objectManager;

    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @var LoggerFactory
     */
    protected $loggerFactory;

    /**
     * @var ThrowableStorageInterface
     * @Flow\Inject()
     */
    protected $exceptionLogger;

    public function injectLogger(LoggerFactory $loggerFactory): void
    {
        $this->loggerFactory = $loggerFactory;
        $this->logger = $loggerFactory->getLogger();
    }

    /** @return string[] */
    public function types(): array
    {
        return ArticleType::values();
    }

    public function filter(NodeInterface $node): FilterRoot
    {
        $articleType = array_flip(ArticleType::$map)[$this->type];
        return (new ArticleFilterFactory($node))->createFilterForArticleType($articleType, [], 'backend');
    }

    /** @param ArticleRoot $filter */
    public function count(FilterRoot $filter): int
    {
        return $filter->countArticles([], true);
    }

    /** @param ArticleRoot $filter */
    public function fetch(FilterRoot $filter): array
    {
        return $filter->getArticleQuery([], true)->getQuery()->execute();
    }

    /**
     * Converts the given fetched item to table items: An array of primitive values displayed in the table.
     *
     * @param Article $item
     */
    public function tableItems($item): array
    {
        return [
            sprintf(
                '<small>%s</small> '
                . '<span data-copy-to-clipboard data-copy-to-clipboard-content="%s">'
                . '<i class="fas fa-clipboard" style="cursor: pointer"></i>'
                . '</span>',
                $item->getOriginalId(),
                $item->getOriginalId()
            ),
            $item->getName(),
            $item->getUpdatedAt()->format('d.m.Y H:i'),
        ];
    }

    /**
     * @param Article $item
     * @return ConfigurationAction[]
     */
    public function actions($item): array
    {
        return [
            'hide' => new ConfigurationAction(
                $item->isHidden() ? 'anzeigen' : 'verbergen',
                $item->isHidden() ? 'fas fa-eye-slash' : 'fas fa-eye',
                $item->isHidden() ? 'red' : 'green',
                function (array $configuration) {
                    $this->contentCache->flushByTag('article');
                    $configuration['hidden'] = !($configuration['hidden'] ?? false);
                    return $configuration;
                }
            ),
            'sync' => new ConfigurationAction(
                'synchronisieren',
                'fas fa-retweet',
                'green',
                function (array $configuration) use ($item) {
                    $this->contentCache->flushByTag('article');
                    $type = ArticleType::$map[$item->getMainType()];

                    switch ($item->getSourceApiVersion()) {
                        case '2':
                            $task = new SyncArticlesFromToubizApiV2($type);
                            break;
                        case '1':
                        default:
                            $task = new SyncArticlesFromToubizApiV1($type);
                    }

                    $id = $item->getOriginalId();
                    $this->configurationHelper->setDefaults([
                        'concurrency' => 2,
                    ]);

                    $errorHandlerWrapper = function ($block) {
                        return function () use ($block) {
                            try {
                                $block(...\func_get_args());
                                $this->persistenceManager->persistAll();
                            } catch (\Throwable $e) {
                                $this->logger->critical($this->exceptionLogger->logThrowable($e));
                                if (!$this->entityManager->isOpen()
                                    || $this->objectManager->getContext()->isTesting()
                                ) {
                                    throw $e;
                                }
                            }
                        };
                    };

                    foreach ($task->configurations($this->configurationHelper) as $syncConfiguration) {
                        $task->synchronizeSingle($id, $syncConfiguration, $errorHandlerWrapper);
                    }

                    return $configuration;
                },
                false,
            ),
        ];
    }

    public function setType(string $type): void
    {
        $this->type = $type;
    }
}
