import 'package:flutter/foundation.dart';
import 'package:lunchpick/core/utils/app_logger.dart';
import 'package:uuid/uuid.dart';

import '../../api/naver_api_client.dart';
import '../../api/naver/naver_local_search_api.dart';
import '../../../core/errors/network_exceptions.dart';
import '../../../domain/entities/restaurant.dart';
import 'naver_map_parser.dart';
import 'naver_url_processor.dart';

/// 네이버 검색 서비스
///
/// 네이버 지도 URL 파싱과 로컬 검색 API를 통합한 서비스입니다.
class NaverSearchService {
  final NaverApiClient _apiClient;
  final NaverMapParser _mapParser;
  final NaverUrlProcessor _urlProcessor;
  final Uuid _uuid = const Uuid();

  // 성능 최적화를 위한 정규식 캐싱
  static final RegExp _nonAlphanumericRegex = RegExp(r'[^가-힣a-z0-9]');

  NaverSearchService({
    NaverApiClient? apiClient,
    NaverMapParser? mapParser,
    NaverUrlProcessor? urlProcessor,
  }) : _apiClient = apiClient ?? NaverApiClient(),
       _mapParser = mapParser ?? NaverMapParser(apiClient: apiClient),
       _urlProcessor =
           urlProcessor ??
           NaverUrlProcessor(apiClient: apiClient, mapParser: mapParser);

  /// URL에서 식당 정보 가져오기
  ///
  /// 네이버 지도 URL(단축 URL 포함)에서 식당 정보를 추출합니다.
  ///
  /// [url] 네이버 지도 URL 또는 단축 URL
  ///
  /// Throws:
  /// - [NaverMapParseException] URL 파싱 실패 시
  /// - [NetworkException] 네트워크 오류 발생 시
  Future<Restaurant> getRestaurantFromUrl(String url) async {
    try {
      return await _urlProcessor.processUrl(url);
    } catch (e) {
      if (e is NaverMapParseException || e is NetworkException) {
        rethrow;
      }
      throw ParseException(message: '식당 정보를 가져올 수 없습니다: $e', originalError: e);
    }
  }

  /// 키워드로 주변 식당 검색
  ///
  /// 검색어와 현재 위치를 기반으로 주변 식당을 검색합니다.
  Future<List<Restaurant>> searchNearbyRestaurants({
    required String query,
    double? latitude,
    double? longitude,
    int maxResults = 20,
    String sort = 'random', // random, comment
  }) async {
    try {
      final searchResults = await _apiClient.searchLocal(
        query: query,
        latitude: latitude,
        longitude: longitude,
        display: maxResults,
        sort: sort,
      );

      return searchResults
          .map((result) => result.toRestaurant(id: _uuid.v4()))
          .toList();
    } catch (e) {
      if (e is NetworkException) {
        rethrow;
      }
      throw ParseException(message: '식당 검색에 실패했습니다: $e', originalError: e);
    }
  }

  /// 식당 이름으로 상세 정보 검색
  ///
  /// 식당 이름과 위치를 기반으로 더 자세한 정보를 검색합니다.
  Future<Restaurant?> searchRestaurantDetails({
    required String name,
    String? address,
    double? latitude,
    double? longitude,
  }) async {
    try {
      // 검색어 구성
      String query = name;
      if (address != null && address.isNotEmpty) {
        // 주소에서 시/구 정보 추출
        final addressParts = address.split(' ');
        if (addressParts.length >= 2) {
          query = '${addressParts[0]} ${addressParts[1]} $name';
        }
      }

      final searchResults = await _apiClient.searchLocal(
        query: query,
        latitude: latitude,
        longitude: longitude,
        display: 5,
        sort: 'comment', // 상세 검색 시 리뷰가 많은 곳 우선
      );

      if (searchResults.isEmpty) {
        return null;
      }

      // 가장 유사한 결과 찾기 (주소가 없으면 거리 기반 선택 포함)
      final bestMatch = _findBestMatch(
        name,
        searchResults,
        latitude: latitude,
        longitude: longitude,
        address: address,
      );

      if (bestMatch != null) {
        final restaurant = bestMatch.toRestaurant(id: _uuid.v4());

        // 네이버 지도 URL이 있으면 상세 정보 파싱 시도
        if (restaurant.naverUrl != null) {
          try {
            final detailedRestaurant = await _mapParser.parseRestaurantFromUrl(
              restaurant.naverUrl!,
            );

            // 기존 정보와 병합
            return Restaurant(
              id: restaurant.id,
              name: restaurant.name,
              category: restaurant.category,
              subCategory: restaurant.subCategory,
              description:
                  detailedRestaurant.description ?? restaurant.description,
              phoneNumber: restaurant.phoneNumber,
              roadAddress: restaurant.roadAddress,
              jibunAddress: restaurant.jibunAddress,
              latitude: restaurant.latitude,
              longitude: restaurant.longitude,
              lastVisitDate: restaurant.lastVisitDate,
              source: restaurant.source,
              createdAt: restaurant.createdAt,
              updatedAt: DateTime.now(),
              naverPlaceId:
                  detailedRestaurant.naverPlaceId ?? restaurant.naverPlaceId,
              naverUrl: restaurant.naverUrl,
              businessHours:
                  detailedRestaurant.businessHours ?? restaurant.businessHours,
              lastVisited: restaurant.lastVisited,
              visitCount: restaurant.visitCount,
            );
          } catch (e) {
            // 상세 파싱 실패해도 기본 정보 반환
            AppLogger.debug(
              '[NaverSearchService] 상세 정보 파싱 실패: ${e.toString()}',
            );
          }
        }

        return restaurant;
      }

      return null;
    } catch (e) {
      if (e is NetworkException) {
        rethrow;
      }
      throw ParseException(
        message: '식당 상세 정보 검색에 실패했습니다: $e',
        originalError: e,
      );
    }
  }

  /// 가장 유사한 검색 결과 찾기
  NaverLocalSearchResult? _findBestMatch(
    String targetName,
    List<NaverLocalSearchResult> results, {
    double? latitude,
    double? longitude,
    String? address,
  }) {
    if (results.isEmpty) return null;

    // 정확히 일치하는 결과 우선
    final exactMatch = results.firstWhere(
      (result) => result.title.toLowerCase() == targetName.toLowerCase(),
      orElse: () => results.first,
    );

    if (exactMatch.title.toLowerCase() == targetName.toLowerCase()) {
      return exactMatch;
    }

    // 주소가 없고 위치 정보가 있는 경우 - 가장 가까운 업체 선택
    // TODO: 네이버 좌표계(mapx, mapy)를 WGS84 좌표계로 변환하는 로직 필요
    // 현재는 네이버 API가 좌표 기반 정렬을 지원하므로 첫 번째 결과 사용
    if ((address == null || address.isEmpty) &&
        latitude != null &&
        longitude != null) {
      // 네이버 API는 coordinate 파라미터로 좌표 기반 정렬을 지원
      // searchRestaurants에서 이미 가까운 순으로 정렬되어 반환됨
      return results.first;
    }

    // 유사도 계산 (간단한 버전)
    NaverLocalSearchResult? bestMatch;
    double bestScore = 0.0;

    for (final result in results) {
      final score = _calculateSimilarity(targetName, result.title);
      if (score > bestScore) {
        bestScore = score;
        bestMatch = result;
      }
    }

    // 유사도가 너무 낮으면 null 반환
    if (bestScore < 0.5) {
      return null;
    }

    return bestMatch ?? results.first;
  }

  /// 문자열 유사도 계산 (Jaccard 유사도)
  double _calculateSimilarity(String str1, String str2) {
    final s1 = str1.toLowerCase().replaceAll(_nonAlphanumericRegex, '');
    final s2 = str2.toLowerCase().replaceAll(_nonAlphanumericRegex, '');

    if (s1.isEmpty || s2.isEmpty) return 0.0;

    // 포함 관계 확인
    if (s1.contains(s2) || s2.contains(s1)) {
      return 0.8;
    }

    // 문자 집합으로 변환
    final set1 = s1.split('').toSet();
    final set2 = s2.split('').toSet();

    // Jaccard 유사도 계산
    final intersection = set1.intersection(set2).length;
    final union = set1.union(set2).length;

    return union > 0 ? intersection / union : 0.0;
  }

  /// 리소스 정리
  void dispose() {
    _apiClient.dispose();
    _mapParser.dispose();
  }

  // 테스트를 위한 내부 메서드 접근
  @visibleForTesting
  NaverLocalSearchResult? findBestMatchForTesting(
    String targetName,
    List<NaverLocalSearchResult> results, {
    double? latitude,
    double? longitude,
    String? address,
  }) {
    return _findBestMatch(
      targetName,
      results,
      latitude: latitude,
      longitude: longitude,
      address: address,
    );
  }

  @visibleForTesting
  double calculateSimilarityForTesting(String str1, String str2) {
    return _calculateSimilarity(str1, str2);
  }
}
