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.Location;
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.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<Integer> getDistance(Location from, Location to) {
        if (from == null || to == null) {
            logger.warn("Source or destination location is null");
            return Optional.empty();
        }

        if (from.getLatitude() == null || from.getLongitude() == null ||
                to.getLatitude() == null || to.getLongitude() == null) {
            return Optional.empty();
        }

        RouteDirectionsResponse response = fetchDistanceFromAzureMaps(BigDecimal.valueOf(from.getLatitude()), BigDecimal.valueOf(from.getLongitude()), BigDecimal.valueOf(to.getLatitude()), BigDecimal.valueOf(to.getLongitude()));

        if (response != null && response.getRoutes() != null && !response.getRoutes().isEmpty()) {
            return Optional.of(response.getRoutes().getFirst().getSummary().getLengthInMeters());
        }

        return Optional.empty();
    }

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

        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, isUsrFrom, to, isUsrTo);

        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, isUsrFrom, to, isUsrTo);

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

        return Optional.empty();
    }

    private RouteDirectionsResponse fetchDistanceFromAzureMaps(BigDecimal fromLat, BigDecimal fromLng, BigDecimal toLat, BigDecimal toLng) {
        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",
                        fromLat, fromLng,
                        toLat, toLng))
                .encode()
                .toUriString();

        return restTemplate.getForObject(url, RouteDirectionsResponse.class);
    }

    private Optional<Distance> fetchDistanceFromAzureMaps(Node from, boolean isUsrFrom, Node to, boolean isUsrTo) {
        try {

            RouteDirectionsResponse response = fetchDistanceFromAzureMaps(from.getGeoLat(), from.getGeoLng(), to.getGeoLat(), to.getGeoLng());

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

                Distance distance = new Distance();

                if (isUsrFrom) {
                    distance.setFromUserNodeId(from.getId());
                    distance.setFromNodeId(null);
                } else {
                    distance.setFromUserNodeId(null);
                    distance.setFromNodeId(from.getId());
                }

                if (isUsrTo) {
                    distance.setToUserNodeId(to.getId());
                    distance.setToNodeId(null);
                } else {
                    distance.setToUserNodeId(null);
                    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();
    }


}