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;

/**
 * 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();

            // Prüfen, ob die Adresse als "ROOFTOP" (genaue Adresse) oder
            // "RANGE_INTERPOLATED" gefunden wurde
            String locationType = geometry.path("location_type").asText();
            boolean isPrecise = "ROOFTOP".equals(locationType) || "RANGE_INTERPOLATED".equals(locationType);

            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)) {
                    } else if ("postal_code".equals(typeStr)) {
                        hasPostalCode = true;
                    }
                }
            }

            // Ergebnis setzen
            result.setValid(isPrecise && hasPostalCode);
            result.setFormattedAddress(formattedAddress);
            result.setLatitude(lat);
            result.setLongitude(lng);

            if (result.isValid()) {
                result.setValidationMessage("Adresse erfolgreich validiert");
            } else {
                result.setValidationMessage("Adresse ungenau gefunden (keine Hausnummer oder Postleitzahl)");
            }

            log.debug("Adressvalidierung erfolgreich: {} -> {}", addressString, formattedAddress);

        } 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) {
        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;
        }

        // Prüfen, ob beide Adressen gültige Koordinaten haben
        if (pickupResult == null || !pickupResult.isValid() || deliveryResult == null || !deliveryResult.isValid()) {
            routeResult.setRouteMessage("Beide Adressen müssen validiert sein");
            return routeResult;
        }

        try {
            // Koordinaten für Start und Ziel
            String origin = String.format("%s,%s", pickupResult.getLatitude(), pickupResult.getLongitude());
            String destination = String.format("%s,%s", deliveryResult.getLatitude(), deliveryResult.getLongitude());

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

            log.debug("Berechne Route von {} nach {}", pickupResult.getFormattedAddress(),
                    deliveryResult.getFormattedAddress());

            // 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 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;
            }

            // Ersten Leg (Hauptstrecke) verwenden
            JsonNode firstLeg = legs.get(0);

            // Distanz extrahieren
            JsonNode distanceNode = firstLeg.path("distance");
            int distanceMeters = distanceNode.path("value").asInt();
            String distanceText = distanceNode.path("text").asText();

            // Dauer extrahieren
            JsonNode durationNode = firstLeg.path("duration");
            int durationSeconds = durationNode.path("value").asInt();
            String durationText = durationNode.path("text").asText();

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

            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;
    }
}
