<?php declare(strict_types=1);

namespace Newland\Toubiz\Sync\Neos\Command\Task;

use Neos\Flow\Configuration\Exception\InvalidConfigurationException;
use Newland\Toubiz\Api\Constants\Language;
use Newland\Toubiz\Api\Service\Toubiz\ApiV1\ArticleService;
use Newland\Toubiz\Api\Service\Toubiz\ApiV1\ObjectAdapter\ArticleAdapter;
use Newland\Toubiz\Sync\Neos\Command\Helper\ApiServiceHelper;
use Newland\Toubiz\Sync\Neos\Command\Helper\ConfigurationHelper;
use Neos\Flow\Annotations as Flow;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;
use Newland\Toubiz\Sync\Neos\Enum\ArticleType;
use Newland\Toubiz\Sync\Neos\Importer\ArticleImporter;
use Newland\Toubiz\Sync\Neos\Logging\LoggerFactory;
use Newland\Toubiz\Sync\Neos\Service\UuidPredictionService;
use Psr\Log\LoggerInterface;

class SyncArticlesFromToubizApiV1 implements SynchronizationTask, DeletesOld
{
    protected const SERVICE_NAME = 'Toubiz/ApiV1/Article';

    protected const SOURCE_API_VERSION = null;

    protected const DEFAULT_REQUESTS = [
        ArticleType::CITIES => [
            'unlicensed' => true,
            'filter' => [
                'categoryTypes' => [ 'city', 'area' ],
            ],
        ],
        ArticleType::POI => [
            'unlicensed' => true,
            'filter' => [
                'category' => [
                    '4c5fffa9-b6df-4ca6-98aa-063d848b4e0b',
                    '80f03acb-0ec9-4224-8174-09062161d64b',
                ],

                'excludeCategory' => [
                    // Direct marketers
                    'a25f18e6-4cba-45f8-9330-759f76f7d8f3',
                    // Region
                    '8db3c052-0ceb-494a-a74a-0bc2718dded7',
                ],
            ],
        ],
        ArticleType::GASTRONOMY => [
            'unlicensed' => true,
            'filter' => [
                'category' => [ 'eb39a3a6-52ce-4dd5-bbef-decfa1f7376e' ],
            ],
        ],
        ArticleType::LODGINGS => [
            'unlicensed' => true,
            'filter' => [
                'category' => [ '32b05798-f54c-462d-9f58-a39899c85ef6' ],
            ],
        ],
        ArticleType::TOURS => [
            'unlicensed' => true,
            'filter' => [
                'category' => [ 'e87547a9-f40e-3e5b-9e19-88073d18eabe' ],
            ],
        ],
        ArticleType::DIRECT_MARKETERS => [
            'unlicensed' => true,
            'filter' => [
                'category' => [ 'a25f18e6-4cba-45f8-9330-759f76f7d8f3' ],
            ],
        ],
    ];

    /**
     * @var ApiServiceHelper
     * @Flow\Inject()
     */
    protected $apiServiceHelper;

    /**
     * @var ArticleRepository
     * @Flow\Inject()
     */
    protected $articleRepository;

    /** @var int */
    protected $processed = 0;

    /** @var string[] */
    protected $ids = [];

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

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

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

    public function __construct(string $type)
    {
        $this->type = $type;
    }

    public function name(): string
    {
        return sprintf('ToubizApiV1: %s', $this->type);
    }

    public function configurations(ConfigurationHelper $configurationHelper): \Generator
    {
        $configuration = $configurationHelper->getConfigurationForService(static::SERVICE_NAME);
        if (!$configuration) {
            return;
        }

        foreach ($configuration['clients'] ?? [] as $clientKey => $clientConfig) {
            foreach ($clientConfig['import'] ?? [] as $importConfig) {
                ArticleType::throwIfInvalid($importConfig['type']);
                if ($importConfig['type'] !== $this->type) {
                    continue;
                }

                $importConfig['client'] = (string) ($clientConfig['client'] ?? $clientKey);
                $importConfig['baseUri'] = $importConfig['baseUri'] ?? $clientConfig['baseUri'] ?? null;
                $importConfig['apiKey'] = $importConfig['apiKey'] ?? $clientConfig['apiKey'] ?? null;
                $importConfig['client'] = $importConfig['client'] ?? $clientConfig['client'] ?? null;
                $importConfig['type'] = $this->typeConstant();

                if (!array_key_exists('request', $importConfig)) {
                    if (!array_key_exists($this->type, static::DEFAULT_REQUESTS)) {
                        throw new InvalidConfigurationException(
                            sprintf(
                                'No default request configuration for type `%s` found.'
                                . ' If you still want to import articles of this type,'
                                . ' you will have to specify a custom request in the `request` key.',
                                $this->type
                            )
                        );
                    }

                    $importConfig['request'] = static::DEFAULT_REQUESTS[$this->type];
                    $importConfig['request']['filter']['clientIncludingManaged'] = $importConfig['client'];
                }

                foreach ($importConfig['languages'] ?? [ Language::DE ] as $language) {
                    $importConfig['language'] = $language;
                    yield $configurationHelper->mergeWithDefaults($importConfig);
                }
            }
        }
    }

    public function synchronize(
        array $configuration,
        \Closure $errorHandlerWrapper,
        \Closure $onProgress
    ): SynchronizationResult {

        /** @var ArticleService $service */
        $service = $this->apiServiceHelper->initializeApiServiceWithCommonConfigurationOptions(
            static::SERVICE_NAME,
            $configuration
        );

        $this->processed = 0;
        $this->ids = [];

        $importer = new ArticleImporter();
        $importer->setLanguage((string) $configuration['language']);
        $importer->setClient((string) $configuration['client']);
        $importer->setUuidPredictionService(
            $this->uuidPredictionService(
                (string) $configuration['client'],
                (string) $configuration['language']
            )
        );

        $result = $service->fetchArticles(
            $errorHandlerWrapper(
                function (ArticleAdapter $record, ?int $total) use ($onProgress, $importer, $configuration) {
                    $onProgress(++$this->processed, $total);
                    $record->setLanguage((string) $configuration['language']);

                    if ($record->ignoreRecord()) {
                        return;
                    }

                    $imported = $importer->import($record);
                    if ($imported) {
                        $this->ids[] = (string) $imported->getPersistenceObjectIdentifier();
                    }
                }
            )
        );

        return SynchronizationResult::fromServiceResult($result, $this->ids);
    }


    private function uuidPredictionService(string $client, string $language): UuidPredictionService
    {
        $uuidPredictionService = new UuidPredictionService();
        $uuidPredictionService->setDefaultProperties(
            [
                'client' => $client,
                'language' => $language,
                'mainType' => $this->typeConstant(),
            ]
        );

        return $uuidPredictionService;
    }

    private function typeConstant(): int
    {
        return array_flip(ArticleType::$map)[$this->type];
    }

    public function deleteOld(SynchronizationResult $result, array $configuration, \Closure $onProgress): void
    {
        if ($configuration['delta'] ?? null) {
            $this->logger->warning(
                'Old articles can only be deleted when running the sync task without specifying a delta (fullsync)'
            );
            return;
        }

        $this->articleRepository->removeBasedOnSynchronizationResult(
            $result,
            $this->typeConstant(),
            (string) $configuration['client'],
            (string) $configuration['language'],
            $onProgress,
            static::SOURCE_API_VERSION
        );
    }
}
