package com.walkguide.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.walkguide.dto.request.*;
import com.walkguide.dto.response.*;
import com.walkguide.security.SecurityHelper;
import com.walkguide.service.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import com.walkguide.security.JwtAuthFilter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

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

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@AutoConfigureMockMvc(addFilters = false)
@WebMvcTest(GuardianController.class)
@WithMockUser(username = "2", roles = "GUARDIAN")
@DisplayName("GuardianController Unit Tests")
class GuardianControllerTest {

    @MockBean private JwtAuthFilter jwtAuthFilter;

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean private GuardianDashboardService dashboardService;
    @MockBean private LocationService locationService;
    @MockBean private ActivityLogService activityLogService;
    @MockBean private ObstacleLogService obstacleLogService;
    @MockBean private NotificationService notificationService;
    @MockBean private SosService sosService;
    @MockBean private AiConfigService aiConfigService;
    @MockBean private VoiceCommandService voiceCommandService;
    @MockBean private HardwareShortcutService hardwareShortcutService;
    @MockBean private GeofenceService geofenceService;
    @MockBean private UserSettingsService userSettingsService;

    // ===== DASHBOARD =====

    @Test
    @DisplayName("GET /api/v1/guardian/dashboard - harus return dashboard")
    void dashboard_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
            when(dashboardService.getDashboard(2L)).thenReturn(new DashboardResponse());

            mockMvc.perform(get("/api/v1/guardian/dashboard"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.success").value(true))
                    .andExpect(jsonPath("$.message").value("Dashboard Guardian"));
        }
    }

    // ===== USER LOCATION =====

    @Test
    @DisplayName("GET /api/v1/guardian/user-location - harus return lokasi terakhir user")
    void userLocation_locationExists_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
            when(locationService.getLastLocationForGuardian(2L))
                    .thenReturn(Optional.of(new LocationResponse()));

            mockMvc.perform(get("/api/v1/guardian/user-location"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.success").value(true))
                    .andExpect(jsonPath("$.message").value("Lokasi terakhir user"));
        }
    }

    @Test
    @DisplayName("GET /api/v1/guardian/user-location - belum ada lokasi harus return data null")
    void userLocation_noLocation_shouldReturnNullData() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
            when(locationService.getLastLocationForGuardian(2L)).thenReturn(Optional.empty());

            mockMvc.perform(get("/api/v1/guardian/user-location"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data").isEmpty());
        }
    }

    // ===== LOCATION HISTORY =====

    @Test
    @DisplayName("GET /api/v1/guardian/location-history - harus return paginated history")
    void locationHistory_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            Page<LocationResponse> page = new PageImpl<>(List.of());
            when(locationService.getLocationHistory(eq(2L), any())).thenReturn(page);

            mockMvc.perform(get("/api/v1/guardian/location-history")
                            .param("page", "0").param("size", "20"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Riwayat lokasi"));
        }
    }

    // ===== ACTIVITY LOGS =====

    @Test
    @DisplayName("GET /api/v1/guardian/activity-logs - harus return log aktivitas user")
    void activityLogs_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            Page<ActivityLogResponse> page = new PageImpl<>(List.of());
            when(activityLogService.getLogsForGuardian(eq(2L), any())).thenReturn(page);

            mockMvc.perform(get("/api/v1/guardian/activity-logs"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Log aktivitas user"));
        }
    }

    // ===== OBSTACLE LOGS =====

    @Test
    @DisplayName("GET /api/v1/guardian/obstacle-logs - harus return log obstacle user")
    void obstacleLogs_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            Page<ObstacleLogResponse> page = new PageImpl<>(List.of());
            when(obstacleLogService.getObstacleLogs(eq(2L), any())).thenReturn(page);

            mockMvc.perform(get("/api/v1/guardian/obstacle-logs"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Log obstacle user"));
        }
    }

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

    @Test
    @DisplayName("POST /api/v1/guardian/notifications/send - harus kirim notifikasi dan return response")
    void sendNotif_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            SendNotificationRequest req = new SendNotificationRequest();
            NotificationResponse resp = new NotificationResponse();
            when(notificationService.sendNotification(eq(2L), any())).thenReturn(resp);

            mockMvc.perform(post("/api/v1/guardian/notifications/send")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(req)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Notifikasi terkirim"));

            verify(notificationService).sendNotification(eq(2L), any(SendNotificationRequest.class));
        }
    }

    // ===== SOS EVENTS =====

    @Test
    @DisplayName("GET /api/v1/guardian/sos-events - harus return paginated SOS events")
    void sosEvents_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            Page<SosEventResponse> page = new PageImpl<>(List.of());
            when(sosService.getSosEventsForGuardian(eq(2L), any())).thenReturn(page);

            mockMvc.perform(get("/api/v1/guardian/sos-events"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("SOS events"));
        }
    }

    @Test
    @DisplayName("PUT /api/v1/guardian/sos/{id}/acknowledge - harus akui SOS")
    void acknowledgeSos_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            SosEventResponse resp = new SosEventResponse();
            when(sosService.acknowledgeSos(2L, 10L)).thenReturn(resp);

            mockMvc.perform(put("/api/v1/guardian/sos/10/acknowledge")
                            .with(csrf()))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("SOS diakui"));

            verify(sosService).acknowledgeSos(2L, 10L);
        }
    }

    // ===== AI CONFIG =====

    @Test
    @DisplayName("GET /api/v1/guardian/ai-config - harus return AI config user")
    void getAiConfig_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
            when(aiConfigService.getConfig(2L)).thenReturn(new AiConfigResponse());

            mockMvc.perform(get("/api/v1/guardian/ai-config"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("AI config"));
        }
    }

    @Test
    @DisplayName("PUT /api/v1/guardian/ai-config - harus update AI config dan return response baru")
    void updateAiConfig_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            AiConfigUpdateRequest req = new AiConfigUpdateRequest();
            AiConfigResponse resp = new AiConfigResponse();
            when(aiConfigService.updateConfigByGuardian(eq(2L), any())).thenReturn(resp);

            mockMvc.perform(put("/api/v1/guardian/ai-config")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(req)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("AI config diperbarui"));

            verify(aiConfigService).updateConfigByGuardian(eq(2L), any(AiConfigUpdateRequest.class));
        }
    }

    // ===== VOICE COMMANDS =====

    @Test
    @DisplayName("GET /api/v1/guardian/voice-commands - harus return voice commands user")
    void getVoiceCommands_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
            when(voiceCommandService.getAll(2L)).thenReturn(List.of());

            mockMvc.perform(get("/api/v1/guardian/voice-commands"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Voice commands"));
        }
    }

    @Test
    @DisplayName("PUT /api/v1/guardian/voice-commands - harus update voice command")
    void updateVoiceCommand_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
            VoiceCommandResponse resp = new VoiceCommandResponse();
            when(voiceCommandService.updateByGuardian(eq(2L), any())).thenReturn(resp);

            mockMvc.perform(put("/api/v1/guardian/voice-commands")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(req)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Voice command diperbarui"));
        }
    }

    // ===== SHORTCUTS =====

    @Test
    @DisplayName("GET /api/v1/guardian/shortcuts - harus return hardware shortcuts")
    void getShortcuts_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
            when(hardwareShortcutService.getAll(2L)).thenReturn(List.of());

            mockMvc.perform(get("/api/v1/guardian/shortcuts"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Hardware shortcuts"));
        }
    }

    // ===== GEOFENCE =====

    @Test
    @DisplayName("GET /api/v1/guardian/geofence - harus return konfigurasi geofence")
    void getGeofence_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
            when(geofenceService.getConfig(2L)).thenReturn(new GeofenceResponse());

            mockMvc.perform(get("/api/v1/guardian/geofence"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Geofence config"));
        }
    }

    @Test
    @DisplayName("PUT /api/v1/guardian/geofence - harus update geofence config")
    void updateGeofence_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            GeofenceConfigRequest req = new GeofenceConfigRequest();
            GeofenceResponse resp = new GeofenceResponse();
            when(geofenceService.updateConfig(eq(2L), any())).thenReturn(resp);

            mockMvc.perform(put("/api/v1/guardian/geofence")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(req)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Geofence diperbarui"));

            verify(geofenceService).updateConfig(eq(2L), any(GeofenceConfigRequest.class));
        }
    }

    // ===== USER SETTINGS (dikelola Guardian) =====

    @Test
    @DisplayName("GET /api/v1/guardian/user-settings - harus return settings user")
    void getUserSettings_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
            when(userSettingsService.getSettings(2L)).thenReturn(new UserSettingsResponse());

            mockMvc.perform(get("/api/v1/guardian/user-settings"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("User settings"));
        }
    }

    @Test
    @DisplayName("PUT /api/v1/guardian/user-settings - harus update user settings")
    void updateUserSettings_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            UserSettingsUpdateRequest req = new UserSettingsUpdateRequest();
            UserSettingsResponse resp = new UserSettingsResponse();
            when(userSettingsService.updateSettings(eq(2L), any())).thenReturn(resp);

            mockMvc.perform(put("/api/v1/guardian/user-settings")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(req)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("User settings diperbarui"));
        }
    }
}
