package com.walkguide.service;

import com.walkguide.dto.request.SosRequest;
import com.walkguide.dto.response.SosEventResponse;
import com.walkguide.entity.PairingRelation;
import com.walkguide.entity.SosEvent;
import com.walkguide.entity.User;
import com.walkguide.enums.PairingStatus;
import com.walkguide.enums.SosStatus;
import com.walkguide.exception.ResourceNotFoundException;
import com.walkguide.repository.*;
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.ArgumentCaptor;
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 org.springframework.data.domain.Pageable;

import java.time.LocalDateTime;
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("SosService Unit Tests")
class SosServiceTest {

    @Mock SosEventRepository sosEventRepository;
    @Mock PairingRelationRepository pairingRelationRepository;
    @Mock UserRepository userRepository;
    @Mock ActivityLogService activityLogService;
    @Mock FcmService fcmService;
    @Mock LocationBroadcaster locationBroadcaster;

    @InjectMocks SosService sosService;

    private User guardian;
    private User user;
    private PairingRelation activePairing;
    private SosEvent savedSos;

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

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

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

        savedSos = SosEvent.builder()
                .id(50L).userId(2L).triggerType("MANUAL")
                .lat(-7.257).lng(112.752)
                .status(SosStatus.TRIGGERED)
                .createdAt(LocalDateTime.now()).build();
    }

    // ===== triggerSos TESTS =====

    @Test
    @DisplayName("triggerSos - MANUAL dengan koordinat: harus simpan SOS dan return response")
    void triggerSos_manualWithCoords_shouldSaveAndReturnResponse() {
        SosRequest req = new SosRequest();
        req.setTriggerType("MANUAL");
        req.setLat(-7.257);
        req.setLng(112.752);

        when(sosEventRepository.save(any(SosEvent.class))).thenReturn(savedSos);
        when(userRepository.findById(2L)).thenReturn(Optional.of(user));
        when(pairingRelationRepository.findByUser_IdAndStatus(2L, PairingStatus.ACTIVE))
                .thenReturn(Optional.empty()); // tidak ada guardian → skip FCM
        doNothing().when(activityLogService).createLog(any(), any(), any(), any());

        SosEventResponse result = sosService.triggerSos(2L, req);

        assertThat(result).isNotNull();
        assertThat(result.getId()).isEqualTo(50L);
        assertThat(result.getUserId()).isEqualTo(2L);
        assertThat(result.getTriggerType()).isEqualTo("MANUAL");
        assertThat(result.getStatus()).isEqualTo("TRIGGERED");
        verify(sosEventRepository).save(any(SosEvent.class));
    }

    @Test
    @DisplayName("triggerSos - triggerType null: harus default ke MANUAL")
    void triggerSos_nullTriggerType_shouldDefaultToManual() {
        SosRequest req = new SosRequest();
        req.setTriggerType(null);
        req.setLat(-7.257);
        req.setLng(112.752);

        when(sosEventRepository.save(any(SosEvent.class))).thenReturn(savedSos);
        when(userRepository.findById(2L)).thenReturn(Optional.of(user));
        when(pairingRelationRepository.findByUser_IdAndStatus(2L, PairingStatus.ACTIVE))
                .thenReturn(Optional.empty());
        doNothing().when(activityLogService).createLog(any(), any(), any(), any());

        ArgumentCaptor<SosEvent> captor = ArgumentCaptor.forClass(SosEvent.class);
        sosService.triggerSos(2L, req);

        verify(sosEventRepository).save(captor.capture());
        assertThat(captor.getValue().getTriggerType()).isEqualTo("MANUAL");
    }

    @Test
    @DisplayName("triggerSos - ada pairing aktif: harus kirim FCM dan WebSocket ke guardian")
    void triggerSos_activePairing_shouldNotifyGuardianViaFcmAndWebSocket() {
        SosRequest req = new SosRequest();
        req.setTriggerType("BUTTON");
        req.setLat(-7.257);
        req.setLng(112.752);

        when(sosEventRepository.save(any(SosEvent.class))).thenReturn(savedSos);
        when(userRepository.findById(2L)).thenReturn(Optional.of(user));
        when(pairingRelationRepository.findByUser_IdAndStatus(2L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        doNothing().when(activityLogService).createLog(any(), any(), any(), any());

        sosService.triggerSos(2L, req);

        verify(fcmService).sendHighPriority(
                eq("guardian-fcm-token"),
                contains("SOS ALERT"),
                anyString(),
                anyMap()
        );
        verify(locationBroadcaster).broadcastSos(eq(1L), any(SosEventResponse.class));
    }

    @Test
    @DisplayName("triggerSos - user tidak ditemukan: harus throw ResourceNotFoundException")
    void triggerSos_userNotFound_shouldThrowException() {
        SosRequest req = new SosRequest();
        req.setTriggerType("MANUAL");

        when(sosEventRepository.save(any(SosEvent.class))).thenReturn(savedSos);
        when(userRepository.findById(99L)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> sosService.triggerSos(99L, req))
                .isInstanceOf(ResourceNotFoundException.class);
    }

    // ===== acknowledgeSos TESTS =====

    @Test
    @DisplayName("acknowledgeSos - SOS ditemukan: harus ubah status ke ACKNOWLEDGED")
    void acknowledgeSos_sosFound_shouldChangeStatusToAcknowledged() {
        when(sosEventRepository.findById(50L)).thenReturn(Optional.of(savedSos));
        when(userRepository.findById(2L)).thenReturn(Optional.of(user));
        when(sosEventRepository.save(any(SosEvent.class))).thenAnswer(inv -> inv.getArgument(0));
        when(pairingRelationRepository.findByUser_IdAndStatus(2L, PairingStatus.ACTIVE))
                .thenReturn(Optional.empty());
        doNothing().when(activityLogService).createLog(any(), any(), any(), any());

        SosEventResponse result = sosService.acknowledgeSos(1L, 50L);

        assertThat(result).isNotNull();
        assertThat(result.getStatus()).isEqualTo("ACKNOWLEDGED");
        ArgumentCaptor<SosEvent> captor = ArgumentCaptor.forClass(SosEvent.class);
        verify(sosEventRepository).save(captor.capture());
        assertThat(captor.getValue().getStatus()).isEqualTo(SosStatus.ACKNOWLEDGED);
        assertThat(captor.getValue().getAcknowledgedAt()).isNotNull();
    }

    @Test
    @DisplayName("acknowledgeSos - ada pairing user: harus kirim FCM ke user bahwa guardian sudah respon")
    void acknowledgeSos_activePairingForUser_shouldNotifyUser() {
        when(sosEventRepository.findById(50L)).thenReturn(Optional.of(savedSos));
        when(userRepository.findById(2L)).thenReturn(Optional.of(user));
        when(sosEventRepository.save(any(SosEvent.class))).thenAnswer(inv -> inv.getArgument(0));
        when(pairingRelationRepository.findByUser_IdAndStatus(2L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        doNothing().when(activityLogService).createLog(any(), any(), any(), any());

        sosService.acknowledgeSos(1L, 50L);

        verify(fcmService).sendToToken(
                eq("user-fcm-token"),
                contains("Guardian Merespons SOS"),
                anyString(),
                anyMap()
        );
    }

    @Test
    @DisplayName("acknowledgeSos - SOS tidak ditemukan: harus throw ResourceNotFoundException")
    void acknowledgeSos_sosNotFound_shouldThrowException() {
        when(sosEventRepository.findById(999L)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> sosService.acknowledgeSos(1L, 999L))
                .isInstanceOf(ResourceNotFoundException.class)
                .hasMessageContaining("SOS event tidak ditemukan");
    }

    // ===== getSosEvents TESTS =====

    @Test
    @DisplayName("getSosEvents - harus return halaman SOS milik user")
    void getSosEvents_shouldReturnPagedSosEvents() {
        Pageable pageable = PageRequest.of(0, 10);
        Page<SosEvent> page = new PageImpl<>(List.of(savedSos), pageable, 1);

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

        Page<SosEventResponse> result = sosService.getSosEvents(2L, pageable);

        assertThat(result).isNotNull();
        assertThat(result.getContent()).hasSize(1);
        assertThat(result.getContent().get(0).getId()).isEqualTo(50L);
    }

    // ===== getSosEventsForGuardian TESTS =====

    @Test
    @DisplayName("getSosEventsForGuardian - ada pairing aktif: harus return SOS milik user yang dipair")
    void getSosEventsForGuardian_activePairing_shouldReturnUserSosEvents() {
        Pageable pageable = PageRequest.of(0, 10);
        Page<SosEvent> page = new PageImpl<>(List.of(savedSos), pageable, 1);

        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(activePairing));
        when(sosEventRepository.findByUserIdOrderByCreatedAtDesc(2L, pageable)).thenReturn(page);

        Page<SosEventResponse> result = sosService.getSosEventsForGuardian(1L, pageable);

        assertThat(result.getContent()).hasSize(1);
        assertThat(result.getContent().get(0).getUserId()).isEqualTo(2L);
    }

    @Test
    @DisplayName("getSosEventsForGuardian - tidak ada pairing: harus throw ResourceNotFoundException")
    void getSosEventsForGuardian_noPairing_shouldThrowException() {
        when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
                .thenReturn(Optional.empty());

        assertThatThrownBy(() -> sosService.getSosEventsForGuardian(1L, PageRequest.of(0, 10)))
                .isInstanceOf(ResourceNotFoundException.class);
    }
}