import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lunchpick/domain/entities/restaurant.dart';
import 'package:lunchpick/domain/repositories/restaurant_repository.dart';
import 'package:lunchpick/presentation/providers/restaurant_provider.dart';
import 'package:lunchpick/presentation/providers/di_providers.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';

@GenerateMocks([RestaurantRepository])
import 'restaurant_provider_test.mocks.dart';

void main() {
  group('RestaurantProvider Tests', () {
    late ProviderContainer container;
    late MockRestaurantRepository mockRepository;

    setUp(() {
      mockRepository = MockRestaurantRepository();
      container = ProviderContainer(
        overrides: [
          restaurantRepositoryProvider.overrideWithValue(mockRepository),
        ],
      );
    });

    tearDown(() {
      container.dispose();
    });

    test('restaurantListProvider returns stream of restaurants', () async {
      // Arrange
      final restaurants = [
        Restaurant(
          id: '1',
          name: '테스트 맛집',
          category: 'korean',
          subCategory: '한식',
          roadAddress: '서울시',
          jibunAddress: '서울시',
          latitude: 37.5,
          longitude: 127.0,
          source: DataSource.USER_INPUT,
          createdAt: DateTime.now(),
          updatedAt: DateTime.now(),
        ),
      ];

      when(
        mockRepository.watchRestaurants(),
      ).thenAnswer((_) => Stream.value(restaurants));

      // Act
      final result = container.read(restaurantListProvider);

      // Assert
      expect(result, isA<AsyncValue<List<Restaurant>>>());
    });

    test('searchRestaurantsProvider filters restaurants by query', () async {
      // Arrange
      final restaurants = [
        Restaurant(
          id: '1',
          name: '김치찌개',
          category: 'korean',
          subCategory: '한식',
          roadAddress: '서울시',
          jibunAddress: '서울시',
          latitude: 37.5,
          longitude: 127.0,
          source: DataSource.USER_INPUT,
          createdAt: DateTime.now(),
          updatedAt: DateTime.now(),
        ),
        Restaurant(
          id: '2',
          name: '된장찌개',
          category: 'korean',
          subCategory: '한식',
          roadAddress: '서울시',
          jibunAddress: '서울시',
          latitude: 37.5,
          longitude: 127.0,
          source: DataSource.USER_INPUT,
          createdAt: DateTime.now(),
          updatedAt: DateTime.now(),
        ),
      ];

      when(
        mockRepository.searchRestaurants('김치'),
      ).thenAnswer((_) async => [restaurants[0]]);

      // Act
      final result = await container.read(
        searchRestaurantsProvider('김치').future,
      );

      // Assert
      expect(result.length, 1);
      expect(result.first.name, '김치찌개');
    });

    test('selectedCategoryProvider updates category filter', () {
      // Act
      container.read(selectedCategoryProvider.notifier).state = '한식';

      // Assert
      expect(container.read(selectedCategoryProvider), '한식');
    });

    test('restaurantNotifier adds new restaurant', () async {
      // Arrange
      when(mockRepository.addRestaurant(any)).thenAnswer((_) async {});

      // Act
      final notifier = container.read(restaurantNotifierProvider.notifier);
      await notifier.addRestaurant(
        name: '새 맛집',
        category: 'korean',
        subCategory: '한식',
        roadAddress: '서울시 강남구',
        jibunAddress: '서울시 강남구',
        latitude: 37.5,
        longitude: 127.0,
        source: DataSource.USER_INPUT,
      );

      // Assert
      verify(mockRepository.addRestaurant(any)).called(1);
    });

    test('restaurantNotifier updates existing restaurant', () async {
      // Arrange
      final restaurant = Restaurant(
        id: '1',
        name: '수정할 맛집',
        category: 'korean',
        subCategory: '한식',
        roadAddress: '서울시',
        jibunAddress: '서울시',
        latitude: 37.5,
        longitude: 127.0,
        source: DataSource.USER_INPUT,
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      );

      when(mockRepository.updateRestaurant(any)).thenAnswer((_) async {});

      // Act
      final notifier = container.read(restaurantNotifierProvider.notifier);
      await notifier.updateRestaurant(restaurant);

      // Assert
      verify(mockRepository.updateRestaurant(any)).called(1);
    });

    test('restaurantNotifier deletes restaurant', () async {
      // Arrange
      when(mockRepository.deleteRestaurant('1')).thenAnswer((_) async {});

      // Act
      final notifier = container.read(restaurantNotifierProvider.notifier);
      await notifier.deleteRestaurant('1');

      // Assert
      verify(mockRepository.deleteRestaurant('1')).called(1);
    });

    test(
      'filteredRestaurantsProvider filters by search and category',
      () async {
        // Arrange
        final restaurants = [
          Restaurant(
            id: '1',
            name: '김치찌개',
            category: '한식',
            subCategory: '한식',
            roadAddress: '서울시',
            jibunAddress: '서울시',
            latitude: 37.5,
            longitude: 127.0,
            source: DataSource.USER_INPUT,
            createdAt: DateTime.now(),
            updatedAt: DateTime.now(),
          ),
          Restaurant(
            id: '2',
            name: '스시',
            category: '일식',
            subCategory: '일식',
            roadAddress: '서울시',
            jibunAddress: '서울시',
            latitude: 37.5,
            longitude: 127.0,
            source: DataSource.USER_INPUT,
            createdAt: DateTime.now(),
            updatedAt: DateTime.now(),
          ),
        ];

        when(
          mockRepository.watchRestaurants(),
        ).thenAnswer((_) => Stream.value(restaurants));

        // Act - 카테고리 필터 설정
        container.read(selectedCategoryProvider.notifier).state = '한식';
        container.read(searchQueryProvider.notifier).state = '김치';
        await container.read(restaurantListProvider.future);
        final filteredAsync = container.read(filteredRestaurantsProvider);
        final filtered = filteredAsync.requireValue;

        // Assert
        expect(filtered.length, 1);
        expect(filtered.first.name, '김치찌개');
      },
    );

    test(
      'restaurantsWithinDistanceProvider returns nearby restaurants',
      () async {
        // Arrange
        final nearbyRestaurants = [
          Restaurant(
            id: '1',
            name: '가까운 맛집',
            category: 'korean',
            subCategory: '한식',
            roadAddress: '서울시',
            jibunAddress: '서울시',
            latitude: 37.5665,
            longitude: 126.9780,
            source: DataSource.USER_INPUT,
            createdAt: DateTime.now(),
            updatedAt: DateTime.now(),
          ),
        ];

        when(
          mockRepository.getRestaurantsWithinDistance(
            userLatitude: 37.5665,
            userLongitude: 126.9780,
            maxDistanceInMeters: 1000,
          ),
        ).thenAnswer((_) async => nearbyRestaurants);

        // Act
        final result = await container.read(
          restaurantsWithinDistanceProvider((
            latitude: 37.5665,
            longitude: 126.9780,
            maxDistance: 1000,
          )).future,
        );

        // Assert
        expect(result.length, 1);
        expect(result.first.name, '가까운 맛집');
      },
    );

    test('categoriesProvider emits sorted unique categories', () async {
      // Arrange
      final restaurants = [
        Restaurant(
          id: '1',
          name: '카테고리1',
          category: '양식',
          subCategory: '파스타',
          roadAddress: '서울시',
          jibunAddress: '서울시',
          latitude: 0,
          longitude: 0,
          source: DataSource.USER_INPUT,
          createdAt: DateTime.now(),
          updatedAt: DateTime.now(),
        ),
        Restaurant(
          id: '2',
          name: '카테고리2',
          category: '한식',
          subCategory: '찌개',
          roadAddress: '서울시',
          jibunAddress: '서울시',
          latitude: 0,
          longitude: 0,
          source: DataSource.USER_INPUT,
          createdAt: DateTime.now(),
          updatedAt: DateTime.now(),
        ),
        Restaurant(
          id: '3',
          name: '카테고리3',
          category: '한식',
          subCategory: '비빔밥',
          roadAddress: '서울시',
          jibunAddress: '서울시',
          latitude: 0,
          longitude: 0,
          source: DataSource.USER_INPUT,
          createdAt: DateTime.now(),
          updatedAt: DateTime.now(),
        ),
      ];

      when(
        mockRepository.watchRestaurants(),
      ).thenAnswer((_) => Stream.value(restaurants));

      final categoriesContainer = ProviderContainer(
        overrides: [
          restaurantListProvider.overrideWith(
            (ref) => Stream.value(restaurants),
          ),
        ],
      );

      final categories = await categoriesContainer
          .read(categoriesProvider.stream)
          .first;

      expect(categories, ['양식', '한식']);
      categoriesContainer.dispose();
    });
  });
}
