import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../config/api_config.dart'; import '../models/member_model.dart'; import './api_service.dart'; import './event_bus.dart'; class MemberService with ChangeNotifier { List _members = []; bool _isLoading = false; String? _token; String? _clubId; final ApiService _apiService; StreamSubscription? _eventSubscription; String? _lastError; DateTime? _lastLoadedAt; bool _disposed = false; // 기본 생성자 MemberService() : _apiService = ApiService(); // 테스트용 생성자 MemberService.forTest(this._apiService); List get members => [..._members]; bool get isLoading => _isLoading; String? get lastError => _lastError; DateTime? get lastLoadedAt => _lastLoadedAt; // 초기화 함수 void initialize(String token) { _token = token; _apiService.setToken(token); // 이벤트 구독 - 기존 구독이 있으면 취소 후 다시 구독 _eventSubscription?.cancel(); _eventSubscription = EventBus().on().listen((event) { if (_disposed) return; // dispose 이후 방어 if (event.clubId.isNotEmpty) { setClubId(event.clubId); } }); } // (개발 중 로깅 유틸 제거됨) @override void dispose() { // dispose 시작 상태 로깅 if (kDebugMode) { debugPrint('MemberService: dispose 시작'); } // 비동기 상태 로깅은 테스트/런타임 안정성을 위해 동기 경로로 축소 // (필요 시 개발 중 수동 호출) // 이벤트 구독 취소: 동기 dispose 규약을 지키기 위해 unawaited로 처리 _disposed = true; // 이후 작업 차단 if (_eventSubscription != null) { try { if (kDebugMode) { debugPrint('MemberService: 구독 취소 시작'); } unawaited(_eventSubscription!.cancel()); _eventSubscription = null; if (kDebugMode) { debugPrint('MemberService: 구독 취소 요청'); } } catch (e) { if (kDebugMode) { debugPrint('MemberService: 구독 취소 중 오류 - $e'); } } } if (kDebugMode) { debugPrint('MemberService: dispose 종료'); } super.dispose(); } // 클럽 ID 설정 Future setClubId(String clubId) async { if (_disposed) return; // dispose 이후 무시 // 클럽 ID가 변경된 경우에만 처리 if (_clubId != clubId) { _clubId = clubId; // SharedPreferences에 저장 final prefs = await SharedPreferences.getInstance(); await prefs.setString(ApiConfig.clubIdKey, clubId); notifyListeners(); // 새 클럽의 회원 목록 가져오기 if (_token != null) { await fetchClubMembers(); } } } // 클럽의 모든 회원 가져오기 Future fetchClubMembers() async { if (_disposed) return; // dispose 이후 무시 final sw = Stopwatch()..start(); if (kDebugMode) { debugPrint('MemberService.fetchClubMembers: 시작 (token=${_token != null}, clubId=$_clubId)'); } _isLoading = true; _lastError = null; notifyListeners(); try { // 클럽 ID 가져오기 - 메모리에 저장된 값을 우선 사용 String? clubId = _clubId; // 메모리에 없으면 SharedPreferences에서 가져오기 if (clubId == null) { final prefs = await SharedPreferences.getInstance(); clubId = prefs.getString(ApiConfig.clubIdKey); } if (clubId == null) { if (kDebugMode) { debugPrint('MemberService.fetchClubMembers: clubId 없음 - SharedPreferences에도 없음'); } throw Exception('선택된 클럽이 없습니다'); } final data = await _apiService.post( '${ApiConfig.clubs}/members', data: {'clubId': clubId}, ); if (kDebugMode) { debugPrint('MemberService.fetchClubMembers: 응답 수신 (elapsed=${sw.elapsedMilliseconds}ms)'); } // 응답 데이터가 리스트 형태로 직접 왔으므로 그대로 사용 if (data is List) { final List membersData = data; _members = membersData .map((memberData) => Member.fromJson(memberData)) .toList(); if (kDebugMode) { debugPrint('MemberService.fetchClubMembers: 리스트 파싱 완료 (count=${_members.length})'); if (_members.isEmpty) { debugPrint('MemberService.fetchClubMembers: 회원 목록이 비어있음'); } } } else { // 응답 데이터가 객체 형태로 왔을 경우 members 키를 통해 접근 final List membersData = data['members'] ?? []; _members = membersData .map((memberData) => Member.fromJson(memberData)) .toList(); if (kDebugMode) { debugPrint('MemberService.fetchClubMembers: 객체 파싱 완료 (count=${_members.length})'); if (_members.isEmpty) { debugPrint('MemberService.fetchClubMembers: 회원 목록이 비어있음'); } } } _isLoading = false; _lastLoadedAt = DateTime.now(); notifyListeners(); if (kDebugMode) { debugPrint('MemberService.fetchClubMembers: 성공 (total=${_members.length}, elapsed=${sw.elapsedMilliseconds}ms, loadedAt=${_lastLoadedAt?.toIso8601String()})'); } } catch (e, st) { _isLoading = false; _lastError = e.toString(); notifyListeners(); if (kDebugMode) { debugPrint('MemberService.fetchClubMembers: 실패 (${e.toString()})'); debugPrint('MemberService.fetchClubMembers: stacktrace -> $st'); } throw Exception('회원 목록을 불러오는데 실패했습니다: $e'); } } // ID로 회원 정보 가져오기 Future fetchMemberById(String memberId) async { if (_token == null || _clubId == null) { throw Exception('인증 또는 클럽 정보가 필요합니다'); } try { final data = await _apiService.post( '${ApiConfig.clubs}/members/detail', data: {'memberId': memberId}, ); if (data['statusCode'] == 200) { final memberData = data['member']; return Member.fromJson(memberData); } else { throw Exception('회원 정보를 불러오는데 실패했습니다'); } } catch (e) { throw Exception('회원 정보를 불러오는데 실패했습니다: $e'); } } // 회원 추가 Future addMember(Map memberData) async { if (_token == null || _clubId == null) { throw Exception('인증 또는 클럽 정보가 필요합니다'); } _isLoading = true; notifyListeners(); try { final payload = { 'clubId': _clubId, ...memberData, }; final data = await _apiService.post( '${ApiConfig.clubs}/members/add', data: payload, ); // 백엔드 응답 처리: 직접 회원 객체가 반환되거나 {member: {...}} 형태로 반환될 수 있음 final newMember = data is Map && data.containsKey('member') ? Member.fromJson(data['member']) : Member.fromJson(data); _members.add(newMember); _isLoading = false; notifyListeners(); return newMember; } catch (e) { _isLoading = false; notifyListeners(); throw Exception('회원 추가에 실패했습니다: $e'); } } // 회원 정보 업데이트 Future updateMember( String memberId, Map updates, ) async { if (_token == null || _clubId == null) { throw Exception('인증 또는 클럽 정보가 필요합니다'); } _isLoading = true; notifyListeners(); try { final data = await _apiService.post( '${ApiConfig.clubs}/members/update', data: {'clubId': _clubId, 'memberId': memberId, ...updates}, ); // 응답 데이터가 { member: {...} } 형태인지 또는 직접 회원 객체인지 확인 final Map memberData = data['member'] != null ? data['member'] as Map : data as Map; final updatedMember = Member.fromJson(memberData); // 회원 목록 업데이트 final index = _members.indexWhere((member) => member.id == memberId); if (index >= 0) { _members[index] = updatedMember; } _isLoading = false; notifyListeners(); return updatedMember; } catch (e) { _isLoading = false; notifyListeners(); throw Exception('회원 정보 업데이트에 실패했습니다: $e'); } } // 회원 삭제 (또는 비활성화) Future deleteMember(String memberId) async { if (_token == null || _clubId == null) { throw Exception('인증 또는 클럽 정보가 필요합니다'); } _isLoading = true; notifyListeners(); try { await _apiService.post( '${ApiConfig.clubs}/members/delete', data: {'memberId': memberId, 'clubId': _clubId}, ); // 회원 목록에서 제거 _members.removeWhere((member) => member.id == memberId); _isLoading = false; notifyListeners(); return true; } catch (e) { _isLoading = false; notifyListeners(); throw Exception('회원 삭제에 실패했습니다: $e'); } } // 여러 회원 ID로 회원 정보 가져오기 Future> fetchMembersByIds(String clubId, List memberIds) async { if (_token == null) { throw Exception('인증이 필요합니다'); } if (memberIds.isEmpty) { return []; } try { // 이미 로드된 회원 정보 중에서 필터링 if (_members.isNotEmpty && _clubId == clubId) { final cachedMembers = _members.where((member) => memberIds.contains(member.id)).toList(); // 모든 회원 정보가 캐시에 있는 경우 if (cachedMembers.length == memberIds.length) { return cachedMembers; } } // API로 회원 정보 요청 final data = await _apiService.post( '${ApiConfig.clubs}/members/batch', data: { 'clubId': clubId, 'memberIds': memberIds, }, ); if (data is List) { return data.map((memberData) => Member.fromJson(memberData)).toList(); } else if (data['members'] != null && data['members'] is List) { return (data['members'] as List).map((memberData) => Member.fromJson(memberData)).toList(); } else { throw Exception('회원 정보 형식이 올바르지 않습니다'); } } catch (e) { throw Exception('회원 정보를 불러오는데 실패했습니다: $e'); } } }