package com.walkguide.service;

import com.walkguide.dto.request.SendNotificationRequest;
import com.walkguide.dto.response.NotificationResponse;
import com.walkguide.entity.GuardianNotification;
import com.walkguide.entity.PairingRelation;
import com.walkguide.entity.User;
import com.walkguide.enums.NotificationType;
import com.walkguide.enums.PairingStatus;
import com.walkguide.exception.PairingException;
import com.walkguide.repository.GuardianNotificationRepository;
import com.walkguide.repository.PairingRelationRepository;
import com.walkguide.websocket.LocationBroadcaster;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;

import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
@DisplayName("NotificationService Unit Tests")
class NotificationServiceTest {

    @Mock GuardianNotificationRepository notifRepository;
    @Mock PairingRelationRepository pairingRelationRepository;
    @Mock FcmService fcmService;
    @Mock LocationBroadcaster locationBroadcaster;

    @InjectMocks NotificationService notificationService;

    private User guardian;
    private User user;
    private PairingRelation activePairing;

    @BeforeEach
    void setUp() {
        guardian = User.builder()
                .id(1L).email("guardian@test.com").role("ROLE_GUARDIAN")
                .displayName("Guardian").fcmToken("guardian-fcm").build();

        user = User.builder()
                .id(2L).email("user@test.com").role("ROLE_USER")
                .displayName("User").fcmToken("user-fcm").build();

        activePairing = PairingRelation.builder()
                .id(10L).guardian(guardian).user(user).status(PairingStatus.ACTIVE).build();
    }

    // ===== SEND NOTIFICATION TESTS =====

    @Test
    @DisplayName("sendNotification - TEXT type: harus simpan dan kirim FCM ke user")
    void sendNotification_textType_shouldSaveAndSendFcm() {
        SendNotificationRequest req = new SendNotificationRequest();
        req.setNotifType("TEXT");
        req.setContent("Hati-hati di pertigaan!");

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        when(notifRepository.save(any(GuardianNotification.class))).thenAnswer(inv -> {
            GuardianNotification n = inv.getArgument(0);
            n.setId(100L);
            return n;
        });

        NotificationResponse result = notificationService.sendNotification(1L, req);

        assertThat(result).isNotNull();
        assertThat(result.getContent()).isEqualTo("Hati-hati di pertigaan!");
        verify(notifRepository).save(any(GuardianNotification.class));
        verify(fcmService).sendToToken(eq("user-fcm"), anyString(), anyString(), anyMap());
    }

    @Test
    @DisplayName("sendNotification - VOICE_NOTE type: harus simpan dengan tipe VOICE_NOTE")
    void sendNotification_voiceNoteType_shouldSaveAsVoiceNote() {
        SendNotificationRequest req = new SendNotificationRequest();
        req.setNotifType("VOICE_NOTE");
        req.setContent(null);
        req.setVoiceNoteUrl("https://storage/voice/001.m4a");
        req.setVoiceNoteDuration(12);

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        when(notifRepository.save(any(GuardianNotification.class))).thenAnswer(inv -> {
            GuardianNotification n = inv.getArgument(0);
            n.setId(101L);
            n.setNotifType(NotificationType.VOICE_NOTE);
            n.setVoiceNoteUrl("https://storage/voice/001.m4a");
            return n;
        });

        NotificationResponse result = notificationService.sendNotification(1L, req);

        assertThat(result.getVoiceNoteUrl()).isEqualTo("https://storage/voice/001.m4a");
        verify(fcmService).sendToToken(eq("user-fcm"), anyString(), anyString(), anyMap());
    }

    @Test
    @DisplayName("sendNotification - Guardian tidak punya active pairing harus throw PairingException")
    void sendNotification_noPairing_shouldThrow() {
        SendNotificationRequest req = new SendNotificationRequest();
        req.setNotifType("TEXT");
        req.setContent("test");

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.empty());

        assertThatThrownBy(() -> notificationService.sendNotification(1L, req))
                .isInstanceOf(PairingException.class)
                .hasMessageContaining("Tidak ada user yang dipair");

        verify(notifRepository, never()).save(any());
        verify(fcmService, never()).sendToToken(any(), any(), any(), any());
    }

    // ===== MARK AS READ TESTS =====

    @Test
    @DisplayName("markAsRead - harus set isRead=true dan simpan")
    void markAsRead_shouldSetReadTrue() {
        GuardianNotification notif = GuardianNotification.builder()
                .id(100L).userId(2L).guardianId(1L)
                .notifType(NotificationType.TEXT).content("Test").isRead(false).build();

        when(notifRepository.findByIdAndUserId(100L, 2L)).thenReturn(Optional.of(notif));
        when(notifRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        notificationService.markAsRead(2L, 100L);

        assertThat(notif.getIsRead()).isTrue();
        verify(notifRepository).save(notif);
    }

    @Test
    @DisplayName("markAsRead - notifikasi tidak ditemukan harus throw RuntimeException")
    void markAsRead_notFound_shouldThrow() {
        when(notifRepository.findByIdAndUserId(999L, 2L)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> notificationService.markAsRead(2L, 999L))
                .isInstanceOf(RuntimeException.class);
    }

    // ===== UNREAD COUNT TESTS =====

    @Test
    @DisplayName("getUnreadCount - harus return jumlah notif yang belum dibaca")
    void getUnreadCount_shouldReturnCorrectCount() {
        when(notifRepository.countByUserIdAndIsReadFalse(2L)).thenReturn(5L);

        long count = notificationService.getUnreadCount(2L);

        assertThat(count).isEqualTo(5L);
    }

    @Test
    @DisplayName("getUnreadCount - tidak ada notif belum dibaca harus return 0")
    void getUnreadCount_noUnread_shouldReturnZero() {
        when(notifRepository.countByUserIdAndIsReadFalse(2L)).thenReturn(0L);

        long count = notificationService.getUnreadCount(2L);

        assertThat(count).isZero();
    }

    // ===== GET NOTIFICATIONS TESTS =====

    @Test
    @DisplayName("getNotifications - harus return page notif untuk user")
    void getNotifications_shouldReturnPagedResults() {
        GuardianNotification notif = GuardianNotification.builder()
                .id(1L).userId(2L).guardianId(1L)
                .notifType(NotificationType.TEXT).content("Hello").isRead(false).build();

        Page<GuardianNotification> page = new PageImpl<>(List.of(notif));
        PageRequest pageable = PageRequest.of(0, 10);

        when(notifRepository.findByUserIdOrderByCreatedAtDesc(2L, pageable)).thenReturn(page);

        Page<NotificationResponse> result = notificationService.getNotifications(2L, pageable);

        assertThat(result.getTotalElements()).isEqualTo(1);
        assertThat(result.getContent().get(0).getContent()).isEqualTo("Hello");
    }
}