import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest.dart' as tz_data;
import 'package:shared_preferences/shared_preferences.dart';

import 'package:lanebow/services/event_service.dart';
import 'package:lanebow/models/event_model.dart';
import 'package:lanebow/services/api_service.dart';
import 'package:lanebow/config/api_config.dart';
import 'package:lanebow/services/event_bus.dart';
import 'package:lanebow/utils/test_utils.dart';

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

void main() {
  // Flutter 위젯 테스트 바인딩 초기화
  TestWidgetsFlutterBinding.ensureInitialized();
  
  // SharedPreferences 모킹 설정
  SharedPreferences.setMockInitialValues({
    ApiConfig.clubIdKey: 'test_club_id',
    ApiConfig.tokenKey: 'test_token'
  });
  
  late TestNotificationService notificationService;
  late EventService eventService;
  late MockApiService mockApiService;
  late MockFlutterLocalNotificationsPlugin mockNotificationsPlugin;

  // 테스트 전 설정
  setUp(() {
    // 테스트 격리를 위한 설정
    final testId = 'notification_service_integration_test_${DateTime.now().millisecondsSinceEpoch}';
    TestUtils.setTestMode(true);
    EventBus.setCurrentTestId(testId);
    
    print('EventBus: 초기화됨 (테스트 ID: $testId)');
    
    // 모킹된 API 서비스 및 알림 플러그인 생성
    mockApiService = MockApiService();
    mockNotificationsPlugin = MockFlutterLocalNotificationsPlugin();
    
    // 테스트용 NotificationService 클래스 생성
    notificationService = TestNotificationService(mockNotificationsPlugin);
    
    // EventService 생성 및 모킹된 API 서비스 주입
    eventService = EventService.forTest(mockApiService);
    eventService.setClubId('test_club_id');
    
    // 타임존 초기화
    tz_data.initializeTimeZones();
    
    // API 서비스 토큰 설정
    when(mockApiService.setToken(any)).thenReturn(null);
    
    // 알림 플러그인 초기화 모킹
    when(mockNotificationsPlugin.initialize(
      any,
      onDidReceiveNotificationResponse: anyNamed('onDidReceiveNotificationResponse'),
      onDidReceiveBackgroundNotificationResponse: anyNamed('onDidReceiveBackgroundNotificationResponse'),
    )).thenAnswer((_) async => true);
  });

  group('NotificationService 통합 테스트', () {
    test('이벤트 생성 시 알림이 설정되어야 함', () async {
      // given
      const clubId = 'test_club_id';
      const token = 'test_token';
      
      // 토큰과 클럽 ID 설정
      eventService.setClubId(clubId);
      when(mockApiService.setToken(token)).thenReturn(null);
      await eventService.initialize(token);
      
      final event = Event(
        id: 'test_event_id',
        clubId: 'test_club_id',
        title: '테스트 이벤트',
        description: '테스트 설명',
        startDate: DateTime.now().add(const Duration(hours: 3)),
        endDate: DateTime.now().add(const Duration(hours: 5)),
        location: '테스트 장소',
        registrationDeadline: DateTime.now().add(const Duration(days: 2)),
        maxParticipants: 10,
        isActive: true,
      );

      // API 응답 모킹
      when(mockApiService.post('${ApiConfig.clubs}/events', data: anyNamed('data'))).thenAnswer((_) async => {
        'event': {
          'id': event.id,
          'clubId': event.clubId,
          'title': event.title,
          'startDate': event.startDate.toIso8601String(),
          'isActive': event.isActive,
        }
      });

      // 알림 설정 모킹
      when(mockNotificationsPlugin.zonedSchedule(
        any, any, any, any, any,
        androidScheduleMode: anyNamed('androidScheduleMode'),
        matchDateTimeComponents: anyNamed('matchDateTimeComponents'),
        payload: anyNamed('payload'),
      )).thenAnswer((_) async {});

      // when
      // 이벤트 생성 및 알림 설정
      await eventService.createEvent(event.toJson());
      await notificationService.scheduleEventStartReminder(event);
      await notificationService.scheduleRegistrationDeadlineReminder(event);

      // then
      // 이벤트 시작 알림 설정 확인
      verify(mockNotificationsPlugin.zonedSchedule(
        event.id.hashCode, '이벤트 시작 알림', '${event.title} 이벤트가 1시간 후에 시작됩니다.', any, any,
        androidScheduleMode: anyNamed('androidScheduleMode'),
        matchDateTimeComponents: anyNamed('matchDateTimeComponents'),
        payload: event.id,
      )).called(1);

      // 등록 마감 알림 설정 확인
      verify(mockNotificationsPlugin.zonedSchedule(
        event.id.hashCode + 1, '이벤트 등록 마감 알림', '${event.title} 이벤트의 등록이 내일 마감됩니다.', any, any,
        androidScheduleMode: anyNamed('androidScheduleMode'),
        matchDateTimeComponents: anyNamed('matchDateTimeComponents'),
        payload: event.id,
      )).called(1);
    });

    test('이벤트 수정 시 알림이 업데이트되어야 함', () async {
      // given
      const clubId = 'test_club_id';
      const token = 'test_token';
      
      // 토큰과 클럽 ID 설정
      eventService.setClubId(clubId);
      when(mockApiService.setToken(token)).thenReturn(null);
      await eventService.initialize(token);
      
      final originalEvent = Event(
        id: 'test_event_id',
        clubId: 'test_club_id',
        title: '원래 이벤트',
        startDate: DateTime.now().add(const Duration(hours: 3)),
        registrationDeadline: DateTime.now().add(const Duration(days: 2)),
        isActive: true,
      );

      final updatedEvent = Event(
        id: 'test_event_id',
        clubId: 'test_club_id',
        title: '수정된 이벤트',
        startDate: DateTime.now().add(const Duration(hours: 5)), // 변경된 시작 시간
        registrationDeadline: DateTime.now().add(const Duration(days: 3)), // 변경된 마감 시간
        isActive: true,
      );

      // API 응답 모킹
      when(mockApiService.put('${ApiConfig.clubs}/events/${originalEvent.id}', data: anyNamed('data'))).thenAnswer((_) async => {
        'event': {
        'id': updatedEvent.id,
        'clubId': updatedEvent.clubId,
        'title': updatedEvent.title,
        'startDate': updatedEvent.startDate.toIso8601String(),
        'registrationDeadline': updatedEvent.registrationDeadline?.toIso8601String(),
        'isActive': updatedEvent.isActive,
        }
      });

      // 알림 설정 모킹
      when(mockNotificationsPlugin.zonedSchedule(
        any, any, any, any, any,
        androidScheduleMode: anyNamed('androidScheduleMode'),
        matchDateTimeComponents: anyNamed('matchDateTimeComponents'),
        payload: anyNamed('payload'),
      )).thenAnswer((_) async {});

      // 알림 취소 모킹
      when(mockNotificationsPlugin.cancel(any)).thenAnswer((_) async {});

      // when
      // 이벤트 수정
      await eventService.updateEvent(updatedEvent.id, updatedEvent.toJson());
      
      // 기존 알림 취소 후 새 알림 설정
      await notificationService.cancelEventNotifications(updatedEvent.id);
      await notificationService.scheduleEventStartReminder(updatedEvent);
      await notificationService.scheduleRegistrationDeadlineReminder(updatedEvent);

      // then
      // 기존 알림 취소 확인
      verify(mockNotificationsPlugin.cancel(updatedEvent.id.hashCode)).called(1);
      verify(mockNotificationsPlugin.cancel(updatedEvent.id.hashCode + 1)).called(1);

      // 새 알림 설정 확인
      verify(mockNotificationsPlugin.zonedSchedule(
        updatedEvent.id.hashCode, '이벤트 시작 알림', '${updatedEvent.title} 이벤트가 1시간 후에 시작됩니다.', any, any,
        androidScheduleMode: anyNamed('androidScheduleMode'),
        matchDateTimeComponents: anyNamed('matchDateTimeComponents'),
        payload: updatedEvent.id,
      )).called(1);

      verify(mockNotificationsPlugin.zonedSchedule(
        updatedEvent.id.hashCode + 1, '이벤트 등록 마감 알림', '${updatedEvent.title} 이벤트의 등록이 내일 마감됩니다.', any, any,
        androidScheduleMode: anyNamed('androidScheduleMode'),
        matchDateTimeComponents: anyNamed('matchDateTimeComponents'),
        payload: updatedEvent.id,
      )).called(1);
    });

    test('이벤트 삭제 시 알림이 취소되어야 함', () async {
      // given
      const eventId = 'test_event_id';
      const clubId = 'test_club_id';
      const token = 'test_token';
      
      // 토큰과 클럽 ID 설정
      eventService.setClubId(clubId);
      when(mockApiService.setToken(token)).thenReturn(null);
      await eventService.initialize(token);
      
      // API 응답 모킹
      when(mockApiService.delete('${ApiConfig.clubs}/events/$eventId')).thenAnswer((_) async => {'success': true});
      
      // 알림 취소 모킹
      when(mockNotificationsPlugin.cancel(any)).thenAnswer((_) async {});

      // when
      // 이벤트 삭제 및 알림 취소
      await eventService.deleteEvent(eventId);
      await notificationService.cancelEventNotifications(eventId);

      // then
      // 알림 취소 확인
      verify(mockNotificationsPlugin.cancel(eventId.hashCode)).called(1);
      verify(mockNotificationsPlugin.cancel(eventId.hashCode + 1)).called(1);
    });

    test('앱 시작 시 알림 서비스가 초기화되어야 함', () async {
      // given
      when(mockNotificationsPlugin.initialize(any, onDidReceiveNotificationResponse: anyNamed('onDidReceiveNotificationResponse')))
          .thenAnswer((_) async => true);

      // when
      // 앱 시작 시 알림 서비스 초기화
      await notificationService.initialize();

      // then
      // 알림 초기화 확인
      verify(mockNotificationsPlugin.initialize(any, onDidReceiveNotificationResponse: anyNamed('onDidReceiveNotificationResponse'))).called(1);
    });

    test('여러 이벤트에 대한 알림이 올바르게 설정되어야 함', () async {
      // given
      const clubId = 'test_club_id';
      const token = 'test_token';
      
      // 토큰과 클럽 ID 설정
      eventService.setClubId(clubId);
      when(mockApiService.setToken(token)).thenReturn(null);
      await eventService.initialize(token);
      
      final events = [
        Event(
          id: 'event_id_1',
          clubId: 'test_club_id',
          title: '이벤트 1',
          startDate: DateTime.now().add(const Duration(hours: 3)),
          registrationDeadline: DateTime.now().add(const Duration(days: 2)),
          isActive: true,
        ),
        Event(
          id: 'event_id_2',
          clubId: 'test_club_id',
          title: '이벤트 2',
          startDate: DateTime.now().add(const Duration(hours: 5)),
          registrationDeadline: DateTime.now().add(const Duration(days: 3)),
          isActive: true,
        ),
      ];

      // API 응답 모킹
      when(mockApiService.post('${ApiConfig.clubs}/events', data: anyNamed('data'))).thenAnswer((_) async => {
        'events': events.map((e) => {
          'id': e.id,
          'clubId': e.clubId,
          'title': e.title,
          'description': e.description,
          'startDate': e.startDate.toIso8601String(),
          'endDate': e.endDate?.toIso8601String(),
          'location': e.location,
          'registrationDeadline': e.registrationDeadline?.toIso8601String(),
          'maxParticipants': e.maxParticipants,
          'isActive': e.isActive,
        }).toList(),
      });

      // 알림 설정 모킹
      when(mockNotificationsPlugin.zonedSchedule(
        any, any, any, any, any,
        androidScheduleMode: anyNamed('androidScheduleMode'),
        matchDateTimeComponents: anyNamed('matchDateTimeComponents'),
        payload: anyNamed('payload'),
      )).thenAnswer((_) async {});

      // when
      // 클럽의 모든 이벤트 조회
      await eventService.fetchClubEvents();
      final fetchedEvents = eventService.events;
      
      // 각 이벤트에 대한 알림 설정
      for (final event in fetchedEvents) {
        await notificationService.scheduleEventStartReminder(event);
        await notificationService.scheduleRegistrationDeadlineReminder(event);
      }

      // then
      // 각 이벤트에 대한 알림 설정 확인
      for (final event in events) {
        verify(mockNotificationsPlugin.zonedSchedule(
          event.id.hashCode, '이벤트 시작 알림', '${event.title} 이벤트가 1시간 후에 시작됩니다.', any, any,
          androidScheduleMode: anyNamed('androidScheduleMode'),
          matchDateTimeComponents: anyNamed('matchDateTimeComponents'),
          payload: event.id,
        )).called(1);

        verify(mockNotificationsPlugin.zonedSchedule(
          event.id.hashCode + 1, '이벤트 등록 마감 알림', '${event.title} 이벤트의 등록이 내일 마감됩니다.', any, any,
          androidScheduleMode: anyNamed('androidScheduleMode'),
          matchDateTimeComponents: anyNamed('matchDateTimeComponents'),
          payload: event.id,
        )).called(1);
      }
    });
  });
}

// 테스트를 위한 NotificationService 클래스
class TestNotificationService {
  final FlutterLocalNotificationsPlugin mockNotificationsPlugin;
  bool _isInitialized = false;

  TestNotificationService(this.mockNotificationsPlugin);

  // 알림 서비스 초기화
  Future<void> initialize() async {
    if (_isInitialized) return;

    // 타임존 초기화
    tz_data.initializeTimeZones();

    // 안드로이드 설정
    const AndroidInitializationSettings androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
    
    // iOS 설정
    const DarwinInitializationSettings iOSSettings = DarwinInitializationSettings(
      requestAlertPermission: true,
      requestBadgePermission: true,
      requestSoundPermission: true,
    );
    
    // 초기화 설정
    const InitializationSettings initSettings = InitializationSettings(
      android: androidSettings,
      iOS: iOSSettings,
    );
    
    // 알림 플러그인 초기화
    await mockNotificationsPlugin.initialize(
      initSettings,
      onDidReceiveNotificationResponse: (NotificationResponse response) {
        // 알림 클릭 시 처리
        debugPrint('알림 클릭: ${response.payload}');
      },
    );

    _isInitialized = true;
  }

  // 권한 요청
  Future<bool> requestPermission() async {
    if (!_isInitialized) await initialize();
    
    // iOS에서 권한 요청
    final bool? result = await mockNotificationsPlugin
        .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
        ?.requestPermissions(
          alert: true,
          badge: true,
          sound: true,
        );
    
    return result ?? false;
  }

  // 즉시 알림 표시
  Future<void> showNotification({
    required int id,
    required String title,
    required String body,
    String? payload,
  }) async {
    if (!_isInitialized) await initialize();

    const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
      'event_channel',
      '이벤트 알림',
      channelDescription: '이벤트 관련 알림을 표시합니다',
      importance: Importance.high,
      priority: Priority.high,
      showWhen: true,
    );

    const DarwinNotificationDetails iOSDetails = DarwinNotificationDetails(
      presentAlert: true,
      presentBadge: true,
      presentSound: true,
    );

    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidDetails,
      iOS: iOSDetails,
    );

    await mockNotificationsPlugin.show(
      id,
      title,
      body,
      notificationDetails,
      payload: payload,
    );
  }

  // 예약 알림 설정
  Future<void> scheduleNotification({
    required int id,
    required String title,
    required String body,
    required DateTime scheduledDate,
    String? payload,
  }) async {
    if (!_isInitialized) await initialize();

    final tz.TZDateTime scheduledTZDate = tz.TZDateTime.from(
      scheduledDate,
      tz.local,
    );

    const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
      'event_reminder_channel',
      '이벤트 리마인더',
      channelDescription: '예정된 이벤트에 대한 알림을 표시합니다',
      importance: Importance.high,
      priority: Priority.high,
    );

    const DarwinNotificationDetails iOSDetails = DarwinNotificationDetails(
      presentAlert: true,
      presentBadge: true,
      presentSound: true,
    );

    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidDetails,
      iOS: iOSDetails,
    );

    await mockNotificationsPlugin.zonedSchedule(
      id,
      title,
      body,
      scheduledTZDate,
      notificationDetails,
      androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
      matchDateTimeComponents: DateTimeComponents.time,
      payload: payload,
    );
  }

  // 이벤트 시작 알림 설정
  Future<void> scheduleEventStartReminder(Event event) async {
    if (event.id.isEmpty) return;
    
    // 이벤트 시작 1시간 전 알림
    final DateTime reminderTime = event.startDate.subtract(const Duration(hours: 1));
    
    // 현재 시간이 알림 시간보다 이후라면 알림을 설정하지 않음
    if (reminderTime.isBefore(DateTime.now())) return;
    
    await scheduleNotification(
      id: event.id.hashCode,
      title: '이벤트 시작 알림',
      body: '${event.title} 이벤트가 1시간 후에 시작됩니다.',
      scheduledDate: reminderTime,
      payload: event.id,
    );
  }

  // 이벤트 등록 마감 알림 설정
  Future<void> scheduleRegistrationDeadlineReminder(Event event) async {
    if (event.id.isEmpty || event.registrationDeadline == null) return;
    
    // 등록 마감 1일 전 알림
    final DateTime reminderTime = event.registrationDeadline!.subtract(const Duration(days: 1));
    
    // 현재 시간이 알림 시간보다 이후라면 알림을 설정하지 않음
    if (reminderTime.isBefore(DateTime.now())) return;
    
    await scheduleNotification(
      id: event.id.hashCode + 1, // 이벤트 시작 알림과 ID가 겹치지 않도록 함
      title: '이벤트 등록 마감 알림',
      body: '${event.title} 이벤트의 등록이 내일 마감됩니다.',
      scheduledDate: reminderTime,
      payload: event.id,
    );
  }

  // 특정 이벤트의 모든 알림 취소
  Future<void> cancelEventNotifications(String eventId) async {
    await mockNotificationsPlugin.cancel(eventId.hashCode);
    await mockNotificationsPlugin.cancel(eventId.hashCode + 1);
  }

  // 모든 알림 취소
  Future<void> cancelAllNotifications() async {
    await mockNotificationsPlugin.cancelAll();
  }
}


