import 'dart:async';
import 'package:flutter/foundation.dart';
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/club_service.dart';
import 'package:lanebow/services/api_service.dart';
import 'package:lanebow/services/event_bus.dart';
import 'package:lanebow/models/club_model.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 'club_service_test.mocks.dart';

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

  // 테스트 클럽 데이터
  final testClub = {
    'id': 'test_club_id',
    'name': '테스트 클럽',
    'description': '테스트 클럽 설명',
    'location': '테스트 위치',
    'ownerId': 'test_owner_id',
    'createdAt': DateTime.now().toIso8601String(),
    'updatedAt': DateTime.now().toIso8601String(),
    'memberCount': 10,
    'isActive': true,
  };

  setUp(() async {
    // 테스트 모드 강제 설정
    TestUtils.setTestMode(true);
    
    // 테스트별 고유 ID 생성 및 설정 - 일관된 패턴 적용
    final testId = 'club_service_test_${DateTime.now().millisecondsSinceEpoch}';
    await EventBusTestUtils.setUp(testId);
    
    // SharedPreferences 모킹
    SharedPreferences.setMockInitialValues({});

    // API 서비스 모킹
    mockApiService = MockApiService();
    
    // ClubService 초기화 (테스트용 생성자 사용)
    clubService = ClubService.forTest(mockApiService);
    
    // 테스트용 토큰 초기화
    await clubService.initialize(testToken);
  });
  
  tearDown(() async {
    // 테스트 종료 후 정리 작업
    try {
      // 서비스 정리 (한 번만 호출)
      clubService.dispose();
    } catch (e) {
      // dispose 중 오류 무시
      if (kDebugMode) {
        print('ClubService dispose 중 오류 무시: $e');
      }
    }
    
    // EventBus 표준 정리
    await EventBusTestUtils.tearDown();
    
    // 테스트 모드 해제
    TestUtils.setTestMode(false);
    
    // 추가 지연으로 비동기 작업 완료 대기
    await Future.delayed(Duration(milliseconds: 100));
  });

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

    test('initialize 메서드가 저장된 클럽 ID가 있으면 클럽 정보를 가져와야 함', () async {
      // given
      SharedPreferences.setMockInitialValues({
        ApiConfig.clubIdKey: testClubId,
      });
      
      when(mockApiService.post(
        ApiConfig.clubs,
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => testClub);

      // when
      await clubService.initialize(testToken);

      // then
      verify(mockApiService.post(
        ApiConfig.clubs,
        data: {'clubId': testClubId},
      )).called(1);
      
      expect(clubService.currentClub?.id, testClubId);
      expect(clubService.currentClub?.name, '테스트 클럽');
    });

    test('fetchUserClubs 메서드가 사용자의 클럽 목록을 가져와야 함', () async {
      // given
      final testClubs = [testClub];
      when(mockApiService.post('${ApiConfig.clubs}/user'))
          .thenAnswer((_) async => testClubs);

      // when
      await clubService.fetchUserClubs();

      // then
      verify(mockApiService.post('${ApiConfig.clubs}/user')).called(1);
      
      expect(clubService.clubs.length, 1);
      expect(clubService.clubs[0].id, testClubId);
      expect(clubService.clubs[0].name, '테스트 클럽');
    });

    test('fetchClubById 메서드가 특정 클럽 정보를 가져와야 함', () async {
      // given
      when(mockApiService.post(
        ApiConfig.clubs,
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => testClub);

      // when
      await clubService.fetchClubById(testClubId);

      // then
      verify(mockApiService.post(
        ApiConfig.clubs,
        data: {'clubId': testClubId},
      )).called(1);
      
      expect(clubService.currentClub?.id, testClubId);
      expect(clubService.currentClub?.name, '테스트 클럽');
    });

    test('setCurrentClub 메서드가 클럽을 설정해야 함', () async {
      // given
      when(mockApiService.post(
        ApiConfig.clubs,
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => testClub);

      // when
      await clubService.setCurrentClub(testClubId);

      // then
      verify(mockApiService.post(
        ApiConfig.clubs,
        data: {'clubId': testClubId},
      )).called(1);
      
      expect(clubService.currentClub?.id, testClubId);
    });

    test('selectClub 메서드가 클럽을 선택하고 이벤트를 발생시켜야 함', () async {
      // given
      when(mockApiService.post(
        '${ApiConfig.clubs}/select-club',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => {});
      
      when(mockApiService.post(
        ApiConfig.clubs,
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => testClub);
      
      // 이벤트 버스 테스트를 위한 변수
      bool eventFired = false;
      String? eventClubId;
      StreamSubscription? subscription;
      
      try {
        // 이벤트 리스너 등록 - 리스너를 변수에 저장하여 나중에 확인 가능하게 함
        subscription = EventBus().on<ClubChangedEvent>().listen((event) {
          eventFired = true;
          eventClubId = event.clubId;
        });

        // when
        await clubService.selectClub(testClubId);

        // 이벤트 처리를 위한 지연 추가 - 시간 증가
        await Future.delayed(Duration(milliseconds: 500));

        // then
        verify(mockApiService.post(
          '${ApiConfig.clubs}/select-club',
          data: {'clubId': testClubId},
        )).called(1);
        
        verify(mockApiService.post(
          ApiConfig.clubs,
          data: {'clubId': testClubId},
        )).called(1);
        
        expect(clubService.currentClub?.id, testClubId);
        
        // 이벤트가 발생했는지 확인
        expect(eventFired, true);
        expect(eventClubId, testClubId);
      } finally {
        // 구독 취소 - 반드시 실행되도록 finally 블록에 배치
        await subscription?.cancel();
      }
    });

    test('createClub 메서드가 새 클럽을 생성해야 함', () async {
      // given
      final newClub = Club(
        name: '새 클럽',
        description: '새 클럽 설명',
        location: '새 위치',
      );
      
      when(mockApiService.post(
        ApiConfig.clubs,
        data: newClub.toJson(),
      )).thenAnswer((_) async => {'club': testClub});

      // when
      final result = await clubService.createClub(newClub);

      // then
      verify(mockApiService.post(
        ApiConfig.clubs,
        data: newClub.toJson(),
      )).called(1);
      
      expect(result.id, testClubId);
      expect(clubService.clubs.length, 1);
      expect(clubService.currentClub?.id, testClubId);
    });

    test('updateClub 메서드가 클럽 정보를 업데이트해야 함', () async {
      // given - 테스트 로컬 인스턴스 사용으로 격리
      final localMock = MockApiService();
      final localService = ClubService.forTest(localMock);
      await localService.initialize(testToken);

      final updates = {'name': '업데이트된 클럽'};
      final updatedClub = Map<String, dynamic>.from(testClub);
      updatedClub['name'] = '업데이트된 클럽';

      // 클럽 목록/현재 클럽 설정은 로컬 서비스 기준으로 스텁
      when(localMock.post('${ApiConfig.clubs}/user'))
          .thenAnswer((_) async => [testClub]);
      await localService.fetchUserClubs();

      when(localMock.post(
        ApiConfig.clubs,
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => testClub);
      await localService.fetchClubById(testClubId);

      // 준비 단계 상호작용 초기화
      clearInteractions(localMock);

      when(localMock.put(
        ApiConfig.clubs,
        data: {
          'clubId': testClubId,
          ...updates,
        },
      )).thenAnswer((_) async => {'club': updatedClub});

      // when
      final result = await localService.updateClub(testClubId, updates);
      // 상태 수렴 보장
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));
      // 추가 폴링: 이름 반영 수렴까지 대기 (최대 2.5s)
      const total = Duration(milliseconds: 2500);
      const step = Duration(milliseconds: 25);
      var waited = Duration.zero;
      while (
        (localService.clubs.isEmpty || localService.clubs.first.name != '업데이트된 클럽' ||
         localService.currentClub?.name != '업데이트된 클럽') &&
        waited < total
      ) {
        await Future<void>.delayed(step);
        waited += step;
      }
      // idle 보장 및 소폭 지연 추가로 최종 수렴 보장
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));

      // then - 상태 중심 검증
      expect(result.name, '업데이트된 클럽');
      expect(localService.clubs[0].name, '업데이트된 클럽');
      expect(localService.currentClub?.name, '업데이트된 클럽');
    });
    
    test('updateClub 메서드가 직접 클럽 객체가 반환되는 경우도 처리해야 함', () async {
      // given - 테스트 로컬 인스턴스 사용
      final localMock = MockApiService();
      final localService = ClubService.forTest(localMock);
      await localService.initialize(testToken);

      final updates = {'name': '업데이트된 클럽'};
      final updatedClub = Map<String, dynamic>.from(testClub);
      updatedClub['name'] = '업데이트된 클럽';

      when(localMock.post('${ApiConfig.clubs}/user'))
          .thenAnswer((_) async => [testClub]);
      await localService.fetchUserClubs();

      when(localMock.post(
        ApiConfig.clubs,
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => testClub);
      await localService.fetchClubById(testClubId);

      // 준비 단계 상호작용 초기화로 검증 범위 격리
      clearInteractions(localMock);

      // 직접 클럽 객체 반환 시뮬레이션
      when(localMock.put(
        ApiConfig.clubs,
        data: {
          'clubId': testClubId,
          ...updates,
        },
      )).thenAnswer((_) async => updatedClub);

      // when
      final result = await localService.updateClub(testClubId, updates);
      // 상태 수렴 보장
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));
      // 추가 폴링: 이름 반영 수렴까지 대기 (최대 2.5s)
      const total = Duration(milliseconds: 2500);
      const step = Duration(milliseconds: 25);
      var waited = Duration.zero;
      while (
        (localService.clubs.isEmpty || localService.clubs.first.name != '업데이트된 클럽' ||
         localService.currentClub?.name != '업데이트된 클럽') &&
        waited < total
      ) {
        await Future<void>.delayed(step);
        waited += step;
      }
      // idle 보장 및 소폭 지연 추가로 최종 수렴 보장
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 50));

      // then - 상태 결과 검증
      expect(result.name, '업데이트된 클럽');
      expect(localService.clubs[0].name, '업데이트된 클럽');
      expect(localService.currentClub?.name, '업데이트된 클럽');
    });
  });
}
