package de.avatic.lcc.service.api;

import de.avatic.lcc.model.excel.ExcelNode;
import de.avatic.lcc.model.azuremaps.geocoding.batch.BatchGeocodingRequest;
import de.avatic.lcc.model.azuremaps.geocoding.batch.BatchGeocodingResponse;
import de.avatic.lcc.model.azuremaps.geocoding.batch.BatchItem;
import de.avatic.lcc.model.bulk.BulkInstruction;
import de.avatic.lcc.model.db.country.IsoCode;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
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.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
public class BatchGeoApiService {

    private static final Logger logger = LoggerFactory.getLogger(BatchGeoApiService.class);
    private static final String AZURE_MAPS_BATCH_GEOCODING_URL = "https://atlas.microsoft.com/geocode:batch";
    private static final int MAX_BATCH_SIZE = 100; // Azure Maps batch limit

    private final RestTemplate restTemplate;
    private final String subscriptionKey;

    public BatchGeoApiService(RestTemplate restTemplate,
                              @Value("${azure.maps.subscription.key}") String subscriptionKey) {
        this.restTemplate = restTemplate;
        this.subscriptionKey = subscriptionKey;
    }


    public void geocodeBatch(List<BulkInstruction<ExcelNode>> nodes) {
        if (nodes == null || nodes.isEmpty()) {
            logger.warn("Address list is null or empty");
            return;
        }

        ArrayList<BulkInstruction<ExcelNode>> noGeo = new ArrayList<>();
        int totalSuccessful = 0;

        for (var node : nodes) {
            if (node.getEntity().getGeoLat() == null || node.getEntity().getGeoLng() == null) {
                noGeo.add(node);
            }
        }

        for (int currentBatch = 0; currentBatch < noGeo.size(); currentBatch += MAX_BATCH_SIZE) {
            int end = Math.min(currentBatch + MAX_BATCH_SIZE, noGeo.size());
            var chunk = noGeo.subList(currentBatch, end);

            var chunkResult = executeBatchRequest(chunk.stream()
                    .map(BulkInstruction::getEntity).map(this::getGeoCodeString).map(BatchItem::new)
                    .toList());

            if (chunkResult.isPresent()) {

                totalSuccessful += chunkResult.get().getSummary().getSuccessfulRequests();


                for (int itemIdx = 0; itemIdx < chunk.size(); itemIdx++) {
                    var result = chunkResult.get().getBatchItems().get(itemIdx);
                    var node = chunk.get(itemIdx).getEntity();

                    if (!result.getFeatures().isEmpty() &&
                            (result.getFeatures().getFirst().getProperties().getConfidence().equalsIgnoreCase("high") ||
                                    result.getFeatures().getFirst().getProperties().getConfidence().equalsIgnoreCase("medium") ||
                                    result.getFeatures().getFirst().getProperties().getMatchCodes().stream().anyMatch(s -> s.equalsIgnoreCase("good")))) {
                        var geometry = result.getFeatures().getFirst().getGeometry();
                        var properties = result.getFeatures().getFirst().getProperties();
                        node.setGeoLng(BigDecimal.valueOf(geometry.getCoordinates().get(0)));
                        node.setGeoLat(BigDecimal.valueOf(geometry.getCoordinates().get(1)));
                        node.setAddress(properties.getAddress().getFormattedAddress());
                        node.setCountryId(IsoCode.valueOf(properties.getAddress().getCountryRegion().getIso()));
                    } else {
                        logger.warn("Geocoding failed for address {}", node.getAddress());
                        throw new ExcelValidationError("Unable to geocode " + node.getName() + ". Please check your address or enter geo position yourself.");
                    }
                }
            }
        }
    }

    private String getGeoCodeString(ExcelNode excelNode) {
        return excelNode.getAddress() + ", " + excelNode.getCountryId();
    }

    private Optional<BatchGeocodingResponse> executeBatchRequest(List<BatchItem> batchItems) {
        try {
            URI uri = UriComponentsBuilder.fromUriString(AZURE_MAPS_BATCH_GEOCODING_URL)
                    .queryParam("api-version", "2025-01-01")
                    .queryParam("subscription-key", subscriptionKey)
                    .build()
                    .toUri();

            BatchGeocodingRequest request = new BatchGeocodingRequest(batchItems);

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<BatchGeocodingRequest> entity = new HttpEntity<>(request, headers);

            logger.debug("Calling Azure Maps Batch API for {} addresses", batchItems.size());

            ResponseEntity<BatchGeocodingResponse> responseEntity = restTemplate.exchange(
                    uri,
                    HttpMethod.POST,
                    entity,
                    BatchGeocodingResponse.class
            );

            return Optional.ofNullable(responseEntity.getBody());

        } catch (Exception e) {
            logger.error("Failed to execute batch geocoding request", e);
            throw new RuntimeException("Batch geocoding failed", e);
        }
    }

}