import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:lunchpick/core/constants/api_keys.dart';
import 'package:lunchpick/core/utils/app_logger.dart';

/// 주소를 위도/경도로 변환하는 간단한 지오코딩(Geocoding) 서비스
class GeocodingService {
  static const _endpoint = 'https://nominatim.openstreetmap.org/search';
  static const _fallbackLatitude = 37.5665; // 서울시청 위도
  static const _fallbackLongitude = 126.9780; // 서울시청 경도

  /// 도로명/지번 주소를 기반으로 위경도를 조회한다.
  ///
  /// 무료(Nominatim) 엔드포인트를 사용하며 별도 API 키가 필요 없다.
  /// 실패 시 null을 반환하고, 호출 측에서 기본 좌표를 사용할 수 있게 둔다.
  Future<({double latitude, double longitude})?> geocode(String address) async {
    if (address.trim().isEmpty) return null;

    // 1차: VWorld 지오코딩 시도 (키가 존재할 때만)
    final vworldResult = await _geocodeWithVworld(address);
    if (vworldResult != null) {
      return vworldResult;
    }

    // 2차: Nominatim (fallback)
    try {
      final uri = Uri.parse(
        '$_endpoint?format=json&limit=1&q=${Uri.encodeQueryComponent(address)}',
      );

      // Nominatim은 User-Agent 헤더를 요구한다.
      final response = await http.get(
        uri,
        headers: const {'User-Agent': 'lunchpick-geocoder/1.0'},
      );

      if (response.statusCode != 200) {
        AppLogger.debug('[GeocodingService] 실패 status: ${response.statusCode}');
        return null;
      }

      final List<dynamic> results = jsonDecode(response.body) as List<dynamic>;
      if (results.isEmpty) return null;

      final first = results.first as Map<String, dynamic>;
      final lat = double.tryParse(first['lat']?.toString() ?? '');
      final lon = double.tryParse(first['lon']?.toString() ?? '');

      if (lat == null || lon == null) {
        AppLogger.debug('[GeocodingService] 응답 파싱 실패: ${first.toString()}');
        return null;
      }

      return (latitude: lat, longitude: lon);
    } catch (e) {
      AppLogger.debug('[GeocodingService] 예외 발생: $e');
      return null;
    }
  }

  /// 기본 좌표(서울시청)를 반환한다.
  ({double latitude, double longitude}) defaultCoordinates() {
    return (latitude: _fallbackLatitude, longitude: _fallbackLongitude);
  }

  Future<({double latitude, double longitude})?> _geocodeWithVworld(
    String address,
  ) async {
    final apiKey = ApiKeys.vworldApiKey;
    if (apiKey.isEmpty) {
      return null;
    }

    try {
      final uri = Uri.https('api.vworld.kr', '/req/address', {
        'service': 'address',
        'request': 'getcoord',
        'format': 'json',
        'type': 'road', // 도로명 주소 기준
        'key': apiKey,
        'address': address,
      });

      final response = await http.get(
        uri,
        headers: const {'User-Agent': 'lunchpick-geocoder/1.0'},
      );

      if (response.statusCode != 200) {
        AppLogger.debug(
          '[GeocodingService] VWorld 실패 status: ${response.statusCode}',
        );
        return null;
      }

      final Map<String, dynamic> json = jsonDecode(response.body);
      final responseNode = json['response'] as Map<String, dynamic>?;
      if (responseNode == null || responseNode['status'] != 'OK') {
        AppLogger.debug('[GeocodingService] VWorld 응답 오류: ${response.body}');
        return null;
      }

      // VWorld 포인트는 WGS84 lon/lat 순서(x=lon, y=lat)
      final result = responseNode['result'] as Map<String, dynamic>?;
      final point = result?['point'] as Map<String, dynamic>?;
      final x = point?['x']?.toString();
      final y = point?['y']?.toString();
      final lon = x != null ? double.tryParse(x) : null;
      final lat = y != null ? double.tryParse(y) : null;
      if (lat == null || lon == null) {
        AppLogger.debug(
          '[GeocodingService] VWorld 좌표 파싱 실패: ${point.toString()}',
        );
        return null;
      }

      return (latitude: lat, longitude: lon);
    } catch (e) {
      AppLogger.debug('[GeocodingService] VWorld 예외: $e');
      return null;
    }
  }
}
