<?php
// PHP-Skript zur Validierung und Geocodierung von Adressdaten
// STRIKTE REGEL 1: local_district MUSS übergeben werden.
// STRIKTE REGEL 2: Wenn ein 'local_district' übergeben wird, MUSS dieser im Ergebnis matchen.
// STRIKTE REGEL 3: Der gefundene Straßenname MUSS eine hohe Ähnlichkeit mit dem gesuchten Namen haben.

// ====================================================================
// KONFIGURATION (API Schlüssel und Endpunkte)
// ====================================================================

// Dummy-API-Schlüssel (Bitte ersetzen Sie dies durch einen echten Schlüssel, falls erforderlich)
define('HERE_API_KEY', 'okEOI_7ww-l-Eop_6L8XY8YpWT2G-3bBNcO7ZoVT_P8');

// Beispiel-URL für Nominatim
define('NOMINATIM_URL_BASE', 'https://nominatim.openstreetmap.org/search.php');

// MINIMALE ÄHNlichkeit IN PROZENT FÜR DEN STRAßENNAMEN (z.B. 75 für 75%)
define('STREET_SIMILARITY_THRESHOLD', 75); 

// ====================================================================
// CORS und Fehlerberichterstattung
// ====================================================================

// Erlaubt Zugriff von jeder Domain (*). Im Produktivbetrieb sollte dies auf die Frontend-Domain beschränkt werden.
header('Access-Control-Allow-Origin: *'); 
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');

// Spezielle Handhabung für OPTIONS Preflight-Anfragen
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit();
}
// ----------------------------------------------------

// --- Fehlerberichterstattung (DEBUG) ---
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// ====================================================================
// LOGGING-INFRASTRUKTUR
// ====================================================================

// --- Interne Debugging-Log-Funktion ---
$debugLogFile = __DIR__ . '/validate_address_details_debug.log';

/**
 * Schreibt eine Debug-Nachricht in die lokale Log-Datei.
 * @param string $message Die Debug-Meldung.
 */
function internal_debug_log($message) {
    global $debugLogFile;
    // Vermeiden Sie die Protokollierung von binären Daten oder sehr langen Strings
    $logMessage = (is_string($message) && strlen($message) > 500) ? substr($message, 0, 500) . '...' : $message;
    file_put_contents($debugLogFile, date('[Y-m-d H:i:s]') . ' ' . $logMessage . PHP_EOL, FILE_APPEND);
}

internal_debug_log("--- " . basename(__FILE__) . " START ---");

// HTTP-Header für JSON-Antwort setzen
header('Content-Type: application/json');

// ====================================================================
// HILFSFUNKTIONEN
// ====================================================================

/**
 * Führt einen HTTP GET Request aus und gibt die dekodierte JSON-Antwort oder NULL zurück.
 * Bei Upstream-Fehlern wird der Statuscode geloggt und NULL zurückgegeben.
 * @param string $url Die API-URL.
 * @return mixed|null
 */
function call_api($url) {
    internal_debug_log("API-Aufruf gestartet. URL: " . $url);
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_USERAGENT, 'AddressValidatorApp/1.0 (Ihre-Webseite.de)'); 
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curl_error = curl_error($ch);
    
    internal_debug_log("API-Antwort erhalten. HTTP Code: {$http_code}, cURL Error: {$curl_error}");
    
    if ($curl_error) {
        curl_close($ch);
        return null; 
    }
    
    curl_close($ch);

    // WICHTIG: Prüfe hier, ob die Upstream-API erfolgreich war (200)
    if ($http_code !== 200) {
        // Upstream-API hat einen technischen Fehler gemeldet (400, 401, 403, 500 etc.)
        internal_debug_log("API-Aufruf fehlgeschlagen. HTTP Code: {$http_code}. Response Snippet: " . substr($response, 0, 200));
        return null; // Rückgabe NULL signalisiert, dass der Upstream-Aufruf gescheitert ist
    }

    $data = json_decode($response, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        internal_debug_log("JSON Decode Error: " . json_last_error_msg());
        return null;
    }

    internal_debug_log("API-Antwort erfolgreich dekodiert.");
    return $data;
}


/**
 * Prüft die Ähnlichkeit zwischen dem gesuchten und dem gefundenen Straßennamen.
 * @param string $inputStreet Der vom Nutzer eingegebene Straßenname.
 * @param string $foundStreet Der von der API gefundene Straßenname.
 * @param int $threshold Die minimale Ähnlichkeit in Prozent.
 * @return bool True, wenn die Ähnlichkeit über dem Schwellenwert liegt.
 */
function check_street_name_similarity($inputStreet, $foundStreet, $threshold) {
    // Normalisierung: Entferne unnötige Leerzeichen, konvertiere zu Kleinbuchstaben und ersetze Abkürzungen
    $inputStreet = strtolower(trim(str_replace(['-', '.', 'str.', 'strasse'], [' ', ' ', 'straße', 'straße'], $inputStreet)));
    $foundStreet = strtolower(trim(str_replace(['-', '.', 'str.', 'strasse'], [' ', ' ', 'straße', 'straße'], $foundStreet)));
    
    // Entferne die Hausnummer aus dem gefundenen Straßennamen, falls sie fälschlicherweise drin ist
    $foundStreet = trim(preg_replace('/\d+/', '', $foundStreet));

    // Entferne doppelte Leerzeichen nach der Bereinigung
    $inputStreet = preg_replace('/\s+/', ' ', $inputStreet);
    $foundStreet = preg_replace('/\s+/', ' ', $foundStreet);
    
    // Wenn der gefundene Name leer ist
    if (empty($foundStreet) && !empty($inputStreet)) {
        return false;
    }
    
    similar_text($inputStreet, $foundStreet, $percent);
    
    internal_debug_log("Straßennamen-Vergleich: InputNorm='{$inputStreet}', FoundNorm='{$foundStreet}'. Ähnlichkeit: {$percent}%");

    return $percent >= $threshold;
}


/**
 * Führt den Geocoding-Versuch für eine bestimmte Quelle durch und extrahiert das Präzisions-Level.
 *
 * @param string $source Der zu verwendende Dienst ('OSM' oder 'HERE').
 * @param string $street Straße.
 * @param string $house_no Hausnummer (vom Nutzer exakt eingegeben).
 * @param string $city Stadt.
 * @param string $country Land.
 * @param string $local_district Optionaler lokaler Distrikt/Bezirk.
 * @return array Ein assoziatives Array mit 'success' (bool), 'source', 'raw_api_response', 'request_url' und 'data'.
 */
function attempt_geocoding($source, $street, $house_no, $city, $country, $local_district = '') {
    $input_hnr = $house_no;
    $input_street = $street;
    $local_district_normalized = strtolower(trim($local_district));
    $request_url = ''; 
    $data = null; // Stellt sicher, dass $data auch bei Upstream-Fehler existiert

    internal_debug_log("Starte Geocoding-Versuch: Quelle={$source}, Input_HNr={$input_hnr}, LocalDistrict={$local_district}");

    // Logik für OSM / NOMINATIM
    if ($source === 'OSM') {
        
        // ... (Nominatim-Logik bleibt unverändert)
        $street_no_combined = trim($house_no . ' ' . $street);

        $query_params = [
            'street' => $street_no_combined,
            'city' => $city,
            'country' => $country,
            'district' => $local_district, 
            'format' => 'json',
            'limit' => 1
        ];

        $query_params = array_filter($query_params);

        $osm_url = NOMINATIM_URL_BASE . '?' . http_build_query($query_params);
        $request_url = $osm_url; 
        
        internal_debug_log("Generierte OSM URL (strukturierte Suche): " . $osm_url);
        
        $data = call_api($osm_url);

        if (empty($data) || !is_array($data) || count($data) === 0) {
            internal_debug_log("OSM-API-Aufruf lieferte kein gültiges Ergebnis oder ist nicht funktional.");
            // Hier wird NULL von call_api oder ein leeres Ergebnis gefangen (kann Upstream-Fehler sein)
            return ['success' => false, 'source' => 'OSM', 'request_url' => $request_url, 'raw_api_response' => $data, 'message' => "OSM: Keine Koordinaten gefunden oder API-Fehler."];
        }

        $first_result = $data[0];
        $osm_display = $first_result['display_name'];
        $osm_lat = $first_result['lat'];
        $osm_lon = $first_result['lon'];
        
        $found_street = $first_result['address']['road'] ?? '';
        
        // FALLBACK-Logik für Straßennamen (unverändert)
        if (empty($found_street) && !empty($osm_display)) {
            $display_parts = array_map('trim', explode(',', $osm_display));
            $indices_to_check = [3, 2, 1, 0]; 
            foreach ($indices_to_check as $index) {
                if (isset($display_parts[$index])) {
                    $part = $display_parts[$index];
                    $part_street_only = trim(preg_replace('/^\d+\s*|,\s*\d+\s*/', '', $part)); 
                    
                    if (check_street_name_similarity($input_street, $part_street_only, STREET_SIMILARITY_THRESHOLD)) {
                        $found_street = $part_street_only;
                        internal_debug_log("OSM Fallback: Gefundene Straße (Index {$index}) aus Display Name extrahiert: '{$found_street}'");
                        break; 
                    }
                }
            }
        }

        // 1. STRIKTE STRASSENNAME-PRÜFUNG (Fachlicher Fehler)
        if (empty($found_street) || !check_street_name_similarity($input_street, $found_street, STREET_SIMILARITY_THRESHOLD)) {
             internal_debug_log("FEHLER: OSM Straßennamen-Mismatch. Input='{$input_street}', Found='{$found_street}'");
             // HIER: success=false wegen FACHLICHEM Mismatch
             return [
                'success' => false,
                'source' => 'OSM',
                'request_url' => $request_url,
                'raw_api_response' => $data,
                'message' => "Straßennamen-Mismatch: Die gefundene Stra\u00dfe '{$found_street}' weicht stark von der gesuchten Stra\u00dfe '{$input_street}' ab. (\u00c4hnlichkeit unter " . STREET_SIMILARITY_THRESHOLD . "%)"
            ];
        }

        // 2. Versuche, den gefundenen Bezirk aus strukturierten Feldern zu extrahieren (unverändert)
        $returned_district = $first_result['address']['city_district'] ?? $first_result['address']['suburb'] ?? $first_result['address']['neighbourhood'] ?? '';
        
        // 3. OSM FALLBACK LOGIK (unverändert)
        if (empty(trim($returned_district)) && !empty($local_district)) {
            internal_debug_log("OSM District fehlt im Address-Objekt. Versuche Parsing aus Display-Name: " . $osm_display);
            if (stripos($osm_display, $local_district) !== false) {
                $returned_district = $local_district;
                internal_debug_log("OSM Fallback erfolgreich: Input-District '{$local_district}' im Display-Namen gefunden.");
            } else {
                 $parts = explode(', ', $osm_display);
                 $street_index = -1;
                 foreach ($parts as $index => $part) {
                     if (strpos($part, $street) !== false && $index > 0) {
                         $street_index = $index;
                         break;
                     }
                 }
                 if ($street_index !== -1 && $street_index + 1 < count($parts)) {
                     $potential_district = trim($parts[$street_index + 1]);
                     $city_normalized = strtolower(trim($city));
                     if (strtolower($potential_district) !== $city_normalized && !empty($potential_district)) {
                          $returned_district = $potential_district;
                          internal_debug_log("OSM Fallback: Bezirk aus Display-Name extrahiert für Fehlermeldung: '{$returned_district}'.");
                     }
                 }
            }
        }
        
        $returned_district_normalized = strtolower(trim($returned_district));

        // 4. STRIKTE BEZIRKSPRÜFUNG (Fachlicher Fehler)
        if (!empty($local_district)) {
            internal_debug_log("Distrikt-Vergleich (OSM): InputNorm='{$local_district_normalized}' vs ReturnedNorm='{$returned_district_normalized}'");

            if ($local_district_normalized !== $returned_district_normalized) {
                internal_debug_log("FEHLER: OSM Distrikt-Mismatch. Input={$local_district}, Returned={$returned_district}");
                 // HIER: success=false wegen FACHLICHEM Mismatch
                return [
                    'success' => false,
                    'source' => 'OSM',
                    'request_url' => $request_url,
                    'raw_api_response' => $data,
                    'message' => "Distrikt-Mismatch: Adresse ist laut OSM in '{$returned_district}', aber '{$local_district}' wurde angefordert."
                ];
            }
        }

        // Ableitung der Präzision (unverändert)
        $precision = 'Grob (Ort)'; 
        if (isset($first_result['type']) && $first_result['type'] === 'house') {
            $precision = 'Präzise (Hnr)';
        } elseif (isset($first_result['type']) && $first_result['type'] === 'street') {
            $precision = 'Grob (Straße)';
        } elseif (strpos($osm_display, $input_hnr) !== false) {
             $precision = 'Interpoliert'; 
        }

        internal_debug_log("OSM-Ergebnis gefunden: DisplayName={$osm_display}, Precision={$precision}, DistrictMatch=true");

        return [
            'success' => true,
            'source' => 'OSM',
            'request_url' => $request_url, 
            'raw_api_response' => $data, 
            'data' => [
                'latitude' => (float)$osm_lat,
                'longitude' => (float)$osm_lon,
                'display_name' => $osm_display,
                'precision_level' => $precision
            ]
        ];

    } 
    // Logik für HERE
    elseif ($source === 'HERE') {
        
        // ... (HERE Logik zur URL-Erstellung unverändert)
        $street_no_combined = $street . ' ' . $input_hnr;
        $query_parts = [$street_no_combined];
        
        if (!empty($local_district)) {
            $query_parts[] = $local_district;
        }
        
        $query_parts[] = $city;
        $query_parts[] = $country;

        $full_query = implode(', ', $query_parts);
        
        $here_url = "https://geocode.search.hereapi.com/v1/geocode?" .
                    "q=" . urlencode($full_query) .
                    "&apiKey=" . HERE_API_KEY .
                    "&limit=1";
        
        $request_url = $here_url; 

        internal_debug_log("Generierte HERE URL (inkl. Local District): " . $here_url);

        $data = call_api($here_url);
        
        // WICHTIG: Prüfe hier auf Upstream-Fehler (NULL-Rückgabe von call_api)
        if ($data === null) {
            internal_debug_log("HERE API hat einen Upstream-Fehler gemeldet oder keine JSON-Antwort geliefert.");
             return ['success' => false, 'source' => 'HERE', 'request_url' => $request_url, 'raw_api_response' => $data, 'message' => "HERE API-Fehler (Prüfen Sie den API Key oder die Upstream-Verbindung)."];
        }

        if (isset($data['items']) && count($data['items']) > 0) {
            $first_result = $data['items'][0];
            $returned_hnr = $first_result['address']['houseNumber'] ?? ''; 
            $returned_road = $first_result['address']['street'] ?? '';
            $matchLevel = $first_result['matchLevel'] ?? 'street'; 
            $resultType = $first_result['resultType'] ?? '';
            $houseNumberType = $first_result['houseNumberType'] ?? ''; 
            
            // 1. STRIKTE STRASSENNAME-PRÜFUNG (Fachlicher Fehler)
            if (!check_street_name_similarity($input_street, $returned_road, STREET_SIMILARITY_THRESHOLD)) {
                 internal_debug_log("FEHLER: HERE Straßennamen-Mismatch. Input='{$input_street}', Found='{$returned_road}'");
                 // HIER: success=false wegen FACHLICHEM Mismatch
                 return [
                    'success' => false,
                    'source' => 'HERE',
                    'request_url' => $request_url,
                    'raw_api_response' => $data,
                    'message' => "Straßennamen-Mismatch: Die gefundene Stra\u00dfe '{$returned_road}' weicht stark von der gesuchten Stra\u00dfe '{$input_street}' ab. (\u00c4hnlichkeit unter " . STREET_SIMILARITY_THRESHOLD . "%)"
                ];
            }


            // 2. HERE liefert den Bezirk im Feld 'district' (unverändert)
            $returned_district = trim($first_result['address']['district'] ?? '');
            $returned_district_normalized = strtolower($returned_district);
            
            internal_debug_log("HERE-Ergebnis gefunden: MatchLevel={$matchLevel}, ReturnedHnr={$returned_hnr}, ReturnedDistrict={$returned_district}, HnrType={$houseNumberType}");

            // 3. STRIKTE BEZIRKSPRÜFUNG (Fachlicher Fehler)
            if (!empty($local_district)) {
                
                internal_debug_log("Distrikt-Vergleich (HERE): InputNorm='{$local_district_normalized}' vs ReturnedNorm='{$returned_district_normalized}'");

                if ($local_district_normalized !== $returned_district_normalized) {
                    internal_debug_log("FEHLER: HERE Distrikt-Mismatch. Input={$local_district}, Returned={$returned_district}");
                    // HIER: success=false wegen FACHLICHEM Mismatch
                    return [
                        'success' => false,
                        'source' => 'HERE',
                        'request_url' => $request_url, 
                        'raw_api_response' => $data,
                        'message' => "Distrikt-Mismatch: Adresse ist laut HERE in '{$returned_district}', aber '{$local_district}' wurde angefordert."
                    ];
                }
            }

            // ... (Präzisionsprüfung unverändert)
            $precision = 'Grob (Unbekannt)';
            $potential_high_precision = ($resultType === 'houseNumber' && !empty($returned_hnr));
            
            if (!$potential_high_precision) {
                $precision_map = [
                    'interpolated' => 'Interpoliert',
                    'street' => 'Grob (Straße)',
                    'district' => 'Grob (Distrikt)',
                    'city' => 'Grob (Ort)',
                ];
                $precision = $precision_map[$matchLevel] ?? 'Grob (Unbekannt)';
            } else {
                if ($houseNumberType === 'interpolated' || strpos(strtolower($first_result['title']), 'interpolated') !== false) {
                    $precision = 'Interpoliert';
                    internal_debug_log("Präzision gesetzt: Interpoliert - Laut 'houseNumberType'.");
                } elseif ($input_hnr === $returned_hnr) {
                    $precision = 'Präzise (Hnr)';
                    internal_debug_log("Präzision gesetzt: Präzise (Hnr) - Exakter Match.");
                } else {
                     $precision = 'Interpoliert';
                    internal_debug_log("Präzision gesetzt: Interpoliert (Fallback) - Hnr Match war unklar.");
                }
            }
            
            internal_debug_log("Endgültige Präzision: " . $precision);

            return [
                'success' => true,
                'source' => 'HERE', 
                'request_url' => $request_url, 
                'raw_api_response' => $data, 
                'data' => [
                    'latitude' => $first_result['position']['lat'],
                    'longitude' => $first_result['position']['lng'],
                    'display_name' => $first_result['address']['label'],
                    'precision_level' => $precision
                ]
            ];
        } else {
             // HERE hat keine Treffer gemeldet (leere items[])
             return ['success' => false, 'source' => 'HERE', 'request_url' => $request_url, 'raw_api_response' => $data, 'message' => "HERE: Keine Koordinaten f\u00fcr die Adresse gefunden."];
        }
    }
    
    internal_debug_log("Geocoding-Versuch fehlgeschlagen: Quelle={$source}");
    return ['success' => false, 'source' => $source, 'request_url' => $request_url, 'raw_api_response' => $data, 'message' => "Geocoding-Versuch für Quelle '{$source}' schlug fehl."];
}

// ====================================================================
// HAUPTPROGRAMM
// ====================================================================

// Adressparameter aus der GET-Anfrage extrahieren und trimmen
$street = trim($_GET['street'] ?? '') ?: '';
$house_no = trim($_GET['house_no'] ?? '') ?: '';
$city = trim($_GET['city'] ?? '') ?: '';
$country = trim($_GET['country'] ?? '') ?: '';
$local_district = trim($_GET['local_district'] ?? ''); 
$source = strtoupper(trim($_GET['source'] ?? 'OSM'));

if (!in_array($source, ['OSM', 'HERE'])) {
     $source = 'OSM'; 
}

internal_debug_log("Anfrage-Parameter erfasst: " . print_r([
    'street' => $street, 'house_no' => $house_no, 'city' => $city, 
    'local_district' => $local_district, 'source' => $source
], true));

// --- PRÜFUNG: Technische Bad Request / Fehlende Pflichtparameter ---
// DIESER FEHLER IST TECHNISCH: Die Anfrage ist unvollständig oder ungültig.
if (empty($local_district)) {
    internal_debug_log("FEHLER: local_district fehlt. Dieses Feld ist nun obligatorisch.");
    http_response_code(400); // TECHNISCHER FEHLER
    echo json_encode(['status' => 'error', 'message' => 'Fehlende Adress-Parameter. local_district ist jetzt ein obligatorisches Feld.']);
    internal_debug_log("--- " . basename(__FILE__) . " END (HTTP 400 - Technical) ---");
    exit;
}

if (empty($street) || empty($house_no) || empty($city) || empty($country)) {
    internal_debug_log("FEHLER: Fehlende Adress-Parameter. Anfrage abgebrochen.");
    http_response_code(400); // TECHNISCHER FEHLER
    echo json_encode(['status' => 'error', 'message' => 'Fehlende Adress-Parameter. street, house_no, city und country sind erforderlich.']);
    internal_debug_log("--- " . basename(__FILE__) . " END (HTTP 400 - Technical) ---");
    exit;
}
// ----------------------------------------------------------------------


$preferred_source = $source;

$result_attempt = attempt_geocoding($preferred_source, $street, $house_no, $city, $country, $local_district);

// Erstelle die finale Antwort
if ($result_attempt['success']) {
    // FACHLICH UND TECHNISCH ERFOLGREICH
    $response = [
        'status' => 'success',
        'source' => $result_attempt['source'], 
        'precision_level' => $result_attempt['data']['precision_level'], 
        'message' => 'Adresse erfolgreich verifiziert und geocodiert.',
        'data' => $result_attempt['data'],
        'request_url' => $result_attempt['request_url'] ?? 'N/A', 
        'raw_api_response' => $result_attempt['raw_api_response'], 
    ];
    internal_debug_log("SUCCESS. Quelle: " . $preferred_source . ", Precision: " . $result_attempt['data']['precision_level']);
    http_response_code(200); // WICHTIG: Setze 200 bei Erfolg
} else {
    // FACHLICH FEHLGESCHLAGEN (oder Upstream API meldete 400/401, was als fachlicher Fehler behandelt wird)
    $errorMessage = $result_attempt['message'] ?? "Die Adresse konnte mit der angefragten Quelle '{$preferred_source}' nicht verifiziert werden. Es wurde KEIN Fallback versucht.";
    
    // WICHTIG: Wir senden HTTP 200 OK zurück, da die Anfrage vom Client technisch korrekt war
    // und der Fehler auf der Anwendungsebene (Validierung) liegt.
    http_response_code(200); 
    
    $response = [
        'status' => 'error', // Im JSON-Body signalisieren wir den Anwendungsfehler
        'message' => $errorMessage,
        'details' => 'Adresse: ' . $street . ' ' . $house_no . ', ' . ($local_district ? $local_district . ', ' : '') . $city . ', ' . $country,
        'request_url' => $result_attempt['request_url'] ?? 'N/A', 
        'raw_api_response' => $result_attempt['raw_api_response'] ?? null,
    ];
    internal_debug_log("Quelle {$preferred_source} konnte die Adresse nicht verifizieren. Ausgabe HTTP 200, JSON Status: error.");
}

// Ausgabe der JSON-Antwort
echo json_encode($response, JSON_PRETTY_PRINT);

internal_debug_log("--- " . basename(__FILE__) . " END ---");
?>