package de.avatic.lcc.service.api;

import de.avatic.lcc.model.azuremaps.route.RouteDirectionsResponse;
import de.avatic.lcc.model.db.nodes.Distance;
import de.avatic.lcc.model.db.nodes.DistanceMatrixState;
import de.avatic.lcc.model.db.nodes.Node;
import de.avatic.lcc.repositories.DistanceMatrixRepository;
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.math.BigDecimal;
import java.net.URI;
import java.time.LocalDateTime;
import java.util.Optional;

@Service
public class DistanceApiService {

    private static final Logger logger = LoggerFactory.getLogger(DistanceApiService.class);
    private static final String AZURE_MAPS_ROUTE_API = "https://atlas.microsoft.com/route/directions/json";

    private final DistanceMatrixRepository distanceMatrixRepository;
    private final RestTemplate restTemplate;

    @Value("${azure.maps.subscription.key}")
    private String subscriptionKey;

    public DistanceApiService(DistanceMatrixRepository distanceMatrixRepository,
                              RestTemplate restTemplate) {
        this.distanceMatrixRepository = distanceMatrixRepository;
        this.restTemplate = restTemplate;
    }

    public Optional<Distance> getDistance(Node from, Node to) {

        if (from == null || to == null) {
            logger.warn("Source or destination node is null");
            return Optional.empty();
        }

        if (from.getGeoLat() == null || from.getGeoLng() == null ||
                to.getGeoLat() == null || to.getGeoLng() == null) {
            logger.warn("Missing geo coordinates for nodes: from={}, to={}", from.getId(), to.getId());
            return Optional.empty();
        }

        Optional<Distance> cachedDistance = distanceMatrixRepository.getDistance(from, to);

        if (cachedDistance.isPresent()) {
            logger.debug("Found cached distance from node {} to node {}", from.getId(), to.getId());
            return cachedDistance;
        }

        logger.debug("Fetching distance from Azure Maps for nodes {} to {}", from.getId(), to.getId());
        Optional<Distance> fetchedDistance = fetchDistanceFromAzureMaps(from, to);

        if (fetchedDistance.isPresent()) {
            distanceMatrixRepository.saveDistance(fetchedDistance.get());
            return fetchedDistance;
        }

        return Optional.empty();
    }

    private Optional<Distance> fetchDistanceFromAzureMaps(Node from, Node to) {
        try {
            String url = UriComponentsBuilder.fromUriString(AZURE_MAPS_ROUTE_API)
                    .queryParam("api-version", "1.0")
                    .queryParam("subscription-key", subscriptionKey)
                    .queryParam("query", String.format("%s,%s:%s,%s",
                            from.getGeoLat(), from.getGeoLng(),
                            to.getGeoLat(), to.getGeoLng()))
                    .encode()
                    .toUriString();

            RouteDirectionsResponse response = restTemplate.getForObject(url, RouteDirectionsResponse.class);

            if (response != null && response.getRoutes() != null && !response.getRoutes().isEmpty()) {
                Integer distanceInMeters = response.getRoutes().getFirst().getSummary().getLengthInMeters();

                Distance distance = new Distance();
                distance.setFromNodeId(from.getId());
                distance.setToNodeId(to.getId());
                distance.setFromGeoLat(from.getGeoLat());
                distance.setFromGeoLng(from.getGeoLng());
                distance.setToGeoLat(to.getGeoLat());
                distance.setToGeoLng(to.getGeoLng());
                distance.setDistance(BigDecimal.valueOf(distanceInMeters));
                distance.setState(DistanceMatrixState.VALID);
                distance.setUpdatedAt(LocalDateTime.now());

                logger.info("Successfully fetched distance: {} meters", distanceInMeters);
                return Optional.of(distance);
            } else {
                logger.warn("No routes found in Azure Maps response");
            }
        } catch (Exception e) {
            logger.error("Error fetching distance from Azure Maps", e);
        }

        return Optional.empty();
    }


}