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/event_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 'event_service_test.mocks.dart';

void main() {
  late EventService eventService;
  late MockApiService mockApiService;
  final testToken = 'test_token';
  final testClubId = 'test_club_id';

  // 테스트 이벤트 데이터
  final testEvent = {
    'id': 'test_event_id',
    'clubId': 'test_club_id',
    'title': '테스트 이벤트',
    'description': '테스트 설명',
    'type': 'regular',
    'status': 'upcoming',
    'startDate': DateTime.now().toIso8601String(),
    'endDate': DateTime.now().add(const Duration(hours: 2)).toIso8601String(),
    'location': '테스트 장소',
    'maxParticipants': 20,
    'gameCount': 3,
    'entryFee': 10000,
    'registrationDeadline': DateTime.now().add(const Duration(days: 1)).toIso8601String(),
    'isActive': true,
  };

  // 테스트 참가자 데이터
  final testParticipant = {
    'id': 'test_participant_id',
    'eventId': 'test_event_id',
    'memberId': 'test_member_id',
    'name': '테스트 참가자',
    'status': 'confirmed',
    'paymentStatus': 'paid',
    'registrationDate': DateTime.now().toIso8601String(),
  };

  // 테스트 점수 데이터
  final testScore = {
    'id': 'test_score_id',
    'eventId': 'test_event_id',
    'participantId': 'test_participant_id',
    'participantName': '테스트 참가자',
    'games': [180, 200, 220],
    'handicap': 10,
    'total': 610,
    'average': 203.33,
  };

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

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

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

    test('fetchClubEvents 메서드가 이벤트 목록을 가져와야 함', () async {
      // given
      final testEvents = [testEvent];
      when(mockApiService.post(
        '${ApiConfig.clubs}/events',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => testEvents);

      // when
      await eventService.fetchClubEvents();

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/events',
        data: {'clubId': testClubId},
      )).called(1);
      
      expect(eventService.events.length, 1);
      expect(eventService.events[0].id, 'test_event_id');
      expect(eventService.events[0].title, '테스트 이벤트');
    });

    test('fetchEventById 메서드가 특정 이벤트를 가져와야 함', () async {
      // given
      final eventId = 'test_event_id';
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/detail',
        data: {'eventId': eventId},
      )).thenAnswer((_) async => {'event': testEvent});

      // when
      final result = await eventService.fetchEventById(eventId);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/events/detail',
        data: {'eventId': eventId},
      )).called(1);
      
      expect(result.id, 'test_event_id');
      expect(result.title, '테스트 이벤트');
    });

    test('createEvent 메서드가 새 이벤트를 생성해야 함', () async {
      // given
      final newEventData = {
        'clubId': testClubId,
        'title': '새 이벤트',
        'startDate': DateTime.now().toIso8601String(),
      };
      
      when(mockApiService.post(
        '${ApiConfig.clubs}/events',
        data: newEventData,
      )).thenAnswer((_) async => {'event': testEvent});

      // when
      final result = await eventService.createEvent(newEventData);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/events',
        data: newEventData,
      )).called(1);
      
      expect(result.id, 'test_event_id');
      expect(eventService.events.length, 1);
    });

    test('updateEvent 메서드가 이벤트를 업데이트해야 함', () async {
      // given
      final eventId = 'test_event_id';
      final updates = {'title': '업데이트된 이벤트'};
      final updatedEvent = Map<String, dynamic>.from(testEvent);
      updatedEvent['title'] = '업데이트된 이벤트';
      
      // 이벤트 목록에 테스트 이벤트 추가
      when(mockApiService.post(
        '${ApiConfig.clubs}/events',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => [testEvent]);
      await eventService.fetchClubEvents();
      
      when(mockApiService.put(
        '${ApiConfig.clubs}/events/$eventId',
        data: updates,
      )).thenAnswer((_) async => {'event': updatedEvent});

      // when
      final result = await eventService.updateEvent(eventId, updates);

      // then
      verify(mockApiService.put(
        '${ApiConfig.clubs}/events/$eventId',
        data: updates,
      )).called(1);
      
      expect(result.title, '업데이트된 이벤트');
      expect(eventService.events[0].title, '업데이트된 이벤트');
    });

    test('deleteEvent 메서드가 이벤트를 삭제해야 함', () async {
      // given
      final eventId = 'test_event_id';
      
      // 이벤트 목록에 테스트 이벤트 추가
      when(mockApiService.post(
        '${ApiConfig.clubs}/events',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => [testEvent]);
      await eventService.fetchClubEvents();
      
      when(mockApiService.delete(
        '${ApiConfig.clubs}/events/$eventId',
      )).thenAnswer((_) async => {});

      // when
      final result = await eventService.deleteEvent(eventId);

      // then
      verify(mockApiService.delete(
        '${ApiConfig.clubs}/events/$eventId',
      )).called(1);
      
      expect(result, true);
      expect(eventService.events.length, 0);
    });

    test('fetchEventParticipants 메서드가 참가자 목록을 가져와야 함', () async {
      // given
      final eventId = 'test_event_id';
      final testParticipants = [testParticipant];
      
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/participants/list',
        data: anyNamed('data'),
      )).thenAnswer((_) async => {'participants': testParticipants});

      // when
      final result = await eventService.fetchEventParticipants(eventId);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/participants/list',
        data: anyNamed('data'),
      )).called(1);
      
      expect(result.length, 1);
      expect(result[0].id, 'test_participant_id');
      expect(result[0].name, '테스트 참가자');
    });

    test('addParticipant 메서드가 참가자를 추가해야 함', () async {
      // given
      final eventId = 'test_event_id';
      final participantData = {
        'memberId': 'test_member_id',
        'status': 'confirmed',
      };
      
      // 서비스는 clubId를 payload에 병합하므로 포함하여 검증
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/participants',
        data: anyNamed('data'),
      )).thenAnswer((_) async => {'participant': testParticipant});

      // when
      final result = await eventService.addParticipant(eventId, participantData);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/participants',
        data: anyNamed('data'),
      )).called(1);
      
      expect(result.id, 'test_participant_id');
      expect(eventService.participants.length, 1);
    });

    test('updateParticipant 메서드가 참가자 정보를 업데이트해야 함', () async {
      // given
      final eventId = 'test_event_id';
      final participantId = 'test_participant_id';
      final updates = {'status': 'canceled'};
      final updatedParticipant = Map<String, dynamic>.from(testParticipant);
      updatedParticipant['status'] = 'canceled';
      
      // 참가자 목록에 테스트 참가자 추가
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/participants/list',
        data: anyNamed('data'),
      )).thenAnswer((_) async => {'participants': [testParticipant]});
      await eventService.fetchEventParticipants(eventId);
      
      when(mockApiService.put(
        '${ApiConfig.clubs}/events/$eventId/participants/$participantId',
        data: anyNamed('data'),
      )).thenAnswer((_) async => {'participant': updatedParticipant});

      // when
      final result = await eventService.updateParticipant(eventId, participantId, updates);

      // then
      verify(mockApiService.put(
        '${ApiConfig.clubs}/events/$eventId/participants/$participantId',
        data: anyNamed('data'),
      )).called(1);
      
      expect(result.status, 'canceled');
      expect(eventService.participants[0].status, 'canceled');
    });

    test('removeParticipant 메서드가 참가자를 삭제해야 함', () async {
      // given
      final eventId = 'test_event_id';
      final participantId = 'test_participant_id';
      
      // 참가자 목록에 테스트 참가자 추가
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/participants/list',
        data: anyNamed('data'),
      )).thenAnswer((_) async => {'participants': [testParticipant]});
      await eventService.fetchEventParticipants(eventId);
      
      when(mockApiService.delete(
        '${ApiConfig.clubs}/events/$eventId/participants/$participantId',
      )).thenAnswer((_) async => {});

      // when
      final result = await eventService.removeParticipant(eventId, participantId);

      // then
      verify(mockApiService.delete(
        '${ApiConfig.clubs}/events/$eventId/participants/$participantId',
      )).called(1);
      
      expect(result, true);
      expect(eventService.participants.length, 0);
    });

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

      // when
      final result = await eventService.fetchEventScores(eventId);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/events/scores',
        data: {'eventId': eventId},
      )).called(1);
      
      expect(result.length, 1);
      expect(result[0].id, 'test_score_id');
      expect(result[0].participantName, '테스트 참가자');
    });

    test('addScore 메서드가 점수를 추가해야 함', () async {
      // given
      final eventId = 'test_event_id';
      final scoreData = {
        'participantId': 'test_participant_id',
        'games': [180, 200, 220],
        'handicap': 10,
      };
      
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/scores',
        data: scoreData,
      )).thenAnswer((_) async => {'score': testScore});

      // when
      final result = await eventService.addScore(eventId, scoreData);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/scores',
        data: scoreData,
      )).called(1);
      
      expect(result.id, 'test_score_id');
      expect(eventService.scores.length, 1);
    });

    test('updateScore 메서드가 점수를 업데이트해야 함', () async {
      // given
      final eventId = 'test_event_id';
      final scoreId = 'test_score_id';
      final updates = {
        'games': [190, 210, 230],
        'handicap': 5,
      };

      // 초기 점수 목록 적재
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/scores',
        data: {'eventId': eventId},
      )).thenAnswer((_) async => {'scores': [testScore]});
      await eventService.fetchEventScores(eventId);

      final updated = Map<String, dynamic>.from(testScore);
      updated['games'] = [190, 210, 230];
      updated['handicap'] = 5;
      updated['total'] = 635; // 예시 합계
      updated['average'] = 211.67;

      when(mockApiService.put(
        '${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
        data: updates,
      )).thenAnswer((_) async => {'score': updated});

      // when
      final result = await eventService.updateScore(eventId, scoreId, updates);

      // then
      verify(mockApiService.put(
        '${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
        data: updates,
      )).called(1);

      expect(result.handicap, 5);
      expect(eventService.scores.first.handicap, 5);
    });

    test('deleteScore 메서드가 점수를 삭제해야 함', () async {
      // given
      final eventId = 'test_event_id';
      final scoreId = 'test_score_id';

      // 초기 점수 목록 적재
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/scores',
        data: {'eventId': eventId},
      )).thenAnswer((_) async => {'scores': [testScore]});
      await eventService.fetchEventScores(eventId);

      when(mockApiService.delete(
        '${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
      )).thenAnswer((_) async => {});

      // when
      final result = await eventService.deleteScore(eventId, scoreId);

      // then
      verify(mockApiService.delete(
        '${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
      )).called(1);
      expect(result, true);
      expect(eventService.scores.length, 0);
    });

    // 실패 케이스: 참가자 추가
    test('addParticipant 실패 시 예외 메시지와 로딩 상태 해제 및 목록 불변', () async {
      // given
      final eventId = 'test_event_id';
      final participantData = {
        'memberId': 'test_member_id',
        'status': 'confirmed',
      };

      when(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/participants',
        data: anyNamed('data'),
      )).thenThrow(Exception('API 오류'));

      // when
      expect(
        () => eventService.addParticipant(eventId, participantData),
        throwsA(isA<Exception>().having((e) => e.toString(), 'message', contains('참가자 추가에 실패했습니다'))),
      );

      // then
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));
      expect(eventService.isLoading, false);
      expect(eventService.participants.length, 0);
    });

    // 실패 케이스: 참가자 업데이트
    test('updateParticipant 실패 시 예외 메시지와 로딩 상태 해제 및 목록 불변', () async {
      // given
      final eventId = 'test_event_id';
      final participantId = 'test_participant_id';
      final updates = {'status': 'canceled'};

      // 기존 목록 적재
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/participants/list',
        data: {'clubId': testClubId, 'eventId': eventId},
      )).thenAnswer((_) async => {'participants': [testParticipant]});
      await eventService.fetchEventParticipants(eventId);

      when(mockApiService.put(
        '${ApiConfig.clubs}/events/$eventId/participants/$participantId',
        data: anyNamed('data'),
      )).thenThrow(Exception('API 오류'));

      // when
      expect(
        () => eventService.updateParticipant(eventId, participantId, updates),
        throwsA(isA<Exception>().having((e) => e.toString(), 'message', contains('참가자 정보 업데이트에 실패했습니다'))),
      );

      // then
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));
      expect(eventService.isLoading, false);
      // 실패 시 기존 상태 유지
      expect(eventService.participants.first.status, testParticipant['status']);
    });

    // 실패 케이스: 점수 추가
    test('addScore 실패 시 예외 메시지와 로딩 상태 해제 및 목록 불변', () async {
      // given
      final eventId = 'test_event_id';
      final scoreData = {
        'participantId': 'test_participant_id',
        'games': [180, 200, 220],
        'handicap': 10,
      };

      when(mockApiService.post(
        '${ApiConfig.clubs}/events/$eventId/scores',
        data: scoreData,
      )).thenThrow(Exception('API 오류'));

      // when
      expect(
        () => eventService.addScore(eventId, scoreData),
        throwsA(isA<Exception>().having((e) => e.toString(), 'message', contains('점수 추가에 실패했습니다'))),
      );

      // then
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));
      expect(eventService.isLoading, false);
      expect(eventService.scores.length, 0);
    });

    // 실패 케이스: 점수 업데이트
    test('updateScore 실패 시 예외 메시지와 로딩 상태 해제 및 목록 불변', () async {
      // given
      final eventId = 'test_event_id';
      final scoreId = 'test_score_id';
      final updates = {
        'games': [190, 210, 230],
        'handicap': 5,
      };

      // 기존 목록 적재
      when(mockApiService.post(
        '${ApiConfig.clubs}/events/scores',
        data: {'eventId': eventId},
      )).thenAnswer((_) async => {'scores': [testScore]});
      await eventService.fetchEventScores(eventId);

      when(mockApiService.put(
        '${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
        data: updates,
      )).thenThrow(Exception('API 오류'));

      // when
      expect(
        () => eventService.updateScore(eventId, scoreId, updates),
        throwsA(isA<Exception>().having((e) => e.toString(), 'message', contains('점수 업데이트에 실패했습니다'))),
      );

      // then
      await EventBusTestUtils.waitForIdle();
      // isLoading=false 수렴까지 폴링 대기 (최대 1.5s)
      const total = Duration(milliseconds: 1500);
      const step = Duration(milliseconds: 25);
      var waited = Duration.zero;
      while (eventService.isLoading && waited < total) {
        await Future<void>.delayed(step);
        waited += step;
      }
      expect(eventService.isLoading, false);
      // 실패 시 기존 상태 유지
      expect(eventService.scores.first.id, testScore['id']);
    });
  });
}
