import 'dart:async';
import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';
import 'package:lanebow/utils/test_utils.dart';
import 'package:lanebow/utils/test_config.dart';

// 이벤트 클래스들 - 클래스 외부로 이동
class ClubChangedEvent {
  final String clubId;
  
  ClubChangedEvent(this.clubId);
  
  @override
  String toString() => 'ClubChangedEvent(clubId: $clubId)';
  
  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is ClubChangedEvent && other.clubId == clubId;
  }
  
  @override
  int get hashCode => clubId.hashCode;
}

class EventBus {
  // 테스트 환경에서 사용할 별도의 인스턴스 맵
  static final Map<String, EventBus> _testInstances = {};
  
  // 기본 싱글톤 인스턴스
  static final EventBus _instance = EventBus._internal();
  
  // 테스트 ID를 저장하는 변수 (테스트 격리용)
  static String? _currentTestId;
  
  // 테스트 환경 초기화 상태 추적
  static bool _isTestEnvironmentReady = false;
  
  // 테스트 환경 초기화 관련 로깅
  static void _logTestEnvironmentInit() {
    _isTestEnvironmentReady = true;
    if (kDebugMode) {
      debugPrint('EventBus: 테스트 환경 초기화 완료');
    }
  }
  
  // 활성 구독 수 추적 (디버깅 및 메모리 누수 감지용)
  static int _activeSubscriptionCount = 0;
  
  /// 현재 활성화된 테스트 인스턴스 수 반환
  static int get testInstanceCount => _testInstances.length;
  
  // 테스트 재시도 횟수 추적 및 관리
  static int _testRetryCount = 0;
  
  // 테스트 재시도 횟수 가져오기
  static int get testRetryCount => _testRetryCount;
  
  // 테스트 재시도 횟수 증가
  static void incrementRetryCount() {
    _testRetryCount++;
    if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus: 테스트 재시도 횟수 증가 - $_testRetryCount');
    }
  }
  
  // 활성 구독 수 접근 게터
  static int get activeSubscriptionCount => _activeSubscriptionCount;
  
  // 현재 테스트 ID 설정 메서드
  static void setCurrentTestId(String testId) {
    // 테스트 설정 로드 확인 및 로깅
    final config = TestConfig.instance;
    if (config.isVerboseLogging) {
      debugPrint('EventBus: 테스트 설정 로드 - asyncTimeout=${config.asyncTimeout}ms, testDelay=${config.testDelay}ms');
    }
    // 이전 테스트 ID가 있으면 해당 인스턴스 정리
    if (_currentTestId != null && _currentTestId != testId) {
      if (_testInstances.containsKey(_currentTestId!)) {
        try {
          _testInstances[_currentTestId!]!.dispose();
        } catch (e) {
          if (kDebugMode) {
            debugPrint('EventBus: 이전 인스턴스 dispose 중 오류 - $e');
          }
        } finally {
          _testInstances.remove(_currentTestId!);
        }
      }
    }
    
    // 새 테스트 ID 설정
    _currentTestId = testId;
    
    // 새 테스트 ID에 대한 인스턴스가 없으면 생성
    if (!_testInstances.containsKey(testId)) {
      _testInstances[testId] = EventBus._internal();
    } else {
      // 이미 존재하는 인스턴스라도 초기화 수행
      _testInstances[testId]!.reset();
    }
    
    // 테스트 환경 초기화 로깅
    _logTestEnvironmentInit();
    
    // 활성 구독 수 초기화
    _activeSubscriptionCount = 0;
    
    // 테스트 재시도 횟수 초기화
    _testRetryCount = 0;
    
    if (kDebugMode) {
      debugPrint('EventBus: 테스트 ID 설정됨 - $testId');
      developer.log('EventBus 메모리 상태: $_testInstances', name: 'EventBus');
    }
  }
  
  // 현재 테스트 ID 초기화 메서드
  static Future<void> tearDownTestEnvironment() async {
    if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus: 테스트 환경 초기화 해제 시작');
    }
    
    // 테스트 환경 해제 전 비동기 리소스 상태 로깅
    await _logAsyncResourceState('테스트 환경 해제 전');
    
    // 테스트 종료 전 남아있는 타이머가 완료될 시간 제공
    await _ensureAllTimersComplete();
    
    // 현재 테스트 ID에 해당하는 인스턴스가 있는 경우 먼저 dispose 호출
    if (_currentTestId != null && _testInstances.containsKey(_currentTestId!)) {
      try {
        if (kDebugMode || TestConfig.instance.isVerboseLogging) {
          debugPrint('EventBus: 현재 테스트 ID($_currentTestId) 인스턴스 dispose 시도');
        }
        await _testInstances[_currentTestId!]!.dispose();
      } catch (e) {
        if (kDebugMode) {
          debugPrint('EventBus: 현재 테스트 인스턴스 dispose 중 오류 - $e');
        }
      }
    }
    
    _currentTestId = null;
    // 테스트 환경 초기화 해제 로깅
    _isTestEnvironmentReady = false;
    if (kDebugMode) {
      debugPrint('EventBus: 테스트 환경 초기화 해제');
    }
    _activeSubscriptionCount = 0;
    
    // 테스트 환경 해제 후 비동기 리소스 상태 로깅
    await _logAsyncResourceState('테스트 환경 해제 후');
    
    // 메모리 누수 방지를 위해 GC 힌트 추가
    if (TestUtils.isInTest) {
      developer.log('EventBus 메모리 정리 요청', name: 'EventBus');
    }
    
    // 추가 마이크로태스크 실행으로 비동기 작업 완료 보장
    await Future.microtask(() {});
    await Future.microtask(() {});
  }
  
  // 테스트 ID 초기화 (후방 호환성을 위해 유지)
  static Future<void> clearCurrentTestId() async {
    // tearDownTestEnvironment로 대체되었지만 후방 호환성을 위해 유지
    await tearDownTestEnvironment();
  }
  
  // 모든 테스트 인스턴스를 정리하는 정적 메서드
  static Future<void> resetAllTestInstances() async {
    if (_testInstances.isEmpty) {
      return;
    }
    
    if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus: 모든 테스트 인스턴스 초기화 시작 (인스턴스 수: ${_testInstances.length})');
    }
    
    // 초기화 전 비동기 리소스 상태 로깅
    await _logAsyncResourceState('모든 테스트 인스턴스 초기화 전');
    
    // 기존 인스턴스 모두 정리
    for (var entry in _testInstances.entries) {
      try {
        if (kDebugMode) {
          debugPrint('EventBus: 인스턴스 정리 중 - ${entry.key}');
        }
        await entry.value.dispose(); // 완전히 dispose 호출
        
        // 각 인스턴스 dispose 후 마이크로태스크 실행으로 비동기 작업 완료 보장
        await Future.microtask(() {});
      } catch (e) {
        // dispose 중 오류 무시
        if (kDebugMode) {
          debugPrint('EventBus: 인스턴스 정리 중 오류 - $e');
        }
      }
    }
    
    // 인스턴스 정리 후 중간 상태 로깅
    await _logAsyncResourceState('테스트 인스턴스 dispose 후, 초기화 전');
    
    // 모든 테스트 인스턴스 제거
    _testInstances.clear();
    
    // 기본 인스턴스도 초기화
    try {
      _instance.reset();
      await Future.microtask(() {}); // 기본 인스턴스 초기화 후 마이크로태스크 실행
    } catch (e) {
      // 기본 인스턴스 초기화 중 오류 무시
      if (kDebugMode) {
        debugPrint('EventBus: 기본 인스턴스 초기화 중 오류 - $e');
      }
    }
    
    // 현재 테스트 ID도 초기화
    _currentTestId = null;
    // 테스트 환경 초기화 해제 로깅
    if (_isTestEnvironmentReady) {
      _isTestEnvironmentReady = false;
      if (kDebugMode) {
        debugPrint('EventBus: 모든 테스트 환경 초기화 해제');
      }
    }
    _activeSubscriptionCount = 0;
    
    // 비동기 작업이 완료될 시간을 제공하되, 타이머 생성 없이 동기화
    // 타이머 생성 없이 마이크로태스크 큐 완료 보장
    await Future.microtask(() {});
    await Future.microtask(() {});
    
    // 테스트 종료 전 남아있는 타이머가 완료될 시간 제공
    // 테스트 환경에서 !timersPending assertion 실패 방지
    await _ensureAllTimersComplete();
    
    // 초기화 완료 후 비동기 리소스 상태 로깅
    await _logAsyncResourceState('모든 테스트 인스턴스 초기화 후');
    
    if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus: 모든 테스트 인스턴스 초기화됨');
      developer.log('EventBus 메모리 상태 초기화 완료', name: 'EventBus');
    }
  }
  
  // 비동기 리소스 상태 진단 및 로깅
  static Future<Map<String, dynamic>> _diagnoseAsyncResources() async {
    final diagnosticInfo = <String, dynamic>{
      'timestamp': DateTime.now().toIso8601String(),
      'activeSubscriptions': _activeSubscriptionCount,
      'testInstances': _testInstances.length,
      'currentTestId': _currentTestId,
      'isTestEnvironmentReady': _isTestEnvironmentReady,
      'testRetryCount': _testRetryCount,
    };
    
    // 인스턴스별 상태 수집
    final instanceStates = <String, Map<String, dynamic>>{};
    for (final entry in _testInstances.entries) {
      instanceStates[entry.key] = {
        'isDisposed': entry.value._isDisposed,
        'isStreamControllerClosed': entry.value._streamController.isClosed,
      };
    }
    diagnosticInfo['instanceStates'] = instanceStates;
    
    // 기본 인스턴스 상태
    diagnosticInfo['defaultInstance'] = {
      'isDisposed': _instance._isDisposed,
      'isStreamControllerClosed': _instance._streamController.isClosed,
    };
    
    return diagnosticInfo;
  }
  
  // 비동기 리소스 상태 로깅
  static Future<void> _logAsyncResourceState(String context) async {
    if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      final diagnosticInfo = await _diagnoseAsyncResources();
      debugPrint('===== EventBus 비동기 리소스 상태 ($context) =====');
      debugPrint('시간: ${diagnosticInfo['timestamp']}');
      debugPrint('활성 구독 수: ${diagnosticInfo['activeSubscriptions']}');
      debugPrint('테스트 인스턴스 수: ${diagnosticInfo['testInstances']}');
      debugPrint('현재 테스트 ID: ${diagnosticInfo['currentTestId']}');
      debugPrint('테스트 환경 준비 상태: ${diagnosticInfo['isTestEnvironmentReady']}');
      debugPrint('테스트 재시도 횟수: ${diagnosticInfo['testRetryCount']}');
      
      // 인스턴스별 상태 출력
      final instanceStates = diagnosticInfo['instanceStates'] as Map<String, Map<String, dynamic>>;
      if (instanceStates.isNotEmpty) {
        debugPrint('--- 테스트 인스턴스 상태 ---');
        instanceStates.forEach((key, value) {
          debugPrint('  $key: disposed=${value['isDisposed']}, streamClosed=${value['isStreamControllerClosed']}');
        });
      }
      
      // 기본 인스턴스 상태
      final defaultInstance = diagnosticInfo['defaultInstance'] as Map<String, dynamic>;
      debugPrint('--- 기본 인스턴스 상태 ---');
      debugPrint('  disposed=${defaultInstance['isDisposed']}, streamClosed=${defaultInstance['isStreamControllerClosed']}');
      
      debugPrint('===========================================');
    }
  }
  
  static Future<void> _ensureAllTimersComplete() async {
    if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus: 타이머 완료 대기 시작');
    }
    
    // 비동기 리소스 상태 로깅 (시작)
    await _logAsyncResourceState('타이머 완료 대기 시작');
    
    // 타이머를 새로 만들지 않기 위해 microtask만 사용
    await Future.microtask(() {});
    await Future.microtask(() {});
    await Future.microtask(() {});
    
    // 중간 상태 로깅
    await _logAsyncResourceState('타이머 완료 중간 상태');
    
    // 테스트 설정에서 정의된 타이머 완료 대기 시간을 적용
    // 추가 대기 시간이 설정되어 있어도 실제 Timer 생성은 하지 않음(테스트 타이머 경합 방지)
    // 필요한 경우 호출자에서 fakeAsync 등을 사용해 제어하도록 위임
    
    // 비동기 리소스 상태 로깅 (완료)
    await _logAsyncResourceState('타이머 완료 대기 완료');
    
    if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus: 타이머 완료 대기 완료');
    }
  }
  
  factory EventBus() {
    // 테스트 환경에서는 테스트 ID 자동 생성 및 설정
    if (TestUtils.isInTest) {
      // 테스트 ID가 설정되지 않았다면 자동으로 생성하지 말고, 명시적 설정을 강제한다
      if (_currentTestId == null) {
        throw StateError('EventBus: 테스트 환경에서 currentTestId가 설정되지 않았습니다. setUp에서 EventBus.setCurrentTestId(...)를 호출하세요.');
      }
      
      // 테스트 ID가 있지만 인스턴스가 없는 경우 생성
      if (!_testInstances.containsKey(_currentTestId!)) {
        if (kDebugMode) {
          debugPrint('EventBus: 테스트 ID $_currentTestId에 대한 새 인스턴스 생성');
        }
        _testInstances[_currentTestId!] = EventBus._internal();
        _testInstances[_currentTestId!]!.reset();
      }
      
      return _testInstances[_currentTestId]!;
    }
    
    // 테스트 환경이 아닌 경우 기본 싱글톤 인스턴스 반환
    return _instance;
  }
  
  EventBus._internal();
  
  // 이벤트 버스 내부 상태
  StreamController _streamController = StreamController.broadcast();
  bool _isDisposed = false;
  
  Stream get stream => _streamController.stream;
  
  void fire(dynamic event) {
    if (!_isDisposed && !_streamController.isClosed) {
      _streamController.add(event);
      // 디버그 모드 또는 상세 로깅 설정 시 이벤트 발생 로그 추가
      if (kDebugMode || TestConfig.instance.isVerboseLogging) {
        debugPrint('EventBus: 이벤트 발생 - $event');
      }
    } else if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus: 이벤트 발생 실패 (이미 dispose됨) - $event');
    }
  }
  
  // 특정 타입의 이벤트를 구독하는 메서드
  Stream<T> on<T>() {
    if (_isDisposed) {
      if (kDebugMode || TestConfig.instance.isVerboseLogging) {
        debugPrint('EventBus: dispose된 인스턴스에서 on<$T> 호출됨');
      }
      // 빈 스트림 반환
      return Stream<T>.empty();
    }
    
    // 타입 필터링 및 변환
    final filteredStream = stream.where((event) => event is T).cast<T>();
    
    // 구독 생성 시 카운터 증가 - 한 번만 증가
    _activeSubscriptionCount++;
    
    if (TestUtils.isInTest) {
      if (kDebugMode || TestConfig.instance.isVerboseLogging) {
        debugPrint('EventBus: 새 구독 추가됨 <$T> (현재 구독 수: $_activeSubscriptionCount)');
      }
      
      // 구독 취소 시 카운터 감소를 위한 처리
      // StreamController를 사용하여 구독 취소 시 카운터 감소 보장
      final controller = StreamController<T>();
      
      // 원본 스트림에서 데이터 수신 및 전달
      final subscription = filteredStream.listen(
        (data) => controller.add(data),
        onError: (error) => controller.addError(error),
        onDone: () => controller.close(),
      );
      
      // 컨트롤러 닫힐 때 구독 취소 및 카운터 감소
      controller.onCancel = () {
        subscription.cancel();
        _activeSubscriptionCount = (_activeSubscriptionCount > 0) ? _activeSubscriptionCount - 1 : 0;
        if (kDebugMode || TestConfig.instance.isVerboseLogging) {
          debugPrint('EventBus: 구독 취소됨 <$T> (남은 구독 수: $_activeSubscriptionCount)');
        }
      };
      
      return controller.stream;
    }
    
    return filteredStream;
  }

  Future<void> _logInstanceState() async {
    if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus 인스턴스 상태:');
      debugPrint('  isDisposed: $_isDisposed');
      debugPrint('  streamController.isClosed: ${_streamController.isClosed}');
      if (TestUtils.isInTest) {
        debugPrint('  현재 테스트 ID: $_currentTestId');
        debugPrint('  활성 구독 수: $_activeSubscriptionCount');
      }
    }
  }
  
  Future<void> dispose() async {
    if (!_isDisposed) {
      // dispose 전 상태 로깅
      if (kDebugMode || TestConfig.instance.isVerboseLogging) {
        debugPrint('EventBus: dispose 시작');
        await _logInstanceState();
      }
      
      _isDisposed = true;
      if (!_streamController.isClosed) {
        try {
          await _streamController.close();
          
          // 마이크로태스크 실행으로 비동기 작업 완료 보장
          await Future.microtask(() {});
          
          if ((kDebugMode || TestConfig.instance.isVerboseLogging) && TestUtils.isInTest) {
            debugPrint('EventBus: 인스턴스 dispose 완료 (테스트 ID: $_currentTestId)');
            // dispose 후 상태 로깅
            await _logInstanceState();
          }
        } catch (e) {
          if (kDebugMode || TestConfig.instance.isVerboseLogging) {
            debugPrint('EventBus: dispose 중 오류 - $e');
          }
        }
      }
    } else if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus: 이미 dispose된 인스턴스에 dispose 호출됨');
    }
  }

  /// 테스트 환경에서 EventBus를 초기화하는 메서드
  /// 기존 StreamController를 닫고 새로운 것으로 교체
  Future<void> reset() async {
    if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus: reset 시작');
      await _logInstanceState();
    }
    
    if (!_isDisposed) {
      try {
        await dispose();
      } catch (e) {
        if (kDebugMode) {
          debugPrint('EventBus: reset 중 dispose 호출 오류 - $e');
        }
      }
    }
    
    // 새로운 StreamController 생성
    _streamController = StreamController.broadcast();
    _isDisposed = false;
    
    // 활성 구독 수 초기화 (인스턴스 레벨)
    if (TestUtils.isInTest) {
      _activeSubscriptionCount = 0;
    }
    
    // 마이크로태스크 실행으로 비동기 작업 완료 보장
    await Future.microtask(() {});
    
    if (kDebugMode || TestConfig.instance.isVerboseLogging) {
      debugPrint('EventBus: 인스턴스 reset 완료 (테스트 ID: $_currentTestId)');
      await _logInstanceState();
    }
  }
}
