package com.walkguide.service;

import com.walkguide.dto.response.ActivityLogResponse;
import com.walkguide.entity.ActivityLog;
import com.walkguide.entity.PairingRelation;
import com.walkguide.entity.User;
import com.walkguide.enums.ActivityLogType;
import com.walkguide.enums.PairingStatus;
import com.walkguide.exception.ResourceNotFoundException;
import com.walkguide.repository.ActivityLogRepository;
import com.walkguide.repository.PairingRelationRepository;
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("ActivityLogService Unit Tests")
class ActivityLogServiceTest {

    @Mock ActivityLogRepository activityLogRepository;
    @Mock PairingRelationRepository pairingRelationRepository;

    @InjectMocks ActivityLogService activityLogService;

    private User sampleUser;
    private User sampleGuardian;
    private ActivityLog sampleLog;

    @BeforeEach
    void setUp() {
        sampleUser = User.builder()
                .id(1L).email("user@test.com").role("ROLE_USER")
                .displayName("Test User").build();

        sampleGuardian = User.builder()
                .id(2L).email("guardian@test.com").role("ROLE_GUARDIAN")
                .displayName("Test Guardian").build();

        sampleLog = ActivityLog.builder()
                .id(10L).user(sampleUser)
                .logType(ActivityLogType.LOGIN)
                .description("User login")
                .metadata(null)
                .build();
    }

    // ===== CREATE LOG TESTS =====

    @Test
    @DisplayName("createLog - harus simpan ActivityLog dengan data yang benar")
    void createLog_shouldSaveWithCorrectData() {
        activityLogService.createLog(sampleUser, ActivityLogType.LOGIN, "User login", null);

        ArgumentCaptor<ActivityLog> captor = ArgumentCaptor.forClass(ActivityLog.class);
        verify(activityLogRepository).save(captor.capture());

        ActivityLog saved = captor.getValue();
        assertThat(saved.getUser()).isEqualTo(sampleUser);
        assertThat(saved.getLogType()).isEqualTo(ActivityLogType.LOGIN);
        assertThat(saved.getDescription()).isEqualTo("User login");
        assertThat(saved.getMetadata()).isNull();
    }

    @Test
    @DisplayName("createLog - harus simpan ActivityLog dengan metadata")
    void createLog_withMetadata_shouldSaveMetadata() {
        String meta = "{\"label\":\"person\",\"direction\":\"LEFT\"}";
        activityLogService.createLog(sampleUser, ActivityLogType.OBSTACLE_DETECTED, "Obstacle terdeteksi", meta);

        ArgumentCaptor<ActivityLog> captor = ArgumentCaptor.forClass(ActivityLog.class);
        verify(activityLogRepository).save(captor.capture());

        assertThat(captor.getValue().getMetadata()).isEqualTo(meta);
        assertThat(captor.getValue().getLogType()).isEqualTo(ActivityLogType.OBSTACLE_DETECTED);
    }

    @Test
    @DisplayName("createLog - berbagai tipe log harus disimpan dengan benar")
    void createLog_variousLogTypes_shouldSaveCorrectly() {
        ActivityLogType[] types = {
            ActivityLogType.LOGOUT, ActivityLogType.SOS_TRIGGERED,
            ActivityLogType.GEOFENCE_EXIT, ActivityLogType.PAIRING_ACCEPTED
        };

        for (ActivityLogType type : types) {
            activityLogService.createLog(sampleUser, type, "Deskripsi", null);
        }

        verify(activityLogRepository, times(types.length)).save(any(ActivityLog.class));
    }

    // ===== GET LOGS TESTS =====

    @Test
    @DisplayName("getLogs - harus return halaman log berdasarkan userId")
    void getLogs_shouldReturnPagedLogs() {
        sampleLog.setId(10L);
        Page<ActivityLog> page = new PageImpl<>(List.of(sampleLog));
        Pageable pageable = PageRequest.of(0, 10);

        when(activityLogRepository.findByUser_IdOrderByCreatedAtDesc(1L, pageable))
                .thenReturn(page);

        Page<ActivityLogResponse> result = activityLogService.getLogs(1L, pageable);

        assertThat(result.getTotalElements()).isEqualTo(1);
        assertThat(result.getContent().get(0).getId()).isEqualTo(10L);
        assertThat(result.getContent().get(0).getLogType()).isEqualTo("LOGIN");
        assertThat(result.getContent().get(0).getDescription()).isEqualTo("User login");
    }

    @Test
    @DisplayName("getLogs - tidak ada log harus return halaman kosong")
    void getLogs_noLogs_shouldReturnEmptyPage() {
        Pageable pageable = PageRequest.of(0, 10);
        when(activityLogRepository.findByUser_IdOrderByCreatedAtDesc(1L, pageable))
                .thenReturn(Page.empty());

        Page<ActivityLogResponse> result = activityLogService.getLogs(1L, pageable);

        assertThat(result.getTotalElements()).isZero();
        assertThat(result.getContent()).isEmpty();
    }

    // ===== GET LOGS FOR GUARDIAN TESTS =====

    @Test
    @DisplayName("getLogsForGuardian - Guardian dengan pairing aktif harus return log user")
    void getLogsForGuardian_activePairing_shouldReturnUserLogs() {
        PairingRelation pairing = PairingRelation.builder()
                .id(5L).guardian(sampleGuardian).user(sampleUser)
                .status(PairingStatus.ACTIVE).build();

        sampleLog.setId(10L);
        Page<ActivityLog> page = new PageImpl<>(List.of(sampleLog));
        Pageable pageable = PageRequest.of(0, 5);

        when(pairingRelationRepository.findByGuardian_IdAndStatus(2L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(pairing));
        when(activityLogRepository.findByUser_IdOrderByCreatedAtDesc(1L, pageable))
                .thenReturn(page);

        Page<ActivityLogResponse> result = activityLogService.getLogsForGuardian(2L, pageable);

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

    @Test
    @DisplayName("getLogsForGuardian - Guardian tanpa pairing aktif harus throw ResourceNotFoundException")
    void getLogsForGuardian_noPairing_shouldThrow() {
        when(pairingRelationRepository.findByGuardian_IdAndStatus(2L, PairingStatus.ACTIVE))
                .thenReturn(Optional.empty());

        assertThatThrownBy(() -> activityLogService.getLogsForGuardian(2L, PageRequest.of(0, 10)))
                .isInstanceOf(ResourceNotFoundException.class)
                .hasMessageContaining("Belum ada user yang dipair");

        verify(activityLogRepository, never()).findByUser_IdOrderByCreatedAtDesc(any(), any());
    }

    @Test
    @DisplayName("getLogsForGuardian - paginasi multi-halaman harus diteruskan ke repository")
    void getLogsForGuardian_pagination_shouldPassPageableToRepository() {
        PairingRelation pairing = PairingRelation.builder()
                .id(5L).guardian(sampleGuardian).user(sampleUser)
                .status(PairingStatus.ACTIVE).build();

        Pageable page2 = PageRequest.of(1, 10);
        when(pairingRelationRepository.findByGuardian_IdAndStatus(2L, PairingStatus.ACTIVE))
                .thenReturn(Optional.of(pairing));
        when(activityLogRepository.findByUser_IdOrderByCreatedAtDesc(1L, page2))
                .thenReturn(Page.empty());

        activityLogService.getLogsForGuardian(2L, page2);

        verify(activityLogRepository).findByUser_IdOrderByCreatedAtDesc(1L, page2);
    }
}