package com.tasteby.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

@Service
public class GeocodingService {

    private static final Logger log = LoggerFactory.getLogger(GeocodingService.class);

    private final WebClient webClient;
    private final ObjectMapper mapper;
    private final String apiKey;

    public GeocodingService(ObjectMapper mapper,
                             @Value("${app.google.maps-api-key}") String apiKey) {
        this.webClient = WebClient.builder()
                .baseUrl("https://maps.googleapis.com/maps/api")
                .build();
        this.mapper = mapper;
        this.apiKey = apiKey;
    }

    /**
     * Look up restaurant coordinates via Google Maps.
     * Tries Places Text Search first, falls back to Geocoding API.
     */
    public Map<String, Object> geocodeRestaurant(String name, String address) {
        String query = name;
        if (address != null && !address.isBlank()) {
            query += " " + address;
        }

        // Try Places Text Search
        Map<String, Object> result = placesTextSearch(query);
        if (result != null) return result;

        // Fallback: Geocoding
        return geocode(query);
    }

    private Map<String, Object> placesTextSearch(String query) {
        try {
            String response = webClient.get()
                    .uri(uriBuilder -> uriBuilder.path("/place/textsearch/json")
                            .queryParam("query", query)
                            .queryParam("key", apiKey)
                            .queryParam("language", "ko")
                            .queryParam("type", "restaurant")
                            .build())
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofSeconds(10));

            JsonNode data = mapper.readTree(response);
            if (!"OK".equals(data.path("status").asText()) || !data.path("results").has(0)) {
                return null;
            }

            JsonNode place = data.path("results").get(0);
            JsonNode loc = place.path("geometry").path("location");

            var result = new HashMap<String, Object>();
            result.put("latitude", loc.path("lat").asDouble());
            result.put("longitude", loc.path("lng").asDouble());
            result.put("formatted_address", place.path("formatted_address").asText(""));
            result.put("google_place_id", place.path("place_id").asText(""));

            if (!place.path("business_status").isMissingNode()) {
                result.put("business_status", place.path("business_status").asText());
            }
            if (!place.path("rating").isMissingNode()) {
                result.put("rating", place.path("rating").asDouble());
            }
            if (!place.path("user_ratings_total").isMissingNode()) {
                result.put("rating_count", place.path("user_ratings_total").asInt());
            }

            // Fetch phone/website from Place Details
            String placeId = place.path("place_id").asText(null);
            if (placeId != null) {
                var details = placeDetails(placeId);
                if (details != null) {
                    result.putAll(details);
                }
            }
            return result;
        } catch (Exception e) {
            log.warn("Places text search failed for '{}': {}", query, e.getMessage());
            return null;
        }
    }

    private Map<String, Object> placeDetails(String placeId) {
        try {
            String response = webClient.get()
                    .uri(uriBuilder -> uriBuilder.path("/place/details/json")
                            .queryParam("place_id", placeId)
                            .queryParam("key", apiKey)
                            .queryParam("language", "ko")
                            .queryParam("fields", "formatted_phone_number,website")
                            .build())
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofSeconds(10));

            JsonNode data = mapper.readTree(response);
            if (!"OK".equals(data.path("status").asText())) return null;

            JsonNode res = data.path("result");
            var details = new HashMap<String, Object>();
            if (!res.path("formatted_phone_number").isMissingNode()) {
                details.put("phone", res.path("formatted_phone_number").asText());
            }
            if (!res.path("website").isMissingNode()) {
                details.put("website", res.path("website").asText());
            }
            return details;
        } catch (Exception e) {
            log.warn("Place details failed for '{}': {}", placeId, e.getMessage());
            return null;
        }
    }

    /**
     * Parse Korean address into region format "나라|시/도|구/군".
     * Example: "대한민국 서울특별시 강남구 역삼동 123" → "한국|서울|강남구"
     */
    public static String parseRegionFromAddress(String address) {
        if (address == null || address.isBlank()) return null;
        String[] parts = address.split("\\s+");
        String country = "";
        String city = "";
        String district = "";

        for (String p : parts) {
            if (p.equals("대한민국") || p.equals("South Korea")) {
                country = "한국";
            } else if (p.endsWith("특별시") || p.endsWith("광역시") || p.endsWith("특별자치시")) {
                city = p.replace("특별시", "").replace("광역시", "").replace("특별자치시", "");
            } else if (p.endsWith("도") && !p.endsWith("동") && p.length() <= 5) {
                city = p;
            } else if (p.endsWith("구") || p.endsWith("군") || (p.endsWith("시") && !city.isEmpty())) {
                if (district.isEmpty()) district = p;
            }
        }

        if (country.isEmpty() && !city.isEmpty()) country = "한국";
        if (country.isEmpty()) return null;
        // #292 — 빈 토큰은 region 문자열에 포함시키지 않는다(`한국||구` 형식 방지).
        StringBuilder sb = new StringBuilder(country);
        if (!city.isEmpty()) {
            sb.append('|').append(city);
            if (!district.isEmpty()) sb.append('|').append(district);
        } else if (!district.isEmpty()) {
            // city 없이 district만 있는 경우는 정확도 낮으므로 무시
        }
        return sb.toString();
    }

    private Map<String, Object> geocode(String query) {
        try {
            String response = webClient.get()
                    .uri(uriBuilder -> uriBuilder.path("/geocode/json")
                            .queryParam("address", query)
                            .queryParam("key", apiKey)
                            .queryParam("language", "ko")
                            .build())
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofSeconds(10));

            JsonNode data = mapper.readTree(response);
            if (!"OK".equals(data.path("status").asText()) || !data.path("results").has(0)) {
                return null;
            }

            JsonNode result = data.path("results").get(0);
            JsonNode loc = result.path("geometry").path("location");

            var map = new HashMap<String, Object>();
            map.put("latitude", loc.path("lat").asDouble());
            map.put("longitude", loc.path("lng").asDouble());
            map.put("formatted_address", result.path("formatted_address").asText(""));
            map.put("google_place_id", "");
            return map;
        } catch (Exception e) {
            log.warn("Geocoding failed for '{}': {}", query, e.getMessage());
            return null;
        }
    }
}
