<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\Configuration\Validator;

use InPost\International\Country;
use InPost\International\Delivery\Address;
use InPost\International\Geocoding\Exception\GeocodingException;
use InPost\International\Geocoding\GeocoderFactoryInterface;
use InPost\International\Geocoding\GoogleMaps\Exception\GoogleMapsException;
use InPost\International\Geocoding\GoogleMaps\Exception\InvalidApiKeyException;
use InPost\International\Geocoding\GoogleMaps\Exception\OverQueryLimitException;
use InPost\International\Geocoding\GoogleMaps\GoogleMapsGeocoder;
use InPost\International\Geocoding\GoogleMaps\GoogleMapsGeocoderFactory;
use Psr\Http\Client\NetworkExceptionInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

if (!defined('_PS_VERSION_')) {
    exit;
}

final class GoogleMapsApiKeyValidator extends ConstraintValidator
{
    /**
     * @var GoogleMapsGeocoderFactory
     */
    private $geocoderFactory;

    /**
     * @param GoogleMapsGeocoderFactory $geocoderFactory
     */
    public function __construct(GeocoderFactoryInterface $geocoderFactory)
    {
        $this->geocoderFactory = $geocoderFactory;
    }

    public function validate($value, Constraint $constraint): void
    {
        if (!$constraint instanceof GoogleMapsApiKey) {
            throw new UnexpectedTypeException($constraint, GoogleMapsApiKey::class);
        }

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

        if (!is_string($value)) {
            throw new UnexpectedTypeException($value, 'string');
        }

        /** @var GoogleMapsGeocoder $geocoder */
        $geocoder = $this->geocoderFactory->create([
            'api_key' => $value,
        ]);

        try {
            $geocoder->geocode(new Address('Pana Tadeusza 4', '30-727', 'Kraków', Country::Poland()));
        } catch (OverQueryLimitException $e) {
            // API key is valid but a request / quota limit is currently exceeded
        } catch (NetworkExceptionInterface $e) {
            $this->context
                ->buildViolation('Could not connect to the Google Maps API.')
                ->setTranslationDomain('Modules.Inpostinternational.Validators')
                ->addViolation();
        } catch (InvalidApiKeyException $e) {
            $this->context
                ->buildViolation('The provided API key is invalid.')
                ->setTranslationDomain('Modules.Inpostinternational.Validators')
                ->addViolation();
        } catch (GoogleMapsException $e) {
            $this->addViolationForApiError($e);
        } catch (GeocodingException $e) {
            $this->context
                ->buildViolation('An error occurred while trying to geocode a test address: {{ message }}')
                ->setParameter('{{ message }}', $e->getMessage())
                ->setTranslationDomain('Modules.Inpostinternational.Validators')
                ->addViolation();
        }
    }

    private function addViolationForApiError(GoogleMapsException $e): void
    {
        if (null === $errorMessage = $e->getErrorMessage()) {
            $this->context
                ->buildViolation('Geocoding a test address failed with status code "{{ status }}".')
                ->setParameter('{{ status }}', $e->getStatusCode())
                ->setTranslationDomain('Modules.Inpostinternational.Validators')
                ->addViolation();

            return;
        }

        $this->context
            ->buildViolation('Geocoding a test address failed with status code "{{ status }}": {{ errorMessage }}')
            ->setParameter('{{ status }}', $e->getStatusCode())
            ->setParameter('{{ errorMessage }}', $errorMessage)
            ->setTranslationDomain('Modules.Inpostinternational.Validators')
            ->addViolation();
    }
}
