<?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\Geocoding\GoogleMaps;

use InPost\International\Api\Point\Model\Coordinates;
use InPost\International\Delivery\Address;
use InPost\International\Geocoding\Exception\GeocodingException;
use InPost\International\Geocoding\Exception\HttpException;
use InPost\International\Geocoding\Exception\NetworkException;
use InPost\International\Geocoding\GeocoderInterface;
use InPost\International\Geocoding\GoogleMaps\Exception\GoogleMapsException;
use InPost\International\Geocoding\GoogleMaps\Model\GeocodingResponse;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\Serializer\Exception\ExceptionInterface as SerializerException;
use Symfony\Component\Serializer\SerializerInterface;

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

final class GoogleMapsGeocoder implements GeocoderInterface
{
    public const ID = 'google_maps';

    private const API_URL = 'https://maps.googleapis.com/maps/api/geocode/json';

    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * @var RequestFactoryInterface
     */
    private $requestFactory;

    /**
     * @var SerializerInterface
     */
    private $serializer;

    /**
     * @var string
     */
    private $apiKey;

    public function __construct(ClientInterface $client, RequestFactoryInterface $requestFactory, SerializerInterface $serializer, string $apiKey)
    {
        $this->client = $client;
        $this->requestFactory = $requestFactory;
        $this->serializer = $serializer;
        $this->apiKey = $apiKey;
    }

    public function geocode(Address $address): ?Coordinates
    {
        if ('' === $address->getAddress()) {
            return null;
        }

        $response = $this->sendRequest($address);

        if (GeocodingResponse::STATUS_ZERO_RESULTS === $response->getStatus()) {
            return null;
        }

        if (GeocodingResponse::STATUS_OK !== $response->getStatus()) {
            throw GoogleMapsException::create($response);
        }

        if (null === $result = $response->getFirstResult()) {
            return null;
        }

        $location = $result->getGeometry()->getLocation();

        return new Coordinates($location->getLatitude(), $location->getLongitude());
    }

    private function sendRequest(Address $address): GeocodingResponse
    {
        $request = $this->createRequest($address);

        try {
            $response = $this->client->sendRequest($request);
        } catch (NetworkExceptionInterface $e) {
            throw new NetworkException($e);
        }

        if (200 !== $response->getStatusCode()) {
            throw new HttpException($request, $response);
        }

        try {
            return $this->serializer->deserialize((string) $response->getBody(), GeocodingResponse::class, 'json');
        } catch (SerializerException $e) {
            throw new GeocodingException('Could not decode the API response.', 0, $e);
        }
    }

    private function createRequest(Address $address): RequestInterface
    {
        $components = [
            sprintf('country:%s', $address->getCountry()->value),
        ];

        if ('' !== $postcode = $address->getPostcode()) {
            $components[] = sprintf('postal_code:%s', $postcode);
        }

        if ('' !== $city = $address->getCity()) {
            $components[] = sprintf('locality:%s', $city);
        }

        $query = http_build_query([
            'key' => $this->apiKey,
            'address' => $address->getAddress(),
            'components' => implode('|', $components),
        ], '', '&', PHP_QUERY_RFC3986);

        $uri = sprintf('%s?%s', self::API_URL, $query);

        return $this->requestFactory->createRequest('GET', $uri);
    }
}
