<?php
namespace Newland\Toubiz\Sync\Neos\Command;

/*
 * This file is part of the "neos-common" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Log\PsrLoggerFactoryInterface;
use Neos\Flow\Mvc\Exception\StopActionException;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Flow\Validation\ValidatorResolver;
use Newland\NeosCommon\Service\ObjectPathMappingService;
use Newland\Toubiz\Api\Constants\Language;
use Psr\Log\LoggerInterface;

class AbstractCommandController extends \Newland\NeosCommon\Command\AbstractCommandController
{
    /**
     * Number of entities after which the EntityManager should be flushed
     * (writes to database and resets entity handling).
     *
     * Smaller sizes may decrease memory footprint but increase runtime,
     * Larger sizes may run into Database errors when trying to write to many
     * entities at a single time.
     *
     * @var int
     */
    protected $flushAfter = 20;

    /**
     * @Flow\InjectConfiguration
     * @var array
     */
    protected $configuration;

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

    /**
     * @var ObjectPathMappingService
     * @Flow\Inject
     */
    protected $objectPathMappingService;

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

    /**
     * @var ValidatorResolver
     * @Flow\Inject
     */
    protected $validatorResolver;

    public function injectLogger(PsrLoggerFactoryInterface $loggerFactory)
    {
        $this->logger = $loggerFactory->get('newlandSyncImport');
    }

    /**
     * Find configuration for given service string.
     *
     * @param string $service
     * @return array|null
     */
    protected function getConfigurationForService($service)
    {
        if (array_key_exists('services', $this->configuration)
            && array_key_exists($service, $this->configuration['services'])
        ) {
            $configuration = $this->configuration['services'][$service];
            $configuration['languages'] = $configuration['languages'] ?? Language::values();
            return $configuration;
        }

        return null;
    }

    /**
     * @param mixed $configuration
     * @return bool
     */
    protected function hasClients($configuration): bool
    {
        return $this->hasArrayChild($configuration, 'clients');
    }

    /**
     * @param mixed $array
     * @param string $key
     * @return bool
     */
    protected function hasArrayChild($array, string $key): bool
    {
        return is_array($array) && array_key_exists($key, $array) &&
            is_array($array[$key]);
    }

    /**
     * @param array $configuration
     * @return array
     */
    protected function getClients(array $configuration = null)
    {
        if ($configuration === null) {
            return [];
        }
        return $configuration['clients'];
    }

    protected function wrapImportClosure(callable $block): callable
    {
        $calls = 0;
        return function () use ($block, &$calls) {
            try {
                $block(...\func_get_args());
                if (++$calls % $this->flushAfter === 0) {
                    $this->persistAndFlushEntityReferencesFromPersistenceManager();
                }
            } catch (\Exception $e) {
                $this->logger->critical(
                    'Error importing record: ' . $e->getMessage(),
                    [
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                        'trace' => $e->getTraceAsString(),
                    ]
                );
            }
        };
    }

    protected function persistAndFlushEntityReferencesFromPersistenceManager(): void
    {
        $this->persistenceManager->persistAll();
        $this->persistenceManager->clearState();
        $this->validatorResolver->reset();
    }

    protected function askForConfirmationAndAbortIfNoneGiven(string $message, bool $default = false)
    {
        $options = $default ? '[Y|n]' : '[y|N]';
        $message .= ' ' . $options . ' ';
        if (!$this->output->askConfirmation($message, $default)) {
            $this->output->outputLine('Aborting: Action not confirmed');
            throw new StopActionException('Action not confirmed.');
        }
    }
}
