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

/*
 * 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 Newland\Toubiz\Api\ObjectAdapter\AddressAdapterInterface;
use Newland\Toubiz\Api\Service\GeoLocation\GeoLocationServiceInterface;
use Newland\Toubiz\Sync\Neos\Domain\Model\Address;
use Newland\Toubiz\Sync\Neos\Domain\Repository\AddressRepository;
use Newland\Toubiz\Sync\Neos\Orm\Uuid\UuidGenerator;
use Newland\Toubiz\Sync\Neos\Service\GeoLocationFactory;
use Newland\Toubiz\Sync\Neos\Utility\NumberUtility;

class AddressImporter extends AbstractImporter
{
    /**
     * @Flow\Inject()
     * @var AddressRepository
     */
    protected $addressRepository;


    /**
     * @var GeoLocationServiceInterface
     */
    protected $geoLocationService;

    public function injectGeoLocationService(GeoLocationFactory $factory): void
    {
        $this->geoLocationService = $factory->get('Newland.Toubiz.Sync.Neos.services.GeoLocation');
    }

    /**
     * Import method.
     *
     * Persist given data by creating new objects or updating existing ones.
     *
     * @param AddressAdapterInterface $data
     * @return Address
     */
    public function import($data)
    {
        $this->initializeLogger($data, [
            'article' => [
                'externalId' => $data->getExternalId(),
                'title' => $data->getTitle(),
            ]
        ]);

        $address = $this->findAddress($data);
        $persisted = $address !== null;
        /** @var Address $address */
        $address = $address ?: new Address();
        $address->setOriginalId($data->getExternalId());
        $address->setName($data->getTitle());
        $address->setFirstName($data->getFirstName());
        $address->setLastName($data->getLastName());
        $address->setStreet($data->getStreet());
        $address->setZip($this->getZip($address, $data));
        $address->setCity($data->getCity());
        $address->setPhoneNumber($data->getPhoneNumber());
        $address->setFaxNumber($data->getFaxNumber());
        $address->setEmailAddress($data->getEmailAddress());
        $address->setWebsiteAddress($data->getWebsiteAddress());
        $address->setLanguage($this->language);
        $this->mapCoordinates($address, $data);
        if ($persisted) {
            $this->addressRepository->update($address);
        } else {
            $this->addressRepository->add($address);
        }

        $this->persistenceManager->persistAll();
        return $address;
    }

    /**
     * @param AddressAdapterInterface $data
     * @return Address|null
     */
    protected function findAddress($data): ?Address
    {
        return $this->addressRepository->withLanguage(
            $this->language,
            function () use ($data) {
                $uuid = UuidGenerator::uuidFromProperties(
                    [
                        $data->getExternalId(),
                        $this->language
                    ]
                )->toString();
                return $this->addressRepository->findByIdentifier($uuid);
            }
        );
    }

    private function mapCoordinates(Address $address, AddressAdapterInterface $data): void
    {
        $latitude = (float) $data->getLatitude();
        $longitude = (float) $data->getLongitude();
        if (abs($latitude) > .1 && abs($longitude) > .1) {
            $address->setLatitude($latitude);
            $address->setLongitude($longitude);
        } else {
            $address->setLatitude(null);
            $address->setLongitude(null);
        }
    }

    private function getZip(Address $address, AddressAdapterInterface $data): ?string
    {
        $addressHasZip = $address->getZip() !== null && $address->getZip() !== '';
        $addressHasCoordinates = $address->getLongitude() !== null
            && !NumberUtility::roughlyEquals($address->getLongitude(), 0, 0.001)
            && $address->getLatitude() !== null
            && !NumberUtility::roughlyEquals($address->getLatitude(), 0, 0.001);

        $adapterHasZip = $data->getZip() !== null && $data->getZip() !== '';
        $adapterHasCoordinates = $data->getLongitude() !== null
            && !NumberUtility::roughlyEquals($data->getLongitude(), 0, 0.001)
            && $data->getLatitude() !== null
            && !NumberUtility::roughlyEquals($data->getLatitude(), 0, 0.001);

        $coordinatesInAdapterAreSameAsInAddress = $adapterHasCoordinates
            && $addressHasCoordinates
            && NumberUtility::roughlyEquals((float) $data->getLatitude(), (float) $address->getLatitude(), .001)
            && NumberUtility::roughlyEquals((float) $data->getLongitude(), (float) $address->getLongitude(), .001);

        // Use zip code specified in adapter if supplied.
        if ($adapterHasZip) {
            return $data->getZip();
        }

        if ($data->isLookup()) {
            // If the coordinates have not changed since the last import then the zip code
            // is assumed to still be valid.
            if ($coordinatesInAdapterAreSameAsInAddress && $addressHasZip) {
                return $address->getZip();
            }

            return $this->geoLocationService->getZipFromGeoLocation(
                $data->getLatitude(),
                $data->getLongitude()
            );
        }

        return null;
    }
}
