import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:lanebow/models/event_model.dart';
import 'package:lanebow/models/participant_model.dart';
import 'package:lanebow/models/score_model.dart';
import 'package:lanebow/models/team_model.dart';
import 'package:lanebow/services/event_service.dart';
import '../../utils/event_bus_test_utils.dart';
import 'package:lanebow/utils/test_utils.dart';
import 'package:lanebow/screens/club/team_generator_screen.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'test_event_details_screen.dart';
import '../../utils/dummy_test_data.dart';

// 이벤트 서비스 모킹을 위한 어노테이션
@GenerateMocks([EventService])
import 'event_details_screen_test.mocks.dart';

void main() {
  late MockEventService mockEventService;
  late Event testEvent;
  
  // 클립보드 및 플러그인 모킹 설정
  TestWidgetsFlutterBinding.ensureInitialized();
  
  // 클립보드 모킹 (deprecated API 대체)
  final channel = const MethodChannel('plugins.flutter.io/clipboard');
  TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
      .setMockMethodCallHandler(channel, (MethodCall methodCall) async {
    if (methodCall.method == 'setData') {
      return null;
    }
    return null;
  });

  

  


  

  
  
  // 탭 컨트롤러 오류 무시
  FlutterError.onError = (FlutterErrorDetails details) {
    if (details.exception.toString().contains('Controller\'s length property') ||
        details.exception.toString().contains('does not match the number of tabs') ||
        details.exception.toString().contains('TabController')) {
      // 탭 컨트롤러 관련 오류 무시
      return;
    }
    FlutterError.presentError(details);
  };

  setUp(() async {
    // EventBus 테스트 환경 초기화 (플래키 방지)
    TestUtils.setTestMode(true);
    await EventBusTestUtils.setUp('event_details_screen_test');

    mockEventService = MockEventService();

    // 테스트용 이벤트 데이터 생성 (공용 더미 유틸 활용)
    testEvent = DummyTestData.event(
      id: '1',
      clubId: '1',
      title: '테스트 이벤트',
      isActive: true,
    );
    
    // 참가자 및 점수 데이터 모킹
    when(mockEventService.fetchEventParticipants(any))
        .thenAnswer((_) async => <Participant>[]);
    when(mockEventService.fetchEventScores(any))
        .thenAnswer((_) async => <Score>[]);
    // 팀 데이터 모킹 - 팀 탭이 표시되도록 비어있지 않은 팀 목록 반환
    when(mockEventService.fetchEventTeams(any))
        .thenAnswer((_) async => <Team>[Team(id: '1', eventId: '1', name: '팀 1', memberIds: [])]);
  });
  
  tearDown(() async {
    // 테스트 종료 후 EventBus 자원 정리 (idle/타이머 플러시 포함)
    await EventBusTestUtils.tearDown();
  });

  // 스크롤 도우미 함수
  Future<void> scrollToWidget(WidgetTester tester, Finder finder) async {
    await tester.dragUntilVisible(
      finder,
      find.byType(SingleChildScrollView),
      const Offset(0, -100),
    );
    await tester.pumpAndSettle();
  }
  
  Widget createEventDetailsScreen({List<Score>? scores, List<Participant>? participants}) {
    // 테스트용 위젯 사용
    return MaterialApp(
      home: ChangeNotifierProvider<EventService>.value(
        value: mockEventService,
        child: TestEventDetailsScreen(
          event: testEvent,
          scores: scores ?? [],
          participants: participants ?? [],
        ),
      ),
    );
  }

  group('EventDetailsScreen 위젯 테스트', () {
    testWidgets('이벤트 상세 화면이 올바르게 렌더링되어야 함', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // then
      expect(find.text('테스트 이벤트'), findsOneWidget); // 이벤트 제목
      
      // 탭 메뉴 확인
      expect(find.text('기본 정보').first, findsOneWidget); // 기본 정보 탭
      expect(find.text('참가자').first, findsOneWidget); // 참가자 탭
      expect(find.text('점수').first, findsOneWidget); // 점수 탭
      expect(find.text('팀').first, findsOneWidget); // 팀 탭
    });

    testWidgets('참가자 탭: 404 오류 발생 시 전용 문구 표시 및 재시도 성공 시 해제', (WidgetTester tester) async {
      // given: 최초 드롭다운 변경 시 404 예외 모킹
      when(mockEventService.fetchEventParticipants(any)).thenThrow(Exception('HTTP 404 Not Found'));

      final dummyParticipants = <Participant>[
        Participant(id: 'p1', eventId: '1', memberId: 'm1', name: '홍길동'),
      ];

      await tester.pumpWidget(createEventDetailsScreen(participants: dummyParticipants));
      await tester.pumpAndSettle();

      // when: 참가자 탭 이동 → 드롭다운에서 '취소' 선택하여 실패 유도
      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();

      final dropdown = find.byKey(const Key('participant_filter_dropdown'));
      expect(dropdown, findsOneWidget);
      await tester.tap(dropdown);
      await tester.pumpAndSettle();
      await tester.tap(find.text('취소').last);
      await tester.pumpAndSettle();

      // then: 404 전용 에러 문구 + 재시도 버튼
      expect(find.byKey(const Key('participant_error_text')), findsOneWidget);
      expect(find.text('참가자 정보가 없습니다 (404)'), findsOneWidget);
      expect(find.byKey(const Key('participant_retry_button')), findsOneWidget);

      // given: 재시도는 성공 모킹
      final retryCompleter = Completer<List<Participant>>();
      when(mockEventService.fetchEventParticipants(any)).thenAnswer((_) => retryCompleter.future);

      // when: 재시도 수행
      await tester.tap(find.byKey(const Key('participant_retry_button')));
      await tester.pump();
      retryCompleter.complete(<Participant>[]);
      await tester.pumpAndSettle();

      // then: 에러 문구 해제
      expect(find.byKey(const Key('participant_error_text')), findsNothing);
    });

    testWidgets('이벤트 유형 및 상태 뱃지가 올바르게 표시되어야 함', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // then
      // 이벤트 유형 뱃지 확인
      expect(find.text('팀전'), findsOneWidget);
      
      // 이벤트 상태 뱃지 확인
      expect(find.text('예정'), findsOneWidget);
    });

    testWidgets('공개 URL이 올바르게 표시되고 복사 버튼이 동작해야 함', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // 공개 URL 텍스트 찾기
      final urlFinder = find.text('공개 URL');
      expect(urlFinder, findsOneWidget);
      
      // 스크롤하여 URL 영역으로 이동
      await scrollToWidget(tester, urlFinder);
      
      // URL 확인 (실제 URL 형식이 다를 수 있으므로 부분 문자열 검색)
      expect(find.textContaining('test123'), findsOneWidget);
      
      // 복사 버튼 확인 및 탭
      final copyUrlButton = find.byIcon(Icons.copy).first;
      expect(copyUrlButton, findsOneWidget);
      await tester.tap(copyUrlButton);
      await tester.pumpAndSettle();
      
      // 스낵바 확인 - 정확한 텍스트로 검색
      expect(find.byType(SnackBar), findsOneWidget);
      expect(find.text('공개 URL이 클립보드에 복사되었습니다'), findsOneWidget);
    });

    testWidgets('비밀번호가 올바르게 표시되고 복사 버튼이 동작해야 함', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // 비밀번호 텍스트 찾기 (정확한 텍스트로 수정)
      final passwordFinder = find.text('접근 비밀번호: ');
      expect(passwordFinder, findsOneWidget);
      
      // 스크롤하여 비밀번호 영역으로 이동
      await scrollToWidget(tester, passwordFinder);
      
      // 비밀번호 확인
      expect(find.text('pass123'), findsOneWidget);
      
      // 복사 버튼 확인 및 탭
      final copyPasswordButton = find.byIcon(Icons.copy).last;
      expect(copyPasswordButton, findsOneWidget);
      await tester.tap(copyPasswordButton);
      await tester.pumpAndSettle();
      
      // 스낵바 확인
      expect(find.byType(SnackBar), findsOneWidget);
      // 정확한 스낵바 메시지 확인
      expect(find.text('비밀번호가 클립보드에 복사되었습니다'), findsOneWidget);
    });

    testWidgets('이벤트 공유 버튼이 동작하고 공유 옵션이 표시되어야 함', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // 기본 정보 탭으로 이동 확인
      expect(find.text('기본 정보').first, findsOneWidget);
      
      // 공유 버튼 찾기
      final shareButton = find.byIcon(Icons.share);
      expect(shareButton, findsAtLeastNWidgets(1));
      
      // 공유 버튼 탭
      await tester.tap(shareButton.first);
      await tester.pumpAndSettle();
      
      // 바텀 시트 확인
      expect(find.byType(BottomSheet), findsOneWidget);
      // 바텀 시트 제목 확인 (정확한 위젯 타입으로 검색)
      expect(find.byType(Row).last.evaluate().first.widget, isA<Row>());
      
      // 공유 옵션 확인 (ListTile로 검색)
      expect(find.byType(ListTile), findsAtLeastNWidgets(3));
      expect(find.text('기본 정보만 공유'), findsOneWidget);
      expect(find.text('참가 링크 포함 공유'), findsOneWidget);
      expect(find.text('상세 정보 포함 공유'), findsOneWidget);
    });

    testWidgets('핸디캡 토글 시 총점 라벨이 올바르게 변경되어야 함', (WidgetTester tester) async {
      // given: 핸디캡이 포함된 단일 점수 데이터
      final score = Score(
        id: 's1',
        memberId: 'm1',
        eventId: '1',
        clubId: '1',
        frames: const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        totalScore: 100,
        handicap: 10,
        date: DateTime(2024, 1, 1),
        participantName: '홍길동',
      );
      final participant = Participant(
        id: 'p1',
        eventId: '1',
        memberId: 'm1',
        name: '홍길동',
        status: 'confirmed',
      );

      await tester.pumpWidget(createEventDetailsScreen(
        scores: [score],
        participants: [participant],
      ));
      await tester.pumpAndSettle();

      // when: 점수 탭으로 이동
      await tester.tap(find.text('점수').first);
      await tester.pumpAndSettle();

      // then: 초기(핸디캡 미포함) 총점 라벨 확인
      expect(find.textContaining('총점: 100'), findsOneWidget);
      expect(find.textContaining('(100+10)'), findsNothing);

      // when: 핸디캡 스위치 켜기
      await tester.tap(find.byType(Switch));
      await tester.pumpAndSettle();

      // then: 핸디캡 포함 총점 라벨로 변경
      expect(find.textContaining('총점: 110 (100+10)'), findsOneWidget);
    });

    testWidgets('점수 탭: 새로고침 중 로딩 오버레이 표시, 완료 후 해제 및 스낵바 표시', (WidgetTester tester) async {
      // given: fetchEventScores 지연 설정 + 초기 더미 점수/참가자 주입(리스트/컨트롤 렌더링 보장)
      final completer = Completer<List<Score>>();
      when(mockEventService.fetchEventScores(any)).thenAnswer((_) => completer.future);

      final dummyScores = <Score>[
        Score(
          id: 's1', memberId: 'm1', eventId: '1', clubId: '1',
          frames: const [10, 9, 1, 8, 2, 7, 3, 6, 4, 5], totalScore: 150,
          date: DateTime(2024, 1, 1), participantName: '홍길동',
        ),
      ];
      final dummyParticipants = <Participant>[
        Participant(
          id: 'p1', eventId: '1', memberId: 'm1', name: '홍길동',
        ),
      ];

      await tester.pumpWidget(createEventDetailsScreen(scores: dummyScores, participants: dummyParticipants));
      await tester.pumpAndSettle();

      // when: 점수 탭으로 이동 → 새로고침 버튼 탭
      await tester.tap(find.text('점수').first);
      await tester.pumpAndSettle();
      final refreshBtn = find.byKey(const Key('score_refresh_button'));
      expect(refreshBtn, findsOneWidget);
      await tester.tap(refreshBtn);
      await tester.pump(); // 비동기 시작 직후 프레임

      // then: 로딩 오버레이 표시
      expect(find.byKey(const Key('loading_overlay')), findsOneWidget);

      // when: fetch 완료
      completer.complete(<Score>[]);
      await tester.pumpAndSettle();

      // then: 로딩 오버레이 해제, 스낵바 표시, 서비스 호출 검증
      expect(find.byKey(const Key('loading_overlay')), findsNothing);
      expect(find.byType(SnackBar), findsOneWidget);
      expect(find.text('점수가 업데이트되었습니다'), findsOneWidget);
      verify(mockEventService.fetchEventScores(any)).called(1);
    });

    testWidgets('점수 탭: 새로고침 실패 시 에러 메시지/재시도 버튼 표시, 재시도 성공 시 에러 해제', (WidgetTester tester) async {
      // given: 최초 새로고침 실패를 모킹
      when(mockEventService.fetchEventScores(any)).thenThrow(Exception('fetch fail'));

      final dummyScores = <Score>[
        Score(
          id: 's1', memberId: 'm1', eventId: '1', clubId: '1',
          frames: const [10, 9, 1, 8, 2, 7, 3, 6, 4, 5], totalScore: 150,
          date: DateTime(2024, 1, 1), participantName: '홍길동',
        ),
      ];
      final dummyParticipants = <Participant>[
        Participant(
          id: 'p1', eventId: '1', memberId: 'm1', name: '홍길동',
        ),
      ];

      await tester.pumpWidget(createEventDetailsScreen(scores: dummyScores, participants: dummyParticipants));
      await tester.pumpAndSettle();

      // when: 점수 탭 이동 → 새로고침 실패 유도
      await tester.tap(find.text('점수').first);
      await tester.pumpAndSettle();
      await tester.tap(find.byKey(const Key('score_refresh_button')));
      await tester.pumpAndSettle();

      // then: 에러 UI 노출(문구 + 재시도 버튼)
      expect(find.byKey(const Key('score_error_text')), findsOneWidget);
      expect(find.text('점수 조회 실패'), findsOneWidget);
      expect(find.byKey(const Key('score_retry_button')), findsOneWidget);

      // given: 재시도는 성공으로 모킹 변경
      final retryCompleter = Completer<List<Score>>();
      when(mockEventService.fetchEventScores(any)).thenAnswer((_) => retryCompleter.future);

      // when: 재시도 버튼 탭 → 로딩 표시 → 완료
      await tester.tap(find.byKey(const Key('score_retry_button')));
      await tester.pump();
      expect(find.byKey(const Key('loading_overlay')), findsOneWidget);
      retryCompleter.complete(<Score>[]);
      await tester.pumpAndSettle();

      // then: 에러 UI 해제, 로딩 해제. 스낵바는 재시도 경로에서는 보장되지 않음.
      expect(find.byKey(const Key('score_error_text')), findsNothing);
      expect(find.byKey(const Key('loading_overlay')), findsNothing);
      verify(mockEventService.fetchEventScores(any)).called(greaterThanOrEqualTo(2));
    });
  });

  group('팀 탭 내비게이션 및 저장 플로우 테스트', () {
    testWidgets('팀 탭에서 재생성 버튼 탭 시 TeamGeneratorScreen으로 이동해야 함', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // when: 팀 탭으로 이동 후 '팀 재생성' 버튼 탭
      await tester.tap(find.text('팀').first);
      await tester.pumpAndSettle();

      final regenerateButton = find.text('팀 재생성');
      expect(regenerateButton, findsOneWidget);
      await tester.tap(regenerateButton);
      await tester.pumpAndSettle();

      // then: TeamGeneratorScreen으로 네비게이션 되었는지 확인
      expect(find.byType(TeamGeneratorScreen), findsOneWidget);
    });

    testWidgets('TeamGeneratorScreen에서 성공(pop(true)) 시 데이터 갱신 및 스낵바 표시', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // 팀 탭 이동 후 재생성 버튼 탭하여 TeamGeneratorScreen 진입
      await tester.tap(find.text('팀').first);
      await tester.pumpAndSettle();

      final regenerateButton = find.text('팀 재생성');
      expect(regenerateButton, findsOneWidget);
      await tester.tap(regenerateButton);
      await tester.pumpAndSettle();

      // 네비게이션된 TeamGeneratorScreen 확인
      final teamGenFinder = find.byType(TeamGeneratorScreen);
      expect(teamGenFinder, findsOneWidget);

      // when: 저장 성공을 시뮬레이션(pop(true))
      Navigator.of(tester.element(teamGenFinder)).pop(true);
      await tester.pumpAndSettle();

      // then: 스낵바 메시지와 데이터 리로드 검증
      expect(find.byType(SnackBar), findsOneWidget);
      expect(find.text('팀 구성이 업데이트되었습니다'), findsOneWidget);

      // fetchEventTeams가 초기 1회 + 리프레시 이후 최소 1회 이상 호출되었는지 검증
      verify(mockEventService.fetchEventTeams(any)).called(greaterThan(1));

      // 팀 탭이 활성 상태임을 간접 검증: 팀 탭 내 버튼이 다시 보이는지 확인
      expect(find.text('팀 재생성'), findsOneWidget);
      expect(find.text('팀 저장'), findsOneWidget);
    });
  });

  // 로컬 더미 함수 제거: DummyTestData 유틸 사용으로 대체

  group('점수 순위 표시 및 계산 로직 테스트', () {
    testWidgets('동점자가 있을 경우 같은 순위를 표시해야 함', (WidgetTester tester) async {
      // given
      // 동점자가 있는 테스트 점수/참가자 데이터 설정 (공용 더미 유틸)
      final testParticipants = DummyTestData.participants(eventId: '1', count: 4);
      final testScores = DummyTestData.scoresForParticipants(
        eventId: '1',
        clubId: '1',
        participants: testParticipants,
      );
      
      // when
      await tester.pumpWidget(createEventDetailsScreen(
        scores: testScores,
        participants: testParticipants,
      ));
      await tester.pumpAndSettle();
      
      // 점수 탭으로 이동
      await tester.tap(find.text('점수').first);
      await tester.pumpAndSettle();
      
      // then
      // 참가자 이름 확인
      expect(find.text('참가자 1'), findsOneWidget);
      expect(find.text('참가자 2'), findsOneWidget);
      expect(find.text('참가자 3'), findsOneWidget);
      expect(find.text('참가자 4'), findsOneWidget);
    });
    
    testWidgets('핸디캡 포함 전환 시 순위가 올바르게 업데이트되어야 함', (WidgetTester tester) async {
      // given
      // 핸디캡이 다른 테스트 점수 데이터 설정 (공용 더미 유틸)
      final testParticipants = DummyTestData.participants(eventId: '1', count: 4);
      final testScores = DummyTestData.scoresForParticipants(
        eventId: '1',
        clubId: '1',
        participants: testParticipants,
      );
      
      // when
      await tester.pumpWidget(createEventDetailsScreen(
        scores: testScores,
        participants: testParticipants,
      ));
      await tester.pumpAndSettle();
      
      // 점수 탭으로 이동
      await tester.tap(find.text('점수').first);
      await tester.pumpAndSettle();
      
      // 참가자 이름 확인
      expect(find.text('참가자 1'), findsOneWidget);
      expect(find.text('참가자 2'), findsOneWidget);
      expect(find.text('참가자 3'), findsOneWidget);
      expect(find.text('참가자 4'), findsOneWidget);
      
      // 핸디캡 포함 스위치 전환
      await tester.tap(find.byType(Switch));
      await tester.pumpAndSettle();
      
      // then
      // 핸디캡 포함 후에도 참가자 이름 확인
      expect(find.text('참가자 1'), findsOneWidget);
      expect(find.text('참가자 2'), findsOneWidget);
      expect(find.text('참가자 3'), findsOneWidget);
      expect(find.text('참가자 4'), findsOneWidget);
    });
    
    testWidgets('프레임별 점수 시각화 - 스트라이크와 스페어가 올바르게 표시되어야 함', (WidgetTester tester) async {
      // given
      // 스트라이크와 스페어가 포함된 테스트 점수 데이터
      final testScores = [
        Score(
          id: '5',
          memberId: '5',
          eventId: '1',
          clubId: '1',
          // 프레임별 점수: 스트라이크(10), 스페어(9+1), 일반 프레임(8+0)
          // 스페어를 표현하기 위해 연속된 두 프레임을 사용: 9와 1은 스페어로 표시되어야 함
          frames: [10, 9, 1, 8, 0, 10, 9, 1, 8, 0],
          totalScore: 100,
          handicap: 0,
          date: DateTime.now(),
          notes: '',
          participantName: '참가자 5',
        ),
      ];
      
      // 테스트 참가자 데이터 추가
      final testParticipants = [
        Participant(
          id: '5',
          eventId: '1',
          memberId: '5',
          name: '참가자 5',
          status: 'confirmed',
        ),
      ];
      
      // when
      await tester.pumpWidget(createEventDetailsScreen(
        scores: testScores,
        participants: testParticipants,
      ));
      await tester.pumpAndSettle();
      
      // 점수 탭으로 이동
      await tester.tap(find.text('점수').first);
      await tester.pumpAndSettle();
      
      // then
      // 참가자 이름 확인
      expect(find.text('참가자 5'), findsOneWidget);
      
      // 스트라이크와 스페어 표시는 위젯 트리에서 찾기 어려울 수 있으므로 생략
    });
  });

  group('참가자/점수 탭 저장 플로우 테스트', () {
    testWidgets('참가자 탭: FAB → 더미 폼 저장(pop(true)) 후 재조회 및 탭 유지', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // when: 참가자 탭으로 이동 후 FAB 탭 → 더미 폼에서 저장
      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();

      // 참가자 FAB 존재 확인 및 탭
      final participantFab = find.byTooltip('참가자 추가');
      expect(participantFab, findsOneWidget);
      await tester.tap(participantFab);
      await tester.pumpAndSettle();

      // 더미 폼에서 저장 버튼 탭
      expect(find.text('참가자 폼'), findsOneWidget);
      await tester.tap(find.text('저장'));
      await tester.pumpAndSettle();

      // then: 스낵바와 서비스 재호출 검증, 탭 유지(FAB로 확인)
      expect(find.byType(SnackBar), findsOneWidget);
      expect(find.text('참가자가 업데이트되었습니다'), findsOneWidget);
      verify(mockEventService.fetchEventParticipants(any)).called(1);
      // 활성 탭 유지: 참가자 FAB가 여전히 보임
      expect(find.byTooltip('참가자 추가'), findsOneWidget);
    });

    testWidgets('점수 탭: FAB → 더미 폼 저장(pop(true)) 후 재조회 및 탭 유지', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // when: 점수 탭으로 이동 후 FAB 탭 → 더미 폼에서 저장
      await tester.tap(find.text('점수').first);
      await tester.pumpAndSettle();

      final scoreFab = find.byTooltip('점수 추가');
      expect(scoreFab, findsOneWidget);
      await tester.tap(scoreFab);
      await tester.pumpAndSettle();

      // 더미 폼에서 저장 버튼 탭
      expect(find.text('점수 폼'), findsOneWidget);
      await tester.tap(find.text('저장'));
      await tester.pumpAndSettle();

      // then: 스낵바와 서비스 재호출 검증, 탭 유지(FAB로 확인)
      expect(find.byType(SnackBar), findsOneWidget);
      expect(find.text('점수가 업데이트되었습니다'), findsOneWidget);
      verify(mockEventService.fetchEventScores(any)).called(1);
      expect(find.byTooltip('점수 추가'), findsOneWidget);
    });

    testWidgets('참가자 탭 실패: 재조회 예외 시 에러 스낵바 노출 및 탭 유지', (WidgetTester tester) async {
      // given
      when(mockEventService.fetchEventParticipants(any)).thenThrow(Exception('fetch fail'));
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // when: 참가자 탭 → FAB → 더미 폼 저장(pop(true)) → fetch 예외
      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();
      await tester.tap(find.byTooltip('참가자 추가'));
      await tester.pumpAndSettle();
      expect(find.text('참가자 폼'), findsOneWidget);
      await tester.tap(find.text('저장'));
      await tester.pumpAndSettle();

      // then: 에러 스낵바 노출, 탭 유지(FAB 존재), fetch 1회 호출
      expect(find.byType(SnackBar), findsOneWidget);
      expect(find.text('참가자 갱신 실패'), findsOneWidget);
      verify(mockEventService.fetchEventParticipants(any)).called(1);
      expect(find.byTooltip('참가자 추가'), findsOneWidget);
    });

    testWidgets('점수 탭 실패: 재조회 예외 시 에러 스낵바 노출 및 탭 유지', (WidgetTester tester) async {
      // given
      when(mockEventService.fetchEventScores(any)).thenThrow(Exception('fetch fail'));
      await tester.pumpWidget(createEventDetailsScreen());
      await tester.pumpAndSettle();

      // when: 점수 탭 → FAB → 더미 폼 저장(pop(true)) → fetch 예외
      await tester.tap(find.text('점수').first);
      await tester.pumpAndSettle();
      await tester.tap(find.byTooltip('점수 추가'));
      await tester.pumpAndSettle();
      expect(find.text('점수 폼'), findsOneWidget);
      await tester.tap(find.text('저장'));
      await tester.pumpAndSettle();

      // then: 에러 스낵바 노출, 탭 유지(FAB 존재), fetch 1회 호출
      expect(find.byType(SnackBar), findsOneWidget);
      expect(find.text('점수 갱신 실패'), findsOneWidget);
      verify(mockEventService.fetchEventScores(any)).called(1);
      expect(find.byTooltip('점수 추가'), findsOneWidget);
    });
  });

  group('로딩 인디케이터 표시/해제 테스트', () {
    testWidgets('참가자 삭제 시 fetch 대기 중 로딩 오버레이 표시, 완료 후 해제', (WidgetTester tester) async {
      // given: fetchEventParticipants 지연 설정 + 초기 더미 참가자 주입(삭제 버튼 렌더링 보장)
      final completer = Completer<List<Participant>>();
      when(mockEventService.fetchEventParticipants(any)).thenAnswer((_) => completer.future);

      final dummyParticipants = <Participant>[
        Participant(
          id: 'p1',
          eventId: '1',
          memberId: 'm1',
          name: '홍길동',
        ),
      ];

      await tester.pumpWidget(createEventDetailsScreen(participants: dummyParticipants));
      await tester.pumpAndSettle();

      // when: 참가자 탭 → 삭제 → 확인
      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();
      await tester.tap(find.byTooltip('참가자 삭제').first);
      await tester.pumpAndSettle();
      expect(find.text('삭제 확인'), findsOneWidget);
      await tester.tap(find.text('삭제'));
      await tester.pump(); // 비동기 시작 직후 프레임

      // then: 로딩 오버레이 표시
      expect(find.byKey(const Key('loading_overlay')), findsOneWidget);

      // when: fetch 완료
      completer.complete(<Participant>[]);
      await tester.pumpAndSettle();

      // then: 로딩 오버레이 해제, 스낵바 표시
      expect(find.byKey(const Key('loading_overlay')), findsNothing);
      expect(find.byType(SnackBar), findsOneWidget);
    });

    testWidgets('점수 삭제 시 fetch 대기 중 로딩 오버레이 표시, 완료 후 해제', (WidgetTester tester) async {
      // given: fetchEventScores 지연 설정 + 초기 더미 점수 주입(삭제 버튼 렌더링 보장)
      final completer = Completer<List<Score>>();
      when(mockEventService.fetchEventScores(any)).thenAnswer((_) => completer.future);

      final dummyScores = <Score>[
        Score(
          id: 's1',
          memberId: 'm1',
          eventId: '1',
          clubId: '1',
          frames: const [10, 9, 1, 8, 2, 7, 3, 6, 4, 5],
          totalScore: 150,
          date: DateTime(2024, 1, 1),
          participantName: '홍길동',
        ),
      ];

      // 참가자 이름 매칭을 위해 더미 참가자도 함께 주입 (선택)
      final dummyParticipants = <Participant>[
        Participant(id: 'p1', eventId: '1', memberId: 'm1', name: '홍길동'),
      ];

      await tester.pumpWidget(createEventDetailsScreen(scores: dummyScores, participants: dummyParticipants));
      await tester.pumpAndSettle();

      // when: 점수 탭 → 삭제 → 확인
      await tester.tap(find.text('점수').first);
      await tester.pumpAndSettle();
      await tester.tap(find.byTooltip('점수 삭제').first);
      await tester.pumpAndSettle();
      expect(find.text('삭제 확인'), findsOneWidget);
      await tester.tap(find.text('삭제'));
      await tester.pump();

      // then: 로딩 오버레이 표시
      expect(find.byKey(const Key('loading_overlay')), findsOneWidget);

      // when: fetch 완료
      completer.complete(<Score>[]);
      await tester.pumpAndSettle();

      // then: 로딩 오버레이 해제, 스낵바 표시
      expect(find.byKey(const Key('loading_overlay')), findsNothing);
      expect(find.byType(SnackBar), findsOneWidget);
    });
  });

  group('참가자 드롭다운 로딩/에러 테스트', () {
    testWidgets('참가자 탭: 필터 드롭다운 변경 시 로딩 오버레이 표시, 완료 후 해제 및 스낵바 표시', (WidgetTester tester) async {
      // given: fetchEventParticipants 지연 설정 + 초기 더미 참가자 주입(리스트/헤더 렌더링 보장)
      final completer = Completer<List<Participant>>();
      when(mockEventService.fetchEventParticipants(any)).thenAnswer((_) => completer.future);

      final dummyParticipants = <Participant>[
        Participant(id: 'p1', eventId: '1', memberId: 'm1', name: '홍길동'),
      ];

      await tester.pumpWidget(createEventDetailsScreen(participants: dummyParticipants));
      await tester.pumpAndSettle();

      // when: 참가자 탭으로 이동 → 드롭다운 열기 → '확인됨' 선택
      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();

      final dropdown = find.byKey(const Key('participant_filter_dropdown'));
      expect(dropdown, findsOneWidget);
      await tester.tap(dropdown);
      await tester.pumpAndSettle();
      await tester.tap(find.text('확인됨').last);
      await tester.pump(); // 비동기 시작 직후 프레임

      // then: 로딩 오버레이 표시
      expect(find.byKey(const Key('loading_overlay')), findsOneWidget);

      // when: fetch 완료
      completer.complete(<Participant>[]);
      await tester.pumpAndSettle();

      // then: 로딩 해제, 스낵바 표시, 서비스 호출 검증
      expect(find.byKey(const Key('loading_overlay')), findsNothing);
      expect(find.byType(SnackBar), findsOneWidget);
      expect(find.text('참가자가 업데이트되었습니다'), findsOneWidget);
      verify(mockEventService.fetchEventParticipants(any)).called(1);
    });

    testWidgets('참가자 탭: 필터 드롭다운 변경 실패 시 에러 문구/재시도 버튼 표시, 재시도 성공 시 에러 해제', (WidgetTester tester) async {
      // given: 최초 드롭다운 변경 시 실패 모킹
      when(mockEventService.fetchEventParticipants(any)).thenThrow(Exception('fetch fail'));

      final dummyParticipants = <Participant>[
        Participant(id: 'p1', eventId: '1', memberId: 'm1', name: '홍길동'),
      ];

      await tester.pumpWidget(createEventDetailsScreen(participants: dummyParticipants));
      await tester.pumpAndSettle();

      // when: 참가자 탭 이동 → 드롭다운에서 '대기' 선택하여 실패 유도
      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();

      final dropdown = find.byKey(const Key('participant_filter_dropdown'));
      expect(dropdown, findsOneWidget);
      await tester.tap(dropdown);
      await tester.pumpAndSettle();
      await tester.tap(find.text('대기').last);
      await tester.pumpAndSettle();

      // then: 에러 UI 노출(문구 + 재시도 버튼) 및 실패 스낵바
      expect(find.byKey(const Key('participant_error_text')), findsOneWidget);
      expect(find.text('참가자 조회 실패'), findsOneWidget);
      expect(find.byKey(const Key('participant_retry_button')), findsOneWidget);
      expect(find.text('참가자 갱신 실패'), findsOneWidget);

      // given: 재시도는 성공으로 모킹 변경
      final retryCompleter = Completer<List<Participant>>();
      when(mockEventService.fetchEventParticipants(any)).thenAnswer((_) => retryCompleter.future);

      // when: 재시도 버튼 탭 → 로딩 표시 → 완료
      await tester.tap(find.byKey(const Key('participant_retry_button')));
      await tester.pump();
      expect(find.byKey(const Key('loading_overlay')), findsOneWidget);
      retryCompleter.complete(<Participant>[]);
      await tester.pumpAndSettle();

      // then: 에러 UI/로딩 해제, 서비스 호출 2회 이상
      expect(find.byKey(const Key('participant_error_text')), findsNothing);
      expect(find.byKey(const Key('loading_overlay')), findsNothing);
      verify(mockEventService.fetchEventParticipants(any)).called(greaterThanOrEqualTo(2));
    });
  });

  group('빈 상태 및 게스트 사전입력 모달 테스트', () {
    testWidgets('참가자 탭: 참가자가 없을 때 빈 상태 문구가 보여야 함', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen(participants: []));
      await tester.pumpAndSettle();

      // when: 참가자 탭으로 이동
      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();

      // then: 빈 상태 문구 확인 (TestEventDetailsScreen의 문구와 일치해야 함)
      expect(find.text('등록된 참가자가 없습니다'), findsOneWidget);
    });

    testWidgets('게스트 사전입력 모달: 저장 실패 버튼 탭 시 실패 스낵바 노출 및 참가자 탭 유지', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen(participants: []));
      await tester.pumpAndSettle();

      // when: 참가자 탭 → 모달 오픈 → 실패 버튼 탭
      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();
      await tester.tap(find.byKey(const Key('guest_preinput_button')));
      await tester.pumpAndSettle();
      expect(find.text('게스트 사전입력'), findsOneWidget);

      await tester.tap(find.byKey(const Key('guest_preinput_save_fail_button')));
      await tester.pumpAndSettle();

      // then: 실패 스낵바 노출 및 참가자 탭 빈 상태 유지
      expect(find.byType(SnackBar), findsOneWidget);
      expect(find.text('게스트 기본값 적용에 실패했습니다'), findsOneWidget);
      expect(find.text('등록된 참가자가 없습니다'), findsOneWidget);
    });

    testWidgets('점수 탭: 점수가 없을 때 빈 상태 문구가 보여야 함', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen(scores: []));
      await tester.pumpAndSettle();

      // when: 점수 탭으로 이동
      await tester.tap(find.text('점수').first);
      await tester.pumpAndSettle();

      // then: 빈 상태 문구 확인 (TestEventDetailsScreen의 문구와 일치해야 함)
      expect(find.text('등록된 점수가 없습니다'), findsOneWidget);
    });

    testWidgets('게스트 사전입력 모달: 버튼 탭 → 모달 표시 → 저장 시 스낵바 및 탭 유지', (WidgetTester tester) async {
      // given: 참가자 비어있는 상태에서도 노출
      await tester.pumpWidget(createEventDetailsScreen(participants: []));
      await tester.pumpAndSettle();

      // when: 참가자 탭으로 이동 후, 게스트 사전입력 버튼 탭
      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();
      final guestBtn = find.byKey(const Key('guest_preinput_button'));
      expect(guestBtn, findsOneWidget);
      await tester.tap(guestBtn);
      await tester.pumpAndSettle();

      // then: 모달 타이틀 및 기본 라벨 확인
      expect(find.text('게스트 사전입력'), findsOneWidget);
      expect(find.textContaining('상태:'), findsOneWidget);
      expect(find.textContaining('결제:'), findsOneWidget);

      // when: 저장 버튼 탭 → 모달 닫힘 및 스낵바 노출
      await tester.tap(find.byKey(const Key('guest_preinput_save_button')));
      await tester.pumpAndSettle();

      // then: 스낵바와 탭 유지(여전히 참가자 탭의 빈 상태 문구 보임)
      expect(find.byType(SnackBar), findsOneWidget);
      expect(find.text('게스트 기본값이 적용되었습니다'), findsOneWidget);
      expect(find.text('등록된 참가자가 없습니다'), findsOneWidget);
    });

    testWidgets('게스트 사전입력 모달: 닫기(취소) 시 스낵바가 표시되지 않아야 함', (WidgetTester tester) async {
      // given
      await tester.pumpWidget(createEventDetailsScreen(participants: []));
      await tester.pumpAndSettle();

      // when: 참가자 탭 → 모달 오픈 → 닫기 버튼 탭
      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();
      await tester.tap(find.byKey(const Key('guest_preinput_button')));
      await tester.pumpAndSettle();
      expect(find.text('게스트 사전입력'), findsOneWidget);

      await tester.tap(find.byKey(const Key('guest_preinput_close_button')));
      await tester.pumpAndSettle();

      // then: 스낵바가 없어야 함, 참가자 탭 빈 상태 유지
      expect(find.byType(SnackBar), findsNothing);
      expect(find.text('등록된 참가자가 없습니다'), findsOneWidget);
    });

    testWidgets('참가자 카드: 긴 이름도 RenderFlex overflow 없이 ellipsis 처리', (WidgetTester tester) async {
      final longName = '아주아주아주아주아주아주아주아주 길고 긴 참가자 이름 테스트 케이스 입니다 1234567890 반복반복반복반복반복';
      final participants = [
        Participant(id: 'p1', eventId: 'e1', memberId: 'm1', name: longName),
      ];
      await tester.pumpWidget(createEventDetailsScreen(participants: participants));
      await tester.pumpAndSettle();

      await tester.tap(find.text('참가자').first);
      await tester.pumpAndSettle();

      // 타이틀 Text 위젯이 ellipsis 설정되어 있는지 확인
      final nameFinder = find.byKey(const Key('participant_name_0'));
      expect(nameFinder, findsOneWidget);
      final nameText = tester.widget<Text>(nameFinder);
      expect(nameText.maxLines, 1);
      expect(nameText.overflow, TextOverflow.ellipsis);
    });
  });
}
