import 'package:hive_flutter/hive_flutter.dart';
import 'package:lunchpick/domain/entities/restaurant.dart';
import 'package:lunchpick/domain/repositories/restaurant_repository.dart';
import 'package:lunchpick/core/utils/distance_calculator.dart';
import 'package:lunchpick/data/datasources/remote/naver_search_service.dart';
import 'package:lunchpick/data/datasources/remote/naver_map_parser.dart';
import 'package:lunchpick/core/constants/api_keys.dart';
import 'package:lunchpick/core/utils/app_logger.dart';

class RestaurantRepositoryImpl implements RestaurantRepository {
  static const String _boxName = 'restaurants';
  final NaverSearchService _naverSearchService;

  RestaurantRepositoryImpl({NaverSearchService? naverSearchService})
    : _naverSearchService = naverSearchService ?? NaverSearchService();

  Future<Box<Restaurant>> get _box async =>
      await Hive.openBox<Restaurant>(_boxName);

  @override
  Future<List<Restaurant>> getAllRestaurants() async {
    final box = await _box;
    return box.values.toList();
  }

  @override
  Future<Restaurant?> getRestaurantById(String id) async {
    final box = await _box;
    return box.get(id);
  }

  @override
  Future<void> addRestaurant(Restaurant restaurant) async {
    final box = await _box;
    await box.put(restaurant.id, restaurant);
  }

  @override
  Future<void> updateRestaurant(Restaurant restaurant) async {
    final box = await _box;
    await box.put(restaurant.id, restaurant);
  }

  @override
  Future<void> deleteRestaurant(String id) async {
    final box = await _box;
    await box.delete(id);
  }

  @override
  Future<List<Restaurant>> getRestaurantsByCategory(String category) async {
    final restaurants = await getAllRestaurants();
    return restaurants.where((r) => r.category == category).toList();
  }

  @override
  Future<List<String>> getAllCategories() async {
    final restaurants = await getAllRestaurants();
    final categories = restaurants.map((r) => r.category).toSet().toList();
    categories.sort();
    return categories;
  }

  @override
  Stream<List<Restaurant>> watchRestaurants() async* {
    final box = await _box;
    final initial = box.values.toList();
    AppLogger.debug('[restaurant_repo] initial load count: ${initial.length}');
    yield initial;
    yield* box.watch().map((event) {
      final values = box.values.toList();
      AppLogger.debug(
        '[restaurant_repo] box watch event -> count: ${values.length} '
        '(key=${event.key}, deleted=${event.deleted})',
      );
      return values;
    });
  }

  @override
  Future<void> updateLastVisitDate(
    String restaurantId,
    DateTime visitDate,
  ) async {
    final restaurant = await getRestaurantById(restaurantId);
    if (restaurant != null) {
      final updatedRestaurant = Restaurant(
        id: restaurant.id,
        name: restaurant.name,
        category: restaurant.category,
        subCategory: restaurant.subCategory,
        description: restaurant.description,
        phoneNumber: restaurant.phoneNumber,
        roadAddress: restaurant.roadAddress,
        jibunAddress: restaurant.jibunAddress,
        latitude: restaurant.latitude,
        longitude: restaurant.longitude,
        lastVisitDate: visitDate,
        source: restaurant.source,
        createdAt: restaurant.createdAt,
        updatedAt: DateTime.now(),
        naverPlaceId: restaurant.naverPlaceId,
        naverUrl: restaurant.naverUrl,
        businessHours: restaurant.businessHours,
        lastVisited: visitDate,
        visitCount: restaurant.visitCount + 1,
        needsAddressVerification: restaurant.needsAddressVerification,
      );
      await updateRestaurant(updatedRestaurant);
    }
  }

  @override
  Future<List<Restaurant>> getRestaurantsWithinDistance({
    required double userLatitude,
    required double userLongitude,
    required double maxDistanceInMeters,
  }) async {
    final restaurants = await getAllRestaurants();
    return restaurants.where((restaurant) {
      final distanceInKm = DistanceCalculator.calculateDistance(
        lat1: userLatitude,
        lon1: userLongitude,
        lat2: restaurant.latitude,
        lon2: restaurant.longitude,
      );
      final distanceInMeters = distanceInKm * 1000;
      return distanceInMeters <= maxDistanceInMeters;
    }).toList();
  }

  @override
  Future<List<Restaurant>> getRestaurantsNotVisitedInDays(int days) async {
    final restaurants = await getAllRestaurants();
    final cutoffDate = DateTime.now().subtract(Duration(days: days));

    return restaurants.where((restaurant) {
      if (restaurant.lastVisitDate == null) return true;
      return restaurant.lastVisitDate!.isBefore(cutoffDate);
    }).toList();
  }

  @override
  Future<List<Restaurant>> searchRestaurants(String query) async {
    if (query.isEmpty) {
      return await getAllRestaurants();
    }

    final restaurants = await getAllRestaurants();
    final lowercaseQuery = query.toLowerCase();

    return restaurants.where((restaurant) {
      return restaurant.name.toLowerCase().contains(lowercaseQuery) ||
          (restaurant.description?.toLowerCase().contains(lowercaseQuery) ??
              false) ||
          restaurant.category.toLowerCase().contains(lowercaseQuery) ||
          restaurant.roadAddress.toLowerCase().contains(lowercaseQuery);
    }).toList();
  }

  @override
  Future<List<Restaurant>> searchRestaurantsFromNaver({
    required String query,
    double? latitude,
    double? longitude,
  }) async {
    return _naverSearchService.searchNearbyRestaurants(
      query: query,
      latitude: latitude,
      longitude: longitude,
    );
  }

  @override
  Future<Restaurant> addRestaurantFromUrl(String url) async {
    return _processRestaurantFromUrl(url, persist: true);
  }

  @override
  Future<Restaurant> previewRestaurantFromUrl(String url) async {
    return _processRestaurantFromUrl(url, persist: false);
  }

  Future<Restaurant> _processRestaurantFromUrl(
    String url, {
    required bool persist,
  }) async {
    try {
      // URL 유효성 검증
      if (!url.contains('naver.com') && !url.contains('naver.me')) {
        throw Exception('유효하지 않은 네이버 지도 URL입니다.');
      }

      // NaverSearchService로 식당 정보 추출
      Restaurant restaurant = await _naverSearchService.getRestaurantFromUrl(
        url,
      );

      // API 키가 설정되어 있으면 추가 정보 검색
      if (ApiKeys.areKeysConfigured() && restaurant.name != '네이버 지도 장소') {
        try {
          final detailedRestaurant = await _naverSearchService
              .searchRestaurantDetails(
                name: restaurant.name,
                address: restaurant.roadAddress,
                latitude: restaurant.latitude,
                longitude: restaurant.longitude,
              );

          if (detailedRestaurant != null) {
            // 기존 정보와 API 검색 결과 병합
            restaurant = Restaurant(
              id: restaurant.id,
              name: restaurant.name,
              category: detailedRestaurant.category,
              subCategory: detailedRestaurant.subCategory,
              description:
                  detailedRestaurant.description ?? restaurant.description,
              phoneNumber:
                  detailedRestaurant.phoneNumber ?? restaurant.phoneNumber,
              roadAddress: detailedRestaurant.roadAddress,
              jibunAddress: detailedRestaurant.jibunAddress,
              latitude: detailedRestaurant.latitude,
              longitude: detailedRestaurant.longitude,
              lastVisitDate: restaurant.lastVisitDate,
              source: DataSource.NAVER,
              createdAt: restaurant.createdAt,
              updatedAt: DateTime.now(),
              naverPlaceId: restaurant.naverPlaceId,
              naverUrl: restaurant.naverUrl,
              businessHours:
                  detailedRestaurant.businessHours ?? restaurant.businessHours,
              lastVisited: restaurant.lastVisited,
              visitCount: restaurant.visitCount,
            );
          }
        } catch (e) {
          AppLogger.debug('API 검색 실패, 스크래핑된 정보만 사용: $e');
        }
      }

      if (persist) {
        await _ensureRestaurantIsUnique(restaurant);
        await addRestaurant(restaurant);
      }

      return restaurant;
    } catch (e) {
      if (e is NaverMapParseException) {
        throw Exception('네이버 지도 파싱 실패: ${e.message}');
      }
      rethrow;
    }
  }

  Future<void> _ensureRestaurantIsUnique(Restaurant restaurant) async {
    final restaurants = await getAllRestaurants();

    if (restaurant.roadAddress.isNotEmpty ||
        restaurant.jibunAddress.isNotEmpty) {
      final addressDuplicate = restaurants.firstWhere(
        (r) =>
            r.name == restaurant.name &&
            (r.roadAddress == restaurant.roadAddress ||
                r.jibunAddress == restaurant.jibunAddress),
        orElse: () => Restaurant(
          id: '',
          name: '',
          category: '',
          subCategory: '',
          roadAddress: '',
          jibunAddress: '',
          latitude: 0,
          longitude: 0,
          source: DataSource.USER_INPUT,
          createdAt: DateTime.now(),
          updatedAt: DateTime.now(),
        ),
      );

      if (addressDuplicate.id.isNotEmpty) {
        throw Exception('동일한 이름과 주소의 맛집이 이미 존재합니다: ${addressDuplicate.name}');
      }
    }

    final locationDuplicate = restaurants.firstWhere(
      (r) {
        if (r.name != restaurant.name) return false;

        final distanceInKm = DistanceCalculator.calculateDistance(
          lat1: r.latitude,
          lon1: r.longitude,
          lat2: restaurant.latitude,
          lon2: restaurant.longitude,
        );
        final distanceInMeters = distanceInKm * 1000;
        return distanceInMeters < 50;
      },
      orElse: () => Restaurant(
        id: '',
        name: '',
        category: '',
        subCategory: '',
        roadAddress: '',
        jibunAddress: '',
        latitude: 0,
        longitude: 0,
        source: DataSource.USER_INPUT,
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      ),
    );

    if (locationDuplicate.id.isNotEmpty) {
      throw Exception(
        '50m 이내에 동일한 이름의 맛집이 이미 존재합니다: ${locationDuplicate.name}',
      );
    }
  }

  @override
  Future<Restaurant?> getRestaurantByNaverPlaceId(String naverPlaceId) async {
    final restaurants = await getAllRestaurants();
    try {
      return restaurants.firstWhere((r) => r.naverPlaceId == naverPlaceId);
    } catch (e) {
      return null;
    }
  }
}
