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

use DateInterval;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Cache\CacheManager;
use Newland\Toubiz\Api\Constants\Language;
use Newland\Toubiz\Api\Service\ServiceFactory;
use Newland\Toubiz\Api\Service\Weather\AbstractWeatherService;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Model\Attribute;
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 Traversable;

class WeatherService
{

    protected const TYPE_METEOTEST = 'Meteotest/Api';
    protected const TYPE_OPEN_WEATHER_MAP = 'OpenWeatherMap/Api';

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

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

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

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

    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
    {
        $configurationMap = [
            'meteotest' => static::TYPE_METEOTEST,
            'openweathermap' => static::TYPE_OPEN_WEATHER_MAP,
        ];
        try {
            $dataProvider = $this->configuration['dataProvider']['weather'];
            return $configurationMap[$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 \Exception('Weather data service not configured!');
        }

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

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

    protected function getConfigurationForService(string $service): ?array
    {
        if (is_array($this->configuration) && ($this->configuration['services'][$service] ?? null)) {
            $configuration = $this->configuration['services'][$service];
            $configuration['languages'] = $configuration['languages'] ?? [ Language::DE ];
            return $configuration;
        }

        return null;
    }
}
