// ignore_for_file: depend_on_referenced_packages

import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:lunchpick/data/datasources/remote/naver_search_service.dart';
import 'package:lunchpick/data/datasources/remote/naver_map_parser.dart';
import 'package:lunchpick/data/api/naver_api_client.dart';
import 'package:lunchpick/data/api/naver/naver_local_search_api.dart';
import 'package:lunchpick/domain/entities/restaurant.dart';
import 'package:lunchpick/core/errors/network_exceptions.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';

class _FakePathProvider extends PathProviderPlatform {
  @override
  Future<String?> getTemporaryPath() async {
    final tempDir = await Directory.systemTemp.createTemp('naver_search_test_');
    return tempDir.path;
  }
}

// Mock 클래스들
class MockNaverApiClient extends NaverApiClient {
  final Map<String, dynamic> _mockResponses = {};
  final Map<String, Exception> _mockExceptions = {};

  // Mock 설정 메서드들
  void setSearchResponse({
    required String query,
    double? latitude,
    double? longitude,
    required List<NaverLocalSearchResult> results,
  }) {
    final key = _generateKey(query, latitude, longitude);
    _mockResponses[key] = results;
  }

  void setSearchException({
    required String query,
    double? latitude,
    double? longitude,
    required Exception exception,
  }) {
    final key = _generateKey(query, latitude, longitude);
    _mockExceptions[key] = exception;
  }

  String _generateKey(String query, double? latitude, double? longitude) {
    return '$query-$latitude-$longitude';
  }

  // 호출 추적
  final List<Map<String, dynamic>> callHistory = [];
  bool disposeCalled = false;

  @override
  Future<List<NaverLocalSearchResult>> searchLocal({
    required String query,
    double? latitude,
    double? longitude,
    int display = 20,
    int start = 1,
    String sort = 'random',
  }) async {
    // 호출 기록
    callHistory.add({
      'query': query,
      'latitude': latitude,
      'longitude': longitude,
      'display': display,
      'sort': sort,
    });

    final key = _generateKey(query, latitude, longitude);

    if (_mockExceptions.containsKey(key)) {
      throw _mockExceptions[key]!;
    }

    if (_mockResponses.containsKey(key)) {
      return _mockResponses[key] as List<NaverLocalSearchResult>;
    }

    return [];
  }

  @override
  void dispose() {
    disposeCalled = true;
  }

  @override
  Future<String> resolveShortUrl(String shortUrl) async => shortUrl;
}

class MockNaverMapParser extends NaverMapParser {
  final Map<String, Restaurant> _mockResponses = {};
  final Map<String, Exception> _mockExceptions = {};

  void setParseResponse(String url, Restaurant restaurant) {
    _mockResponses[url] = restaurant;
  }

  void setParseException(String url, Exception exception) {
    _mockExceptions[url] = exception;
  }

  // 호출 추적
  bool disposeCalled = false;
  final List<String> parseCallHistory = [];

  @override
  Future<Restaurant> parseRestaurantFromUrl(
    String url, {
    double? userLatitude,
    double? userLongitude,
  }) async {
    parseCallHistory.add(url);

    if (_mockExceptions.containsKey(url)) {
      throw _mockExceptions[url]!;
    }

    if (_mockResponses.containsKey(url)) {
      return _mockResponses[url]!;
    }

    throw NaverMapParseException('No mock response set for URL: $url');
  }

  @override
  void dispose() {
    disposeCalled = true;
  }
}

void main() {
  late NaverSearchService service;
  late MockNaverApiClient mockApiClient;
  late MockNaverMapParser mockMapParser;

  setUpAll(() {
    TestWidgetsFlutterBinding.ensureInitialized();
    PathProviderPlatform.instance = _FakePathProvider();
  });

  setUp(() {
    mockApiClient = MockNaverApiClient();
    mockMapParser = MockNaverMapParser();
    service = NaverSearchService(
      apiClient: mockApiClient,
      mapParser: mockMapParser,
    );
  });

  group('NaverSearchService', () {
    group('getRestaurantFromUrl', () {
      const testUrl = 'https://map.naver.com/p/restaurant/1234567890';
      final testRestaurant = Restaurant(
        id: '1',
        name: 'Test Restaurant',
        category: '한식',
        subCategory: '백반',
        roadAddress: '서울시 강남구 테헤란로 123',
        jibunAddress: '서울시 강남구 역삼동 123-45',
        latitude: 37.123456,
        longitude: 127.123456,
        source: DataSource.NAVER,
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      );

      test('URL에서 식당 정보를 성공적으로 가져온다', () async {
        // Arrange
        mockMapParser.setParseResponse(testUrl, testRestaurant);

        // Act
        final result = await service.getRestaurantFromUrl(testUrl);

        // Assert
        expect(result, equals(testRestaurant));
        expect(mockMapParser.parseCallHistory, contains(testUrl));
      });

      test('NaverMapParseException을 그대로 전파한다', () async {
        // Arrange
        final exception = NaverMapParseException('파싱 실패');
        mockMapParser.setParseException(testUrl, exception);

        // Act & Assert
        expect(
          () async => await service.getRestaurantFromUrl(testUrl),
          throwsA(isA<NaverMapParseException>()),
        );
      });

      test('NetworkException을 그대로 전파한다', () async {
        // Arrange
        const exception = ServerException(message: '네트워크 오류', statusCode: 500);
        mockMapParser.setParseException(testUrl, exception);

        // Act & Assert
        expect(
          () async => await service.getRestaurantFromUrl(testUrl),
          throwsA(isA<NetworkException>()),
        );
      });

      test('일반 예외를 NetworkException으로 래핑한다', () async {
        // Arrange
        final exception = Exception('알 수 없는 오류');
        mockMapParser.setParseException(testUrl, exception);

        // Act & Assert
        expect(
          () async => await service.getRestaurantFromUrl(testUrl),
          throwsA(
            allOf(
              isA<ParseException>(),
              predicate<ParseException>(
                (e) =>
                    e.message.contains('식당 정보를 가져올 수 없습니다') &&
                    e.originalError.toString() == exception.toString(),
              ),
            ),
          ),
        );
      });
    });

    group('searchNearbyRestaurants', () {
      const testQuery = '김치찌개';
      const testLatitude = 37.123456;
      const testLongitude = 127.123456;

      final testSearchResults = [
        NaverLocalSearchResult.fromJson({
          'title': '김치찌개 맛집',
          'category': '한식>찌개',
          'description': '맛있는 김치찌개',
          'telephone': '02-1234-5678',
          'address': '서울시 강남구 역삼동',
          'roadAddress': '서울시 강남구 테헤란로 123',
          'mapx': 127123456,
          'mapy': 37123456,
          'link': 'https://map.naver.com/p/restaurant/1234567890',
        }),
      ];

      test('검색 결과를 Restaurant 리스트로 변환한다', () async {
        // Arrange
        mockApiClient.setSearchResponse(
          query: testQuery,
          latitude: testLatitude,
          longitude: testLongitude,
          results: testSearchResults,
        );

        // Act
        final results = await service.searchNearbyRestaurants(
          query: testQuery,
          latitude: testLatitude,
          longitude: testLongitude,
        );

        // Assert
        expect(results.length, equals(1));
        expect(results.first.name, equals('김치찌개 맛집'));
        expect(results.first.category, equals('한식'));
        expect(results.first.subCategory, equals('찌개'));

        // API 호출 확인
        expect(mockApiClient.callHistory.length, equals(1));
        expect(mockApiClient.callHistory.first['query'], equals(testQuery));
        expect(mockApiClient.callHistory.first['sort'], equals('random'));
      });

      test('빈 검색 결과를 처리한다', () async {
        // Arrange
        mockApiClient.setSearchResponse(
          query: testQuery,
          latitude: null,
          longitude: null,
          results: [],
        );

        // Act
        final results = await service.searchNearbyRestaurants(query: testQuery);

        // Assert
        expect(results, isEmpty);
      });

      test('NetworkException을 그대로 전파한다', () async {
        // Arrange
        const exception = ServerException(message: '네트워크 오류', statusCode: 500);
        mockApiClient.setSearchException(
          query: testQuery,
          latitude: null,
          longitude: null,
          exception: exception,
        );

        // Act & Assert
        expect(
          () async => await service.searchNearbyRestaurants(query: testQuery),
          throwsA(isA<NetworkException>()),
        );
      });

      test('일반 예외를 NetworkException으로 래핑한다', () async {
        // Arrange
        final exception = Exception('알 수 없는 오류');
        mockApiClient.setSearchException(
          query: testQuery,
          latitude: null,
          longitude: null,
          exception: exception,
        );

        // Act & Assert
        expect(
          () async => await service.searchNearbyRestaurants(query: testQuery),
          throwsA(
            allOf(
              isA<ParseException>(),
              predicate<ParseException>(
                (e) =>
                    e.message.contains('식당 검색에 실패했습니다') &&
                    e.originalError.toString() == exception.toString(),
              ),
            ),
          ),
        );
      });
    });

    group('searchRestaurantDetails', () {
      const testName = '김치찌개 맛집';
      const testAddress = '서울시 강남구 역삼동';
      const testLatitude = 37.123456;
      const testLongitude = 127.123456;

      final testSearchResults = [
        NaverLocalSearchResult.fromJson({
          'title': testName,
          'category': '한식>찌개',
          'description': '맛있는 김치찌개',
          'telephone': '02-1234-5678',
          'address': testAddress,
          'roadAddress': '서울시 강남구 테헤란로 123',
          'mapx': 127123456,
          'mapy': 37123456,
          'link': 'https://map.naver.com/p/restaurant/1234567890',
        }),
      ];

      test('정확히 일치하는 식당을 찾는다', () async {
        // Arrange
        mockApiClient.setSearchResponse(
          query: '서울시 강남구 $testName',
          latitude: testLatitude,
          longitude: testLongitude,
          results: testSearchResults,
        );

        // Act
        final result = await service.searchRestaurantDetails(
          name: testName,
          address: testAddress,
          latitude: testLatitude,
          longitude: testLongitude,
        );

        // Assert
        expect(result, isNotNull);
        expect(result!.name, equals(testName));
      });

      test('검색 결과가 없으면 null을 반환한다', () async {
        // Arrange
        mockApiClient.setSearchResponse(
          query: testName,
          latitude: null,
          longitude: null,
          results: [],
        );

        // Act
        final result = await service.searchRestaurantDetails(name: testName);

        // Assert
        expect(result, isNull);
      });

      test('유사도가 낮은 결과는 null을 반환한다', () async {
        // Arrange
        final unmatchedResults = [
          NaverLocalSearchResult.fromJson({
            'title': '완전히 다른 식당',
            'category': '양식',
            'description': '파스타',
            'telephone': '02-9999-9999',
            'address': '서울시 종로구',
            'roadAddress': '서울시 종로구 세종대로 99',
            'mapx': 127999999,
            'mapy': 37999999,
            'link': 'https://map.naver.com/p/restaurant/9999999999',
          }),
        ];

        mockApiClient.setSearchResponse(
          query: testName,
          latitude: null,
          longitude: null,
          results: unmatchedResults,
        );

        // Act
        final result = await service.searchRestaurantDetails(name: testName);

        // Assert
        expect(result, isNull);
      });

      test('네이버 지도 URL이 있으면 상세 정보를 파싱한다', () async {
        // Arrange
        final detailedRestaurant = Restaurant(
          id: '2',
          name: testName,
          category: '한식',
          subCategory: '찌개',
          description: '업데이트된 설명',
          businessHours: '09:00 - 21:00',
          naverPlaceId: '1234567890',
          roadAddress: '서울시 강남구 테헤란로 123',
          jibunAddress: '서울시 강남구 역삼동 123-45',
          latitude: testLatitude,
          longitude: testLongitude,
          source: DataSource.NAVER,
          createdAt: DateTime.now(),
          updatedAt: DateTime.now(),
        );

        mockApiClient.setSearchResponse(
          query: testName,
          latitude: null,
          longitude: null,
          results: testSearchResults,
        );

        mockMapParser.setParseResponse(
          'https://map.naver.com/p/restaurant/1234567890',
          detailedRestaurant,
        );

        // Act
        final result = await service.searchRestaurantDetails(name: testName);

        // Assert
        expect(result, isNotNull);
        expect(result!.description, equals('업데이트된 설명'));
        expect(result.businessHours, equals('09:00 - 21:00'));
        expect(result.naverPlaceId, equals('1234567890'));
      });

      test('상세 파싱 실패해도 기본 정보를 반환한다', () async {
        // Arrange
        mockApiClient.setSearchResponse(
          query: testName,
          latitude: null,
          longitude: null,
          results: testSearchResults,
        );

        mockMapParser.setParseException(
          'https://map.naver.com/p/restaurant/1234567890',
          Exception('파싱 실패'),
        );

        // Act
        final result = await service.searchRestaurantDetails(name: testName);

        // Assert
        expect(result, isNotNull);
        expect(result!.name, equals(testName));
      });
    });

    group('_findBestMatch', () {
      test('정확히 일치하는 결과를 우선 반환한다', () {
        // Arrange
        const targetName = '김치찌개 맛집';
        final results = [
          NaverLocalSearchResult.fromJson({
            'title': '다른 식당',
            'category': '한식',
            'description': '',
            'telephone': '',
            'address': '',
            'roadAddress': '',
            'mapx': 0,
            'mapy': 0,
            'link': '',
          }),
          NaverLocalSearchResult.fromJson({
            'title': targetName,
            'category': '한식',
            'description': '',
            'telephone': '',
            'address': '',
            'roadAddress': '',
            'mapx': 0,
            'mapy': 0,
            'link': '',
          }),
        ];

        // Act
        final result = service.findBestMatchForTesting(targetName, results);

        // Assert
        expect(result?.title, equals(targetName));
      });

      test('빈 리스트에서는 null을 반환한다', () {
        // Act
        final result = service.findBestMatchForTesting('test', []);

        // Assert
        expect(result, isNull);
      });
    });

    group('_calculateSimilarity', () {
      test('동일한 문자열은 1.0을 반환한다', () {
        // Act
        final similarity = service.calculateSimilarityForTesting('테스트', '테스트');

        // Assert
        expect(similarity, greaterThan(0.7));
      });

      test('포함 관계가 있으면 0.8을 반환한다', () {
        // Act
        final similarity = service.calculateSimilarityForTesting(
          '김치찌개',
          '김치찌개 맛집',
        );

        // Assert
        expect(similarity, equals(0.8));
      });

      test('완전히 다른 문자열은 낮은 유사도를 반환한다', () {
        // Act
        final similarity = service.calculateSimilarityForTesting('한식', '양식');

        // Assert
        expect(similarity, lessThan(0.5));
      });

      test('빈 문자열은 0.0을 반환한다', () {
        // Act
        final similarity = service.calculateSimilarityForTesting('', 'test');

        // Assert
        expect(similarity, equals(0.0));
      });

      test('특수문자를 제거하고 비교한다', () {
        // Act
        final similarity = service.calculateSimilarityForTesting(
          '김치찌개@#\$',
          '김치찌개!!!',
        );

        // Assert
        // 특수문자가 제거된 후 동일한 문자열이므로 0.8 이상이어야 함
        expect(similarity, greaterThanOrEqualTo(0.8));
      });
    });

    group('dispose', () {
      test('dispose가 정상적으로 호출된다', () {
        // Act
        service.dispose();

        // Assert
        expect(mockApiClient.disposeCalled, isTrue);
        expect(mockMapParser.disposeCalled, isTrue);
      });
    });
  });
}
