<?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\Delivery\Point;

use InPost\International\Api\Exception\ApiClientExceptionInterface;
use InPost\International\Api\Point\Exception\PostCodeNotFoundException;
use InPost\International\Api\Point\Model\Coordinates;
use InPost\International\Api\Point\Model\PointCapability;
use InPost\International\Api\Point\PointsApiClientInterface;
use InPost\International\Api\Point\Request\PointsSearchByLocationRequest;
use InPost\International\Delivery\Address;
use InPost\International\Geocoding\Exception\GeocodingException;
use InPost\International\Geocoding\GeocoderInterface;
use InPost\International\Geocoding\NullGeocoder;
use Psr\Log\LoggerInterface;

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

final class ClosestPointFinder implements ClosestPointFinderInterface
{
    /**
     * @var PointsApiClientInterface
     */
    private $client;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var GeocoderInterface
     */
    private $geocoder;

    public function __construct(PointsApiClientInterface $client, LoggerInterface $logger, GeocoderInterface $geocoder = null)
    {
        $this->client = $client;
        $this->logger = $logger;
        $this->geocoder = $geocoder ?? new NullGeocoder();
    }

    public function findClosestPoint(Address $address, PointCapability ...$capabilities): ?RelativePointInterface
    {
        $request = $this->createRequest($address, $capabilities);

        try {
            $points = $this->client->getPointsByLocation($request)->getItems();
        } catch (PostCodeNotFoundException $e) {
            return null;
        } catch (\Throwable $e) {
            $this->handleException($e);
        }

        return $points[0] ?? null;
    }

    private function createRequest(Address $address, array $capabilities): PointsSearchByLocationRequest
    {
        if (null !== $coordinates = $this->geocode($address)) {
            return PointsSearchByLocationRequest::byCoordinates($coordinates, $capabilities);
        }

        return PointsSearchByLocationRequest::byPostcode(
            $address->getPostcode(),
            $address->getCountry(),
            $capabilities
        );
    }

    private function geocode(Address $address): ?Coordinates
    {
        try {
            return $this->geocoder->geocode($address);
        } catch (GeocodingException $e) {
            return null;
        }
    }

    private function handleException(\Throwable $e): void
    {
        if (!$e instanceof ApiClientExceptionInterface) {
            $this->logger->critical('Could not fetch closest point data: {exception}', [
                'exception' => $e,
            ]);
        }

        throw $e;
    }
}
