import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:lanebow/services/score_service.dart';
import 'package:lanebow/services/api_service.dart';
import 'package:lanebow/config/api_config.dart';
import 'package:lanebow/utils/test_utils.dart';
import '../utils/event_bus_test_utils.dart';

// 모킹 클래스 생성
@GenerateMocks([ApiService])
import 'score_service_test.mocks.dart';

void main() {
  late ScoreService scoreService;
  late MockApiService mockApiService;
  final testToken = 'test_token';
  final testClubId = 'test_club_id';
  final testMemberId = 'test_member_id';
  final testEventId = 'test_event_id';
  final testScoreId = 'test_score_id';

  // 테스트 점수 데이터
  final testScore = {
    'id': 'test_score_id',
    'memberId': 'test_member_id',
    'eventId': 'test_event_id',
    'clubId': 'test_club_id',
    'frames': [10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
    'totalScore': 55,
    'date': DateTime.now().toIso8601String(),
    'notes': '테스트 점수',
  };

  // 테스트 통계 데이터
  final testStatistics = {
    'average': 150.5,
    'highScore': 200,
    'lowScore': 100,
    'gamesPlayed': 10,
    'additionalStats': {
      'strikes': 5,
      'spares': 3,
    },
  };

  setUp(() async {
    // 테스트 모드 강제 설정
    TestUtils.setTestMode(true);
    
    // 테스트별 고유 ID 생성 및 설정
    final testId = 'score_service_test_${DateTime.now().millisecondsSinceEpoch}';
    await EventBusTestUtils.setUp(testId);
    
    // SharedPreferences 모킹
    SharedPreferences.setMockInitialValues({
      ApiConfig.clubIdKey: testClubId,
    });

    // API 서비스 모킹
    mockApiService = MockApiService();
    
    // ScoreService 초기화 (forTest 생성자 사용)
    scoreService = ScoreService.forTest(mockApiService, clubId: testClubId);
    
    // 토큰 설정
    scoreService.initialize(testToken);
  });
  
  tearDown(() async {
    // 테스트에서 생성된 ScoreService 정리
    try { scoreService.dispose(); } catch (_) {}
    
    // EventBus 표준 정리
    await EventBusTestUtils.tearDown();
    
    // 테스트 모드 해제
    TestUtils.setTestMode(false);
  });

  group('ScoreService 테스트', () {
    test('initialize 메서드가 토큰을 설정해야 함', () {
      // given
      // setUp에서 이미 initialize 호출됨
      
      // then
      verify(mockApiService.setToken(testToken)).called(1);
    });

    test('setMemberId 메서드가 회원 ID를 설정해야 함', () {
      // when
      scoreService.setMemberId(testMemberId);

      // then
      expect(scoreService.testMemberId, testMemberId);
    });

    test('setEventId 메서드가 이벤트 ID를 설정해야 함', () {
      // when
      scoreService.setEventId(testEventId);

      // then
      expect(scoreService.testEventId, testEventId);
    });

    test('fetchClubScores 메서드가 클럽의 점수 목록을 가져와야 함', () async {
      // given
      when(mockApiService.post(
        '${ApiConfig.clubs}/scores',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => [testScore]);

      // when
      await scoreService.fetchClubScores();

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/scores',
        data: {'clubId': testClubId},
      )).called(1);
      
      expect(scoreService.scores.length, 1);
      expect(scoreService.scores[0].id, testScoreId);
      expect(scoreService.scores[0].totalScore, 55);
      expect(scoreService.lastLoadedAt, isNotNull);
      expect(scoreService.lastError, isNull);
    });

    test('fetchClubScores 메서드가 객체 형태의 응답도 처리해야 함', () async {
      // given
      when(mockApiService.post(
        '${ApiConfig.clubs}/scores',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => {'scores': [testScore]});

      // when
      await scoreService.fetchClubScores();

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/scores',
        data: {'clubId': testClubId},
      )).called(1);
      
      expect(scoreService.scores.length, 1);
      expect(scoreService.scores[0].id, testScoreId);
    });

    test('fetchMemberScores 메서드가 회원의 점수 목록을 가져와야 함', () async {
      // given
      when(mockApiService.post(
        '${ApiConfig.clubs}/members/scores',
        data: {'memberId': testMemberId},
      )).thenAnswer((_) async => {'scores': [testScore]});

      // when
      final result = await scoreService.fetchMemberScores(testMemberId);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/members/scores',
        data: {'memberId': testMemberId},
      )).called(1);
      
      expect(result.length, 1);
      expect(result[0].id, testScoreId);
      expect(result[0].memberId, testMemberId);
    });

    test('fetchEventScores 메서드가 이벤트의 점수 목록을 가져와야 함', () async {
      // given
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/scores',
        data: {'eventId': testEventId},
      )).thenAnswer((_) async => {'scores': [testScore]});

      // when
      final result = await scoreService.fetchEventScores(testEventId);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/events/scores',
        data: {'eventId': testEventId},
      )).called(1);
      
      expect(result.length, 1);
      expect(result[0].id, testScoreId);
      expect(result[0].eventId, testEventId);
    });

    test('addScore 메서드가 새 점수를 추가해야 함', () async {
      // given
      final newScoreData = {
        'memberId': testMemberId,
        'eventId': testEventId,
        'clubId': testClubId,
        'frames': [10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
        'totalScore': 55,
      };
      
      when(mockApiService.post(
        ApiConfig.scores,
        data: newScoreData,
      )).thenAnswer((_) async => {'score': testScore});

      // when
      final result = await scoreService.addScore(newScoreData);

      // then
      verify(mockApiService.post(
        ApiConfig.scores,
        data: newScoreData,
      )).called(1);
      
      expect(result.id, testScoreId);
      expect(scoreService.scores.length, 1);
      expect(scoreService.scores[0].id, testScoreId);
    });

    test('updateScore 메서드가 점수 정보를 업데이트해야 함', () async {
      // given
      // 먼저 점수 목록 가져오기
      when(mockApiService.post(
        '${ApiConfig.clubs}/scores',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => [testScore]);
      await scoreService.fetchClubScores();
      
      final updates = {'totalScore': 60};
      final updatedScore = Map<String, dynamic>.from(testScore);
      updatedScore['totalScore'] = 60;
      
      when(mockApiService.put(
        '${ApiConfig.scores}/$testScoreId',
        data: updates,
      )).thenAnswer((_) async => {'score': updatedScore});

      // when
      final result = await scoreService.updateScore(testScoreId, updates);

      // then
      verify(mockApiService.put(
        '${ApiConfig.scores}/$testScoreId',
        data: updates,
      )).called(1);
      
      expect(result.totalScore, 60);
      expect(scoreService.scores[0].totalScore, 60);
    });

    test('deleteScore 메서드가 점수를 삭제해야 함', () async {
      // given
      // 먼저 점수 목록 가져오기
      when(mockApiService.post(
        '${ApiConfig.clubs}/scores',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => [testScore]);
      await scoreService.fetchClubScores();
      
      when(mockApiService.delete(
        '${ApiConfig.scores}/$testScoreId',
      )).thenAnswer((_) async => {'success': true});

      // when
      final result = await scoreService.deleteScore(testScoreId);

      // then
      verify(mockApiService.delete(
        '${ApiConfig.scores}/$testScoreId',
      )).called(1);
      
      expect(result, true);
      expect(scoreService.scores.length, 0);
    });

    test('fetchClubScores: clubId가 null이면 예외를 던지고 isLoading=false 유지', () async {
      // given: prefs에서 clubId 제거
      SharedPreferences.setMockInitialValues({});
      // 새 서비스 인스턴스(클럽 미설정)
      final svc = ScoreService.forTest(mockApiService);
      svc.initialize(testToken);

      // when/then
      expect(() async => await svc.fetchClubScores(), throwsA(isA<Exception>()));
      // 약간 대기 후 상태 확인(비동기 반영 안정화)
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));
      expect(svc.isLoading, isFalse);
      expect(svc.lastError, isNotNull);
    });

    test('fetchClubScores: 빈 목록 응답이면 기존 scores를 비워야 함', () async {
      // given: 먼저 1개 로드
      when(mockApiService.post(
        '${ApiConfig.clubs}/scores',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => [testScore]);
      await scoreService.fetchClubScores();
      expect(scoreService.scores.length, 1);

      // 빈 목록 응답으로 재설정
      when(mockApiService.post(
        '${ApiConfig.clubs}/scores',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => <Map<String, dynamic>>[]);

      // when
      await scoreService.fetchClubScores();

      // then
      expect(scoreService.scores, isEmpty);
      expect(scoreService.isLoading, isFalse);
      expect(scoreService.lastLoadedAt, isNotNull);
      expect(scoreService.lastError, isNull);
    });

    test('addScore 실패 시 예외 전파, isLoading=false, scores 불변', () async {
      // given
      final payload = {
        'memberId': testMemberId,
        'eventId': testEventId,
        'clubId': testClubId,
        'frames': [1],
        'totalScore': 1,
      };
      when(mockApiService.post(ApiConfig.scores, data: payload))
          .thenThrow(Exception('network'));

      final beforeLen = scoreService.scores.length;

      // when/then
      expect(() async => await scoreService.addScore(payload), throwsA(isA<Exception>()));
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));
      expect(scoreService.isLoading, isFalse);
      expect(scoreService.scores.length, beforeLen);
      expect(scoreService.lastError, isNotNull);
    });

    test('updateScore 실패 시 예외 전파, isLoading=false, scores 불변', () async {
      // given: 기존 점수 1개 로드
      when(mockApiService.post(
        '${ApiConfig.clubs}/scores',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => [testScore]);
      await scoreService.fetchClubScores();
      final before = List.from(scoreService.scores);

      when(mockApiService.put('${ApiConfig.scores}/$testScoreId', data: anyNamed('data')))
          .thenThrow(Exception('update failed'));

      // when/then
      expect(() async => await scoreService.updateScore(testScoreId, {'totalScore': 77}), throwsA(isA<Exception>()));
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));
      expect(scoreService.isLoading, isFalse);
      expect(scoreService.scores.map((e) => e.totalScore), before.map((e) => e.totalScore));
      expect(scoreService.lastError, isNotNull);
    });

    test('deleteScore 실패 시 예외 전파, isLoading=false, scores 불변', () async {
      // given: 기존 점수 1개 로드
      when(mockApiService.post(
        '${ApiConfig.clubs}/scores',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => [testScore]);
      await scoreService.fetchClubScores();
      final beforeLen = scoreService.scores.length;

      when(mockApiService.delete('${ApiConfig.scores}/$testScoreId'))
          .thenThrow(Exception('delete failed'));

      // when/then
      expect(() async => await scoreService.deleteScore(testScoreId), throwsA(isA<Exception>()));
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));
      expect(scoreService.isLoading, isFalse);
      expect(scoreService.scores.length, beforeLen);
    });

    test('fetchMemberScores: token 또는 clubId 없으면 예외', () async {
      // given: 새 서비스(초기화/클럽 미설정)
      final svc = ScoreService.forTest(mockApiService);
      // when/then
      expect(() async => await svc.fetchMemberScores(testMemberId), throwsA(isA<Exception>()));
    });

    test('fetchEventScores: token 또는 clubId 없으면 예외', () async {
      // given: 새 서비스(초기화/클럽 미설정)
      final svc = ScoreService.forTest(mockApiService);
      // when/then
      expect(() async => await svc.fetchEventScores(testEventId), throwsA(isA<Exception>()));
    });

    test('fetchMemberStatistics 메서드가 회원 통계를 가져와야 함', () async {
      // given
      when(mockApiService.post(
        '${ApiConfig.clubs}/members/stats',
        data: {'memberId': testMemberId},
      )).thenAnswer((_) async => {'statistics': testStatistics});

      // when
      final result = await scoreService.fetchMemberStatistics(testMemberId);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/members/stats',
        data: {'memberId': testMemberId},
      )).called(1);
      
      expect(result.average, 150.5);
      expect(result.highScore, 200);
      expect(result.lowScore, 100);
      expect(result.gamesPlayed, 10);
      expect(result.additionalStats?['strikes'], 5);
      expect(result.additionalStats?['spares'], 3);
    });

    test('fetchClubStatistics 메서드가 클럽 통계를 가져와야 함', () async {
      // given
      final clubStats = {
        'overall': testStatistics,
        'monthly': testStatistics,
      };
      
      when(mockApiService.post(
        '${ApiConfig.clubs}/scores/stats',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => {'statistics': clubStats});

      // when
      final result = await scoreService.fetchClubStatistics();
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/scores/stats',
        data: {'clubId': testClubId},
      )).called(1);
      
      expect(result.length, 2);
      expect(result['overall']?.average, 150.5);
      expect(result['monthly']?.highScore, 200);
    });
  });
}
