package de.assecutor.votianlt.pages.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.assecutor.votianlt.model.AddressValidationResult;
import de.assecutor.votianlt.model.RouteCalculationResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Locale;

/**
 * Service zur Validierung von Adressen über die Google Geocoding API.
 */
@Service
@Slf4j
public class AddressValidationService {

    @Value("${app.google.maps.api-key:}")
    private String googleMapsApiKey;

    private final HttpClient httpClient;
    private final ObjectMapper objectMapper;

    private static final String GEOCODING_API_URL = "https://maps.googleapis.com/maps/api/geocode/json";
    private static final String DIRECTIONS_API_URL = "https://maps.googleapis.com/maps/api/directions/json";

    public AddressValidationService() {
        this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
        this.objectMapper = new ObjectMapper();
    }

    /**
     * Validiert eine Adresse über die Google Geocoding API.
     *
     * @param addressType
     *            "pickup" oder "delivery"
     * @param street
     *            Straße
     * @param houseNumber
     *            Hausnummer
     * @param zip
     *            Postleitzahl
     * @param city
     *            Stadt
     * @return AddressValidationResult mit dem Validierungsergebnis
     */
    public AddressValidationResult validateAddress(String addressType, String street, String houseNumber, String zip,
            String city) {
        AddressValidationResult result = new AddressValidationResult(addressType, street, houseNumber, zip, city);

        // Prüfen, ob API-Key konfiguriert ist
        if (googleMapsApiKey == null || googleMapsApiKey.isBlank()) {
            log.warn("Google Maps API Key nicht konfiguriert. Adressvalidierung übersprungen.");
            result.setValidationMessage("API-Key nicht konfiguriert");
            return result;
        }

        // Prüfen, ob alle erforderlichen Felder vorhanden sind
        if (isEmpty(street) || isEmpty(zip) || isEmpty(city)) {
            result.setValidationMessage("Unvollständige Adresse");
            return result;
        }

        try {
            // Adressstring zusammenbauen
            String addressString = buildAddressString(street, houseNumber, zip, city);
            String encodedAddress = URLEncoder.encode(addressString, StandardCharsets.UTF_8);

            // URL für die Geocoding API erstellen
            String requestUrl = String.format("%s?address=%s&key=%s&language=de&region=de", GEOCODING_API_URL,
                    encodedAddress, googleMapsApiKey);

            log.debug("Validiere Adresse: {}", addressString);

            // HTTP Request senden
            HttpRequest request = HttpRequest.newBuilder().uri(URI.create(requestUrl)).GET()
                    .timeout(Duration.ofSeconds(10)).build();

            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() != 200) {
                log.error("Fehler bei der Geocoding API: HTTP {}", response.statusCode());
                result.setValidationMessage("Fehler bei der API-Anfrage");
                return result;
            }

            // JSON Response parsen
            JsonNode rootNode = objectMapper.readTree(response.body());
            String status = rootNode.path("status").asText();

            if (!"OK".equals(status)) {
                log.warn("Geocoding API Status: {} für Adresse: {}", status, addressString);
                result.setValidationMessage("Adresse nicht gefunden: " + status);
                return result;
            }

            // Ergebnisse extrahieren
            JsonNode results = rootNode.path("results");
            if (results.isEmpty()) {
                result.setValidationMessage("Keine Ergebnisse gefunden");
                return result;
            }

            JsonNode firstResult = results.get(0);
            String formattedAddress = firstResult.path("formatted_address").asText();

            // Geokoordinaten extrahieren
            JsonNode geometry = firstResult.path("geometry");
            JsonNode location = geometry.path("location");
            double lat = location.path("lat").asDouble();
            double lng = location.path("lng").asDouble();

            // Google liefert für valide Adressen nicht immer nur
            // ROOFTOP/RANGE_INTERPOLATED.
            // Für unseren Flow reicht ein erfolgreicher Geocoding-Treffer mit Koordinaten.
            String locationType = geometry.path("location_type").asText();
            boolean hasCoordinates = location.hasNonNull("lat") && location.hasNonNull("lng");

            boolean hasStreetNumber = false;
            boolean hasPostalCode = false;

            JsonNode addressComponents = firstResult.path("address_components");
            for (JsonNode component : addressComponents) {
                JsonNode types = component.path("types");
                for (JsonNode type : types) {
                    String typeStr = type.asText();
                    if ("street_number".equals(typeStr)) {
                        hasStreetNumber = true;
                    } else if ("postal_code".equals(typeStr)) {
                        hasPostalCode = true;
                    }
                }
            }

            // Ergebnis setzen
            result.setValid(hasCoordinates);
            result.setFormattedAddress(formattedAddress);
            result.setLatitude(lat);
            result.setLongitude(lng);

            if (result.isValid()) {
                result.setValidationMessage("Adresse erfolgreich validiert");
                log.debug("Adressvalidierung erfolgreich: {} -> {} (locationType={}, streetNumber={}, postalCode={})",
                        addressString, formattedAddress, locationType, hasStreetNumber, hasPostalCode);
            } else {
                result.setValidationMessage("Adresse gefunden, aber ohne verwertbare Koordinaten");
                log.warn("Adressvalidierung unvollständig: {} -> {} (locationType={}, streetNumber={}, postalCode={})",
                        addressString, formattedAddress, locationType, hasStreetNumber, hasPostalCode);
            }

        } catch (Exception e) {
            log.error("Fehler bei der Adressvalidierung", e);
            result.setValidationMessage("Fehler: " + e.getMessage());
        }

        return result;
    }

    /**
     * Baut einen zusammenhängenden Adressstring für die API-Anfrage.
     */
    private String buildAddressString(String street, String houseNumber, String zip, String city) {
        StringBuilder sb = new StringBuilder();
        sb.append(street.trim());
        if (!isEmpty(houseNumber)) {
            sb.append(" ").append(houseNumber.trim());
        }
        sb.append(", ").append(zip.trim()).append(" ").append(city.trim());
        sb.append(", Deutschland");
        return sb.toString();
    }

    private boolean isEmpty(String value) {
        return value == null || value.trim().isEmpty();
    }

    /**
     * Berechnet die schnellste Route zwischen zwei Adressen über die Google
     * Directions API.
     *
     * @param pickupResult
     *            Validierungsergebnis der Abholadresse (muss gültige Koordinaten
     *            enthalten)
     * @param deliveryResult
     *            Validierungsergebnis der Lieferadresse (muss gültige Koordinaten
     *            enthalten)
     * @return RouteCalculationResult mit Entfernung und Dauer
     */
    public RouteCalculationResult calculateRoute(AddressValidationResult pickupResult,
            AddressValidationResult deliveryResult) {
        return calculateRoute(List.of(pickupResult, deliveryResult));
    }

    /**
     * Berechnet die schnellste Route über mehrere validierte Stationen.
     *
     * @param stationResults
     *            validierte Stationen in Fahrreihenfolge
     * @return RouteCalculationResult mit Gesamtentfernung und Gesamtdauer
     */
    public RouteCalculationResult calculateRoute(List<AddressValidationResult> stationResults) {
        RouteCalculationResult routeResult = new RouteCalculationResult();

        // Prüfen, ob API-Key konfiguriert ist
        if (googleMapsApiKey == null || googleMapsApiKey.isBlank()) {
            log.warn("Google Maps API Key nicht konfiguriert. Routenberechnung übersprungen.");
            routeResult.setRouteMessage("API-Key nicht konfiguriert");
            return routeResult;
        }

        if (stationResults == null || stationResults.size() < 2) {
            routeResult.setRouteMessage("Mindestens zwei validierte Stationen werden benötigt");
            return routeResult;
        }

        // Prüfen, ob alle Adressen gültige Koordinaten haben
        if (stationResults.stream().anyMatch(result -> result == null || !result.isValid())) {
            routeResult.setRouteMessage("Alle Stationen müssen validiert sein");
            return routeResult;
        }

        try {
            AddressValidationResult originResult = stationResults.getFirst();
            AddressValidationResult destinationResult = stationResults.getLast();

            // Koordinaten für Start und Ziel
            String origin = formatLatLng(originResult);
            String destination = formatLatLng(destinationResult);

            // URL für die Directions API erstellen
            StringBuilder requestUrl = new StringBuilder(
                    String.format("%s?origin=%s&destination=%s&mode=driving&key=%s&language=de&region=de",
                            DIRECTIONS_API_URL, origin, destination, googleMapsApiKey));

            if (stationResults.size() > 2) {
                List<String> waypoints = stationResults.subList(1, stationResults.size() - 1).stream()
                        .map(this::formatLatLng).toList();
                requestUrl.append("&waypoints=").append(String.join("|", waypoints));
            }

            log.debug("Berechne Route über {} Stationen von {} nach {}", stationResults.size(),
                    originResult.getFormattedAddress(), destinationResult.getFormattedAddress());

            // HTTP Request senden
            HttpRequest request = HttpRequest.newBuilder().uri(URI.create(requestUrl.toString())).GET()
                    .timeout(Duration.ofSeconds(10)).build();

            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() != 200) {
                log.error("Fehler bei der Directions API: HTTP {}", response.statusCode());
                routeResult.setRouteMessage("Fehler bei der API-Anfrage");
                return routeResult;
            }

            // JSON Response parsen
            JsonNode rootNode = objectMapper.readTree(response.body());
            String status = rootNode.path("status").asText();

            if (!"OK".equals(status)) {
                log.warn("Directions API Status: {}", status);
                routeResult.setRouteMessage("Route konnte nicht berechnet werden: " + status);
                return routeResult;
            }

            // Routen extrahieren
            JsonNode routes = rootNode.path("routes");
            if (routes.isEmpty()) {
                routeResult.setRouteMessage("Keine Route gefunden");
                return routeResult;
            }

            JsonNode firstRoute = routes.get(0);
            JsonNode legs = firstRoute.path("legs");
            if (legs.isEmpty()) {
                routeResult.setRouteMessage("Keine Routeninformationen verfügbar");
                return routeResult;
            }

            int totalDistanceMeters = 0;
            int totalDurationSeconds = 0;

            for (JsonNode leg : legs) {
                totalDistanceMeters += leg.path("distance").path("value").asInt();
                totalDurationSeconds += leg.path("duration").path("value").asInt();
            }

            double totalDistanceKm = totalDistanceMeters / 1000.0;
            String distanceText = String.format(Locale.GERMANY, "%.1f km", totalDistanceKm);
            String durationText = formatDuration(totalDurationSeconds);

            // Ergebnis setzen
            routeResult.setValid(true);
            routeResult.setDistanceKm(totalDistanceKm);
            routeResult.setDurationSeconds(totalDurationSeconds);
            routeResult.setFormattedDistance(distanceText);
            routeResult.setFormattedDuration(durationText);
            routeResult.setRouteMessage(String.format("Route: %s, Dauer: %s", distanceText, durationText));

            log.debug("Routenberechnung erfolgreich: {} km, {} Min.", routeResult.getDistanceKm(),
                    routeResult.getDurationMinutes());

        } catch (Exception e) {
            log.error("Fehler bei der Routenberechnung", e);
            routeResult.setRouteMessage("Fehler: " + e.getMessage());
        }

        return routeResult;
    }

    private String formatLatLng(AddressValidationResult result) {
        return String.format(Locale.US, "%s,%s", result.getLatitude(), result.getLongitude());
    }

    private String formatDuration(int durationSeconds) {
        int hours = durationSeconds / 3600;
        int minutes = (durationSeconds % 3600) / 60;

        if (hours > 0) {
            return String.format("%d Std. %d Min.", hours, minutes);
        }
        return String.format("%d Min.", minutes);
    }
}
