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

/*
 * This file is part of the "toubiz-sync-neos" 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\Cache\CacheManager;
use Neos\Flow\Configuration\Exception\InvalidConfigurationException;
use Newland\Toubiz\Api\Service\ServiceFactory;
use Newland\Toubiz\Api\Service\Weather\AbstractWeatherService;
use Newland\Toubiz\Sync\Neos\Command\Helper\ConfigurationHelper;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Model\Weather;
use Newland\Toubiz\Sync\Neos\Domain\Repository\WeatherRepository;
use Newland\Toubiz\Sync\Neos\Exception\UnknownDataProviderException;
use Newland\Toubiz\Sync\Neos\Importer\WeatherImporter;
use Newland\Toubiz\Sync\Neos\Logging\LoggerFactory;
use Psr\Log\LoggerInterface;

class WeatherService
{
    protected const TYPE_METEOTEST = 'Meteotest/Api';
    protected const TYPE_OPEN_WEATHER_MAP = 'OpenWeatherMap/Api';
    protected const TYPE_METEOGROUP = 'Meteogroup/Api';
    public const CONFIGURATION_MAP = [
        'meteotest' => self::TYPE_METEOTEST,
        'openweathermap' => self::TYPE_OPEN_WEATHER_MAP,
        'meteogroup' => self::TYPE_METEOGROUP,
    ];

    /**
     * @var CacheManager
     * @Flow\Inject()
     */
    protected $cacheManager;

    /**
     * @var WeatherRepository
     * @Flow\Inject()
     */
    protected $weatherRepository;

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

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

    public function findByCity(string $location): ?Weather
    {
        return $this->weatherRepository->findOneByLocation($location);
    }

    /**
     * @var LoggerInterface
     */
    protected $logger;
    public function injectLogger(LoggerFactory $loggerFactory): void
    {
        $this->logger = $loggerFactory->getLogger();
    }

    public function findByCityArticle(Article $cityArticle): ?Weather
    {
        if (!$cityArticle->getIsCity()) {
            throw new \InvalidArgumentException(sprintf('The article is not a City Article'));
        }
        $weather = $this->findByCity($cityArticle->getName());
        $today = new \DateTime();
        if ($weather === null || ($weather->getUpdatedAt()->diff($today)->h > 6)) {
            $locationConfiguration = $this->getLocationConfigurationFromArticle($cityArticle);
            $this->synchronize($locationConfiguration);
            $weather = $this->findByCity($cityArticle->getName());
        }

        return $weather;
    }

    public function findAllCities(): array
    {
        return $this->weatherRepository->findAllCities();
    }

    private function getLocationConfigurationFromArticle(Article $cityArticle): array
    {
        return [
            $cityArticle->getName() => [
                'lat' => $cityArticle->getLatitude(),
                'lon' => $cityArticle->getLongitude(),
            ],
        ];
    }

    /**
     * @return string
     * @throws UnknownDataProviderException
     */
    private function getConfigurationKey(): string
    {
        try {
            $dataProvider = $this->configuration['dataProvider']['weather'];
            return static::CONFIGURATION_MAP[$dataProvider];
        } catch (\Exception $exception) {
            throw new UnknownDataProviderException(
                'Unknown weather data provider or weather data provider not configured.'
            );
        }
    }

    private function synchronize(array $locations): void
    {
        $configurationKey = $this->getConfigurationKey();

        $configuration = $this->getConfigurationForService($configurationKey);
        if (!$configuration) {
            throw new InvalidConfigurationException(
                sprintf('No configuration found for weather data service "%s"', $configurationKey)
            );
        }

        /** @var AbstractWeatherService $service */
        $service = ServiceFactory::get($configurationKey);
        $service->setApiKey($configuration['apiKey']);
        $service->setParameters([ 'locations' => $locations ]);
        $service->setLogger($this->logger);

        $service->fetchForecast(
            function ($record) {
                $importer = new WeatherImporter();
                $importer->import($record);
            }
        );
    }

    protected function getConfigurationForService(string $service, string $client = null): ?array
    {
        $configuration = $this->configurationHelper->getConfigurationForService($service);

        if ($configuration === null) {
            return null;
        }

        // Use the first configured client as fallback. Most projects will only have a single client, anyway.
        $client = $client ?? array_keys($configuration['clients'])[0];

        return $configuration['clients'][$client];
    }
}
