package de.avatic.lcc.service.api;

import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.generic.NodeType;
import de.avatic.lcc.model.azuremaps.geocoding.Feature;
import de.avatic.lcc.model.azuremaps.geocoding.GeocodingResponse;
import de.avatic.lcc.model.azuremaps.geocoding.GeocodingResult;
import de.avatic.lcc.model.db.country.IsoCode;
import de.avatic.lcc.model.db.nodes.Location;
import de.avatic.lcc.repositories.country.CountryRepository;
import de.avatic.lcc.service.transformer.generic.CountryTransformer;
import de.avatic.lcc.service.transformer.generic.LocationTransformer;
import de.avatic.lcc.util.exception.internalerror.GeocodingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.List;

@Service
public class GeoApiService {

    private static final Logger logger = LoggerFactory.getLogger(GeoApiService.class);
    private static final String AZURE_MAPS_GEOCODING_URL = "https://atlas.microsoft.com/geocode";

    private final RestTemplate restTemplate;
    private final String subscriptionKey;
    private final LocationTransformer locationTransformer;
    private final CountryRepository countryRepository;
    private final CountryTransformer countryTransformer;

    public GeoApiService(RestTemplate restTemplate,
                         @Value("${azure.maps.subscription.key}") String subscriptionKey, LocationTransformer locationTransformer, CountryRepository countryRepository, CountryTransformer countryTransformer) {
        this.restTemplate = restTemplate;
        this.subscriptionKey = subscriptionKey;
        this.locationTransformer = locationTransformer;
        this.countryRepository = countryRepository;
        this.countryTransformer = countryTransformer;
    }

    public GeocodingResult geocode(String address) {
        if (address == null || address.trim().isEmpty()) {
            logger.warn("Address is null or empty");
            return null;
        }

        try {
            URI uri = UriComponentsBuilder.fromUriString(AZURE_MAPS_GEOCODING_URL)
                    .queryParam("api-version", "2025-01-01")
                    .queryParam("query", "{address}")
                    .queryParam("subscription-key", subscriptionKey)
                    .buildAndExpand(address)
                    .encode()
                    .toUri();

            logger.info("Calling Azure Maps API for address: {}", address);

            GeocodingResponse response = restTemplate.getForObject(uri, GeocodingResponse.class);

            if (response != null && response.getFeatures() != null && !response.getFeatures().isEmpty()) {
                Feature feature = response.getFeatures().getFirst();
                logger.debug("Successfully geocoded address: {}", address);
                return new GeocodingResult(feature);
            }

            logger.warn("No results found for address: {}", address);
            return null;

        } catch (Exception e) {
            logger.error("Failed to geocode address: {}", address, e);
            throw new GeocodingException();
        }
    }

    public Location locate(String address) {
        GeocodingResult result = geocode(address);
        return result != null ? result.getLocation() : null;
    }

    public NodeDTO locateNode(String address) {
        NodeDTO node = new NodeDTO();

        GeocodingResult result = geocode(address);

        if (result == null || result.getAddress() == null || result.getLocation() == null)  throw new GeocodingException();

        node.setUserNode(true);
        node.setDeprecated(false);
        node.setTypes(List.of(NodeType.SOURCE));

        node.setAddress(result.getAddress().getFormattedAddress());
        node.setCountry(countryTransformer.toCountryDTO(countryRepository.getByIsoCode(IsoCode.valueOf(result.getAddress().getCountryRegion().getIso())).orElseThrow()));
        node.setLocation(locationTransformer.toLocationDTO(result.getLocation()));

        return node;
    }
}