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/member_service.dart';
import 'package:lanebow/services/api_service.dart';
import 'package:lanebow/services/event_bus.dart'; // EventBus().fire() 호출에 필요
import 'package:lanebow/config/api_config.dart';
import 'package:lanebow/utils/test_utils.dart';
import '../utils/event_bus_test_utils.dart';

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

void main() {
  late MemberService memberService;
  late MockApiService mockApiService;
  final testToken = 'test_token';
  final testClubId = 'test_club_id';
  final testMemberId = 'test_member_id';

  // 테스트 데이터 초기화
  final testMember = {
    'id': 'test_member_id',
    'name': '테스트 회원',
    'email': 'test@example.com',
    'phone': '010-1234-5678',
    'gender': '남성',
    'birthYear': 1990,
    'level': '중급',
    'position': '회원',
    'joinDate': DateTime.now().toIso8601String(),
    'clubId': 'test_club_id',
    'isActive': true,
  };

  setUp(() async {
    // 테스트 모드 강제 설정
    TestUtils.setTestMode(true);
    
    // 테스트별 고유 ID 생성 및 설정 - 일관된 패턴 적용
    final testId = 'member_service_test_${DateTime.now().millisecondsSinceEpoch}';
    await EventBusTestUtils.setUp(testId);
    
    // API 서비스 모킹 생성 (한 번만 생성)
    mockApiService = MockApiService();
    
    // 토큰 설정 모킹
    when(mockApiService.setToken(any)).thenReturn(null);
    
    // SharedPreferences 모킹 설정
    SharedPreferences.setMockInitialValues({
      ApiConfig.tokenKey: testToken,
      ApiConfig.clubIdKey: testClubId,
    });
    
    // 상수 정의 - 모든 테스트에서 동일한 값 사용
    const newClubId = 'new_club_id';
    
    // 클럽 회원 목록 모킹 - 테스트 클럽 ID
    when(mockApiService.post(
      '${ApiConfig.clubs}/members',
      data: {'clubId': testClubId},
    )).thenAnswer((_) async => [testMember]);
    
    // 클럽 회원 목록 모킹 - new_club_id (명시적으로 설정)
    when(mockApiService.post(
      '${ApiConfig.clubs}/members',
      data: {'clubId': newClubId},
    )).thenAnswer((_) async => [testMember]);
    
    // 회원 추가 모킹
    when(mockApiService.post(
      '${ApiConfig.clubs}/members/add',
      data: anyNamed('data'),
    )).thenAnswer((_) async => {'id': testMemberId, ...testMember});
    
    // 회원 업데이트 모킹
    when(mockApiService.post(
      '${ApiConfig.clubs}/members/update',
      data: anyNamed('data'),
    )).thenAnswer((_) async => {
      'id': testMemberId,
      'name': '업데이트된 회원',
      'email': 'updated@example.com',
      'phone': '010-5555-5555',
      ...testMember,
    });
    
    // 회원 삭제 모킹
    when(mockApiService.post(
      '${ApiConfig.clubs}/members/delete',
      data: anyNamed('data'),
    )).thenAnswer((_) async => {'success': true});
    
    // MemberService 생성
    memberService = MemberService.forTest(mockApiService);
    
    // 초기화는 각 테스트에서 필요할 때 직접 호출
  });
  
  tearDown(() async {
    // 서비스 정리 (예외 무시)
    try {
      memberService.dispose();
    } catch (_) {}

    // EventBus 표준 정리 (내부에서 reset 포함)
    await EventBusTestUtils.tearDown();

    // 테스트 모드 해제
    TestUtils.setTestMode(false);

    // 약간의 지연으로 잔여 비동기 작업 안정화
    await Future.delayed(Duration(milliseconds: 100));
  });

  group('MemberService 테스트', () {
    // 간단한 폴링 헬퍼: 조건이 만족될 때까지 짧게 대기
    Future<void> _waitUntil(bool Function() predicate, {Duration timeout = const Duration(milliseconds: 400), Duration interval = const Duration(milliseconds: 10)}) async {
      final end = DateTime.now().add(timeout);
      while (DateTime.now().isBefore(end)) {
        if (predicate()) return;
        await Future<void>.delayed(interval);
      }
    }
    test('initialize 메서드가 토큰을 설정하고 이벤트를 구독해야 함', () async {
      // given
      // 테스트용 새 클럽 ID - setUp에서 정의한 값과 동일하게 사용
      const newClubId = 'new_club_id';
      
      // 테스트 시작 전 초기화 확인
      memberService = MemberService.forTest(mockApiService);
      
      // when
      memberService.initialize(testToken);
      
      // then
      verify(mockApiService.setToken(testToken)).called(1);
      
      // 이벤트 구독 테스트
      // 새 클럽 ID로 이벤트 발생
      EventBus().fire(ClubChangedEvent(newClubId));
      
      // 비동기 처리를 위한 지연 추가 - 조금 더 길게 설정
      await Future.delayed(Duration(milliseconds: 800));
      
      // 새 클럽 ID로 API 호출 확인
      verify(mockApiService.post(
        '/club/members',
        data: {'clubId': newClubId},
      )).called(1);
    });

    test('setClubId 메서드가 클럽 ID를 설정하고 회원 목록을 가져와야 함', () async {
      // given
      // 초기화 먼저 호출
      memberService.initialize(testToken);
      
      // 테스트용 클럽 ID
      const clubId = 'test_club_id';
      
      // when
      await memberService.setClubId(clubId);
      
      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/members',
        data: {'clubId': clubId},
      )).called(1);
      
      // 회원 목록 확인
      expect(memberService.members.length, 1);
      expect(memberService.members[0].id, testMember['id']);
    });

    test('fetchClubMembers 메서드가 클럽의 회원 목록을 가져와야 함', () async {
      // given
      // 초기화 먼저 호출
      memberService.initialize(testToken);
      
      // 클럽 ID 설정
      await memberService.setClubId(testClubId);
      
      // 초기화 후 모든 호출 기록 초기화
      clearInteractions(mockApiService);

      // when
      await memberService.fetchClubMembers();

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/members',
        data: {'clubId': testClubId},
      )).called(1);
    });

    test('addMember 메서드가 새 회원을 추가해야 함', () async {
      // given
      // 초기화 먼저 호출
      memberService.initialize(testToken);
      
      // 클럽 ID 설정
      await memberService.setClubId(testClubId);
      
      // 초기화 후 모든 호출 기록 초기화
      clearInteractions(mockApiService);
      
      final newMemberData = {
        'name': '새 회원',
        'email': 'new@example.com',
        'phone': '010-9876-5432',
        'clubId': testClubId,
      };

      // when
      final result = await memberService.addMember(newMemberData);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/members/add',
        data: newMemberData,
      )).called(1);
      
      expect(result.id, testMember['id']);
    });

    test('addMember 메서드가 직접 회원 객체가 반환되는 경우도 처리해야 함', () async {
      // given
      // 초기화 먼저 호출
      memberService.initialize(testToken);
      
      // 클럽 ID 설정
      await memberService.setClubId(testClubId);
      
      // 초기화 후 모든 호출 기록 초기화
      clearInteractions(mockApiService);
      
      final newMemberData = {
        'name': '새 회원',
        'email': 'new@example.com',
        'phone': '010-9876-5432',
        'clubId': testClubId,
      };
      
      when(mockApiService.post(
        '${ApiConfig.clubs}/members/add',
        data: newMemberData,
      )).thenAnswer((_) async => testMember);

      // when
      final result = await memberService.addMember(newMemberData);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/members/add',
        data: newMemberData,
      )).called(1);
      
      expect(result.id, testMember['id']);
    });

    test('updateMember 메서드가 회원 정보를 업데이트해야 함', () async {
      // given
      // 초기화 먼저 호출
      memberService.initialize(testToken);
      
      // 클럽 ID 설정
      await memberService.setClubId(testClubId);
      clearInteractions(mockApiService);
      
      final updateMemberData = {
        'name': '업데이트된 회원',
        'email': 'updated@example.com',
        'phone': '010-5555-5555',
      };
      
      // 업데이트된 회원 데이터 mock 설정
      when(mockApiService.post(
        '${ApiConfig.clubs}/members/update',
        data: anyNamed('data'),
      )).thenAnswer((_) async => {'member': {
        ...testMember,
        'name': '업데이트된 회원',
        'email': 'updated@example.com',
        'phone': '010-5555-5555',
      }});

      // when
      final result = await memberService.updateMember(testMemberId, updateMemberData);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/members/update',
        data: anyNamed('data'),
      )).called(1);
      
      expect(result.name, '업데이트된 회원');
      expect(result.email, 'updated@example.com');
      expect(result.phone, '010-5555-5555');
    });

    test('deleteMember 메서드가 회원을 삭제해야 함', () async {
      // given
      // 초기화 먼저 호출
      memberService.initialize(testToken);
      
      // 클럽 ID 설정
      await memberService.setClubId(testClubId);
      clearInteractions(mockApiService);

      // when
      final result = await memberService.deleteMember(testMemberId);

      // then
      verify(mockApiService.post(
        '${ApiConfig.clubs}/members/delete',
        data: anyNamed('data'),
      )).called(1);
      
      expect(result, true);
    });

    test('ClubChangedEvent가 발생하면 setClubId가 호출되어야 함', () async {
      // given
      // 테스트용 새 클럽 ID - setUp에서 정의한 값과 동일하게 사용
      const newClubId = 'new_club_id';
      
      // 테스트 시작 전 초기화 확인
      memberService = MemberService.forTest(mockApiService);
      
      // 초기화 먼저 호출
      memberService.initialize(testToken);
      
      // 이전 호출 기록 초기화
      clearInteractions(mockApiService);
      
      try {
        // when
        // 이벤트 발생
        EventBus().fire(ClubChangedEvent(newClubId));
        
        // 비동기 처리를 위한 지연 추가 - 조금 더 길게 설정
        await Future.delayed(Duration(milliseconds: 800));
        
        // then
        // 새 클럽 ID로 API 호출 확인
        verify(mockApiService.post(
          '/club/members',
          data: {'clubId': newClubId},
        )).called(1);
      } finally {
        // 테스트 종료 시 memberService 정리 (파일 전역 tearDown도 수행되지만 중복해도 안전)
        memberService.dispose();
        // EventBus 정리는 파일 전역 tearDown의 EventBusTestUtils.tearDown()에만 맡김
      }
    });
    
    // 이벤트 구독 테스트 추가
    test('dispose 메서드가 이벤트 구독을 취소해야 함', () async {
      // 파일 전역의 EventBusTestUtils.setUp/tearDown에만 의존하여 경합 방지
      if (kDebugMode) {
        debugPrint('\n===== dispose 테스트 시작 =====');
      }

      // given - 로컬 격리 인스턴스 사용으로 교차 테스트 간섭 차단
      final localMock = MockApiService();
      when(localMock.setToken(any)).thenReturn(null);
      final localService = MemberService.forTest(localMock);
      localService.initialize(testToken);

      // 선행 호출 기록 초기화: dispose 전 이후 호출만 관찰하기 위함
      clearInteractions(localMock);

      // when - 먼저 dispose 실행 후 이벤트 발생
      localService.dispose();
      
      // 비동기 취소가 완료되도록 충분히 대기 (플래키 방지)
      await Future.delayed(Duration(milliseconds: 1200));
      await Future.microtask(() {});
      
      // dispose 이후 이벤트 발행
      EventBus().fire(ClubChangedEvent('any_club'));
      
      // 비동기 이벤트 전파 대기 (플래키 방지)
      await Future.delayed(Duration(milliseconds: 1200));
      await Future.microtask(() {});
      
      // then - dispose 이후에는 회원 목록 조회 호출이 발생하면 안 됨(핵심 상호작용만 검증)
      verifyNever(localMock.post(
        '${ApiConfig.clubs}/members',
        data: anyNamed('data'),
      ));
      if (kDebugMode) {
        debugPrint('===== dispose 테스트 종료 =====\n');
      }
    });

    test('fetchClubMembers 실패 시 예외 전파, isLoading 해제 및 lastError 설정', () async {
      // given
      memberService.initialize(testToken);
      await memberService.setClubId(testClubId);
      clearInteractions(mockApiService);

      when(mockApiService.post(
        '${ApiConfig.clubs}/members',
        data: {'clubId': testClubId},
      )).thenThrow(Exception('network error'));

      // when/then
      expect(
        () async => await memberService.fetchClubMembers(),
        throwsA(isA<Exception>()),
      );

      // 마지막 상태 확인
      expect(memberService.isLoading, isFalse);
      expect(memberService.lastError, isNotNull);
      expect(memberService.lastError!, contains('network error'));
    });

    test('fetchClubMembers가 빈 목록을 반환하면 기존 members를 비워야 함', () async {
      // given: 초기 조회에서 1명 로드
      memberService.initialize(testToken);
      await memberService.setClubId(testClubId);
      expect(memberService.members.length, 1);

      clearInteractions(mockApiService);
      // 광역 스텁: 어떤 clubId로 호출되더라도 빈 목록을 반환하여 외부 이벤트 간섭 차단
      when(mockApiService.post(
        any,
        data: anyNamed('data'),
      )).thenAnswer((invocation) async {
        return <Map<String, dynamic>>[];
      });
      when(mockApiService.post(
        '${ApiConfig.clubs}/members',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => <Map<String, dynamic>>[]);
      // 예기치 않은 ClubChangedEvent로 new_club_id 재조회가 발생해도 빈 목록을 유지하도록 방어
      when(mockApiService.post(
        '${ApiConfig.clubs}/members',
        data: {'clubId': 'new_club_id'},
      )).thenAnswer((_) async => <Map<String, dynamic>>[]);

      // when
      await memberService.fetchClubMembers();
      // 상태 수렴까지 짧게 폴링(플래키 방지)
      await _waitUntil(() => memberService.members.isEmpty && memberService.isLoading == false && memberService.lastError == null);

      // then
      expect(memberService.members, isEmpty);
      expect(memberService.isLoading, isFalse);
      expect(memberService.lastError, isNull);
    });

    test('fetchClubMembers 진행 중 isLoading이 true였다가 완료 시 false로 변경되어야 함', () async {
      // given
      memberService.initialize(testToken);
      await memberService.setClubId(testClubId);
      clearInteractions(mockApiService);

      final completer = Completer<List<Map<String, dynamic>>>();
      when(mockApiService.post(
        '${ApiConfig.clubs}/members',
        data: {'clubId': testClubId},
      )).thenAnswer((_) => completer.future);

      // when: await하지 않고 먼저 호출하여 중간 상태 관찰
      final future = memberService.fetchClubMembers();

      // then: 호출 직후 로딩 on
      expect(memberService.isLoading, isTrue);

      // when: 응답 완료
      completer.complete([{
        'id': 'm2',
        'name': '새 회원2',
        'clubId': testClubId,
        'isActive': true,
      }]);
      await future;

      // then: 로딩 off
      expect(memberService.isLoading, isFalse);
      expect(memberService.members.length, 1);
    });

    test('ClubChangedEvent 수신 시 setClubId 호출 및 새 클럽 회원 재조회', () async {
      // given: 초기 설정 및 첫 조회
      memberService.initialize(testToken);
      await memberService.setClubId(testClubId);
      expect(memberService.members.length, 1);

      // 새 클럽 응답을 별도로 설정 (setup의 기본 설정을 override)
      const newClubId = 'new_club_id';
      final newMember = {
        'id': 'm_new',
        'name': '새 회원',
        'clubId': newClubId,
        'isActive': true,
      };
      clearInteractions(mockApiService);
      when(mockApiService.post(
        '${ApiConfig.clubs}/members',
        data: {'clubId': newClubId},
      )).thenAnswer((_) async => [newMember]);

      // when: ClubChangedEvent 발생
      EventBus().fire(ClubChangedEvent(newClubId));

      // then: 비동기 처리 완료 대기 후 검증
      // setClubId -> SharedPreferences 저장 확인
      // 간단한 대기 후 확인 (EventBus 비동기 루프 반영)
      await Future<void>.delayed(const Duration(milliseconds: 10));

      // Api가 새로운 clubId로 호출되었는지 검증
      verify(mockApiService.post(
        '${ApiConfig.clubs}/members',
        data: {'clubId': newClubId},
      )).called(greaterThanOrEqualTo(1));

      // SharedPreferences에 최신 clubId 반영 확인
      final prefs = await SharedPreferences.getInstance();
      expect(prefs.getString(ApiConfig.clubIdKey), newClubId);

      // members 갱신 확인
      expect(memberService.members.length, 1);
      expect(memberService.members.first.id, 'm_new');
    });

    test('clubId가 null이면 fetchClubMembers는 예외를 던지고 isLoading 해제 및 lastError 설정', () async {
      // given: prefs/내부 clubId 모두 제거
      memberService.initialize(testToken);
      clearInteractions(mockApiService);
      final prefs = await SharedPreferences.getInstance();
      await prefs.remove(ApiConfig.clubIdKey);

      // when/then
      expect(
        () async => await memberService.fetchClubMembers(),
        throwsA(isA<Exception>()),
      );

      // 비동기 상태 업데이트가 완료되도록 잠시 대기(플래키 방지)
      await Future<void>.delayed(const Duration(milliseconds: 10));

      // 상태 확인
      expect(memberService.isLoading, isFalse);
      expect(memberService.lastError, isNotNull);
      expect(memberService.lastError!, contains('선택된 클럽이 없습니다'));
      // API는 호출되지 않음
      verifyNever(mockApiService.post(any, data: anyNamed('data')));
    });

    test("API 응답이 {'members': [...]} 형태여도 파싱되어 members가 채워져야 함", () async {
      // given
      memberService.initialize(testToken);
      await memberService.setClubId(testClubId);
      clearInteractions(mockApiService);

      when(mockApiService.post(
        '${ApiConfig.clubs}/members',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => {
            'members': [
              {
                'id': 'm3',
                'name': '객체응답 회원',
                'clubId': testClubId,
                'isActive': true,
              }
            ]
          });

      // when
      await memberService.fetchClubMembers();

      // then
      expect(memberService.members.length, 1);
      expect(memberService.members.first.id, 'm3');
      expect(memberService.lastError, isNull);
    });

    test('fetchClubMembers 성공 시 lastLoadedAt이 설정되어야 함', () async {
      // given
      memberService.initialize(testToken);
      await memberService.setClubId(testClubId);
      clearInteractions(mockApiService);
      when(mockApiService.post(
        '${ApiConfig.clubs}/members',
        data: {'clubId': testClubId},
      )).thenAnswer((_) async => [
            {
              'id': 'm4',
              'name': '최근 조회 회원',
              'clubId': testClubId,
              'isActive': true,
            }
          ]);

      // when
      await memberService.fetchClubMembers();
      // lastLoadedAt 및 members 수렴까지 대기 (타임아웃 여유 증가)
      await _waitUntil(
        () => memberService.lastLoadedAt != null && memberService.members.length == 1,
        timeout: const Duration(milliseconds: 1500),
        interval: const Duration(milliseconds: 25),
      );
      await EventBusTestUtils.waitForIdle();
      await Future<void>.delayed(const Duration(milliseconds: 100));

      // then
      expect(memberService.members.first.id, 'm4');
      expect(memberService.lastLoadedAt, isNotNull);
      // 시간 범위 비교는 환경 의존성이 커서 제거하고 비null만 검증
    });
  });
}

