<?php
declare(strict_types=1);

namespace App\Service\Utils;

use App\ApiResources\Utils\GpsCoordinate;
use App\Tests\Service\Utils\GpsCoordinateUtilsTest;
use Exception;
use JsonException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
 * Class GpsCoordinateSearching : service aidant à la manipulation de l'API OpenStreetMap
 * @package App\Service\Utils
 */
class GpsCoordinateUtils
{
    private HttpClientInterface $clientOpenStreetMap;

    public function __construct(
        HttpClientInterface $openstreetmap
    )
    {
        $this->clientOpenStreetMap = $openstreetmap;
    }

    /**
     * Renvoie un tableau d'adresse existante correspondant à la recherche d'adresse demandée
     * @param string|null $street
     * @param string|null $cp
     * @param string|null $city
     * @return mixed
     * @throws JsonException
     * @throws ClientExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ServerExceptionInterface
     * @throws TransportExceptionInterface
     * @see GpsCoordinateUtilsTest::testSearchGpsCoordinates()
     */
    public function searchGpsCoordinates(?string $street, ?string $cp, ?string $city): mixed
    {
        try{
            $url = sprintf('search?addressdetails=1&format=json&limit=10&street=%s&postalcode=%s&city=%s', $street, $cp, $city);
            $response = $this->clientOpenStreetMap->request('GET', $url)->getContent();
        }catch(Exception $e){
            throw new NotFoundHttpException('no_reverse_gps_coordinate', $e, 404);
        }
        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    /**
     * Renvoi l'adresse correspondant à la latitude et longitude demandée
     * @param float $latitude
     * @param float $longitude
     * @return mixed
     * @throws ClientExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ServerExceptionInterface
     * @throws TransportExceptionInterface
     * @throws JsonException
     * @see GpsCoordinateUtilsTest::testReverseGpsCoordinates()
     */
    public function reverseGpsCoordinates(float $latitude, float $longitude): mixed
    {
        try{
            $url = sprintf('reverse?addressdetails=1&format=json&lat=%s&lon=%s', $latitude, $longitude);
            $response = $this->clientOpenStreetMap->request('GET', $url)->getContent();
        }catch(Exception $e){
            throw new NotFoundHttpException('no_reverse_gps_coordinate', $e, 404);
        }

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    /**
     * Transforme une réponse d'API en ressource GpsCoordinate
     * @param mixed[] $gpsApiResponse
     * @return GpsCoordinate
     * @see GpsCoordinateUtilsTest::testCreateGpsCoordinate()
     */
    public function createGpsCoordinate(array $gpsApiResponse): GpsCoordinate
    {
        $address = $this->transformAddress($gpsApiResponse['address']);
        $gpsCoordinate = new GpsCoordinate();
        $gpsCoordinate
            ->setLatitude((float) $gpsApiResponse['lat'])
            ->setLongitude((float) $gpsApiResponse['lon'])
            ->setCity($address['city'])
            ->setCp($address['cp'])
            ->setStreetAddress($address['streetAddress'])
            ->setStreetAddressSecond($address['streetAddressSecond']);
        return $gpsCoordinate;
    }

    /**
     * Permet de faire correspondre le bloc adresse renvoyé par l'API avec des éléments plus communs
     * @param list<string> $address
     * @return array<string, string|null>
     */
    public function transformAddress(array $address): array
    {
        $addressTransformed['streetAddress'] = array_key_exists('road', $address) ? $address['road'] : (array_key_exists('hamlet', $address) ? $address['hamlet'] : null);
        $addressTransformed['streetAddressSecond'] =  array_key_exists('road', $address) && array_key_exists('hamlet', $address)  ? $address['hamlet'] : null;
        $addressTransformed['city'] = array_key_exists('town', $address) ? $address['town'] : (array_key_exists('village', $address) ? $address['village'] : null);
        $addressTransformed['cp'] = array_key_exists('postcode', $address) ? $address['postcode'] : null;
        return $addressTransformed;
    }
}
