package com.walkguide.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.walkguide.dto.request.InviteUserRequest;
import com.walkguide.dto.request.PairingResponseRequest;
import com.walkguide.dto.response.PairingStatusResponse;
import com.walkguide.security.SecurityHelper;
import com.walkguide.service.PairingService;
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.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Collection;
import java.util.List;

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(PairingController.class)
@DisplayName("PairingController Unit Tests")
class PairingControllerTest {

    @MockBean private JwtAuthFilter jwtAuthFilter;

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private PairingService pairingService;

    private PairingStatusResponse buildPairingStatus(String status) {
        return PairingStatusResponse.builder()
                .pairingId(1L)
                .status(status)
                .pairedWithName("Test Partner")
                .pairedWithEmail("partner@test.com")
                .uniqueUserId("ABC123")
                .build();
    }

    // ===== INVITE =====

    @Test
    @WithMockUser(username = "2", roles = "GUARDIAN")
    @DisplayName("POST /api/v1/shared/pairing/invite - Guardian undang User harus return 200")
    void invite_asGuardian_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            InviteUserRequest req = new InviteUserRequest();
            req.setUniqueUserId("ABC123DEF456");

            PairingStatusResponse resp = buildPairingStatus("PENDING");
            when(pairingService.inviteUser(2L, "ABC123DEF456")).thenReturn(resp);

            mockMvc.perform(post("/api/v1/shared/pairing/invite")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(req)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.success").value(true))
                    .andExpect(jsonPath("$.message").value("Undangan dikirim ke user"))
                    .andExpect(jsonPath("$.data.status").value("PENDING"));

            verify(pairingService).inviteUser(2L, "ABC123DEF456");
        }
    }

    @Test
    @WithMockUser(username = "2", roles = "GUARDIAN")
    @DisplayName("POST /api/v1/shared/pairing/invite - uniqueUserId tidak ada harus return 500")
    void invite_userNotFound_shouldReturn500() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);

            InviteUserRequest req = new InviteUserRequest();
            req.setUniqueUserId("INVALID999");

            when(pairingService.inviteUser(2L, "INVALID999"))
                    .thenThrow(new RuntimeException("User dengan ID tersebut tidak ditemukan"));

            mockMvc.perform(post("/api/v1/shared/pairing/invite")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(req)))
                    .andExpect(status().isInternalServerError());
        }
    }

    // ===== RESPOND =====

    @Test
    @WithMockUser(username = "1", roles = "USER")
    @DisplayName("POST /api/v1/shared/pairing/respond - User terima pairing harus return 200")
    void respond_accept_shouldReturn200WithAcceptMessage() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);

            PairingResponseRequest req = new PairingResponseRequest();
            req.setPairingId(5L);
            req.setAccept(true);

            PairingStatusResponse resp = buildPairingStatus("ACTIVE");
            when(pairingService.respondToPairing(1L, 5L, true)).thenReturn(resp);

            mockMvc.perform(post("/api/v1/shared/pairing/respond")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(req)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.success").value(true))
                    .andExpect(jsonPath("$.message").value("Pairing diterima"))
                    .andExpect(jsonPath("$.data.status").value("ACTIVE"));

            verify(pairingService).respondToPairing(1L, 5L, true);
        }
    }

    @Test
    @WithMockUser(username = "1", roles = "USER")
    @DisplayName("POST /api/v1/shared/pairing/respond - User tolak pairing harus return 200 pesan ditolak")
    void respond_reject_shouldReturn200WithRejectMessage() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);

            PairingResponseRequest req = new PairingResponseRequest();
            req.setPairingId(5L);
            req.setAccept(false);

            PairingStatusResponse resp = buildPairingStatus("REJECTED");
            when(pairingService.respondToPairing(1L, 5L, false)).thenReturn(resp);

            mockMvc.perform(post("/api/v1/shared/pairing/respond")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(req)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Pairing ditolak"))
                    .andExpect(jsonPath("$.data.status").value("REJECTED"));

            verify(pairingService).respondToPairing(1L, 5L, false);
        }
    }

    // ===== UNPAIR =====

    @Test
    @WithMockUser(username = "1", roles = "USER")
    @DisplayName("DELETE /api/v1/shared/pairing/unpair - harus akhiri pairing dan return 200")
    void unpair_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
            doNothing().when(pairingService).unpair(1L);

            mockMvc.perform(delete("/api/v1/shared/pairing/unpair")
                            .with(csrf()))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.success").value(true))
                    .andExpect(jsonPath("$.message").value("Pairing diakhiri"));

            verify(pairingService).unpair(1L);
        }
    }

    @Test
    @WithMockUser(username = "2", roles = "GUARDIAN")
    @DisplayName("DELETE /api/v1/shared/pairing/unpair - Guardian juga bisa unpair")
    void unpair_asGuardian_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
            doNothing().when(pairingService).unpair(2L);

            mockMvc.perform(delete("/api/v1/shared/pairing/unpair")
                            .with(csrf()))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Pairing diakhiri"));

            verify(pairingService).unpair(2L);
        }
    }

    @Test
    @WithMockUser(username = "1", roles = "USER")
    @DisplayName("DELETE /api/v1/shared/pairing/unpair - service throw harus return 500")
    void unpair_serviceThrows_shouldReturn500() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
            doThrow(new RuntimeException("Tidak ada pairing aktif")).when(pairingService).unpair(1L);

            mockMvc.perform(delete("/api/v1/shared/pairing/unpair")
                            .with(csrf()))
                    .andExpect(status().isInternalServerError());
        }
    }

    // ===== STATUS =====

    @Test
    @WithMockUser(username = "1", roles = "USER")
    @DisplayName("GET /api/v1/shared/pairing/status - User harus return status pairing")
    void status_asUser_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
            sh.when(SecurityHelper::getCurrentUserRole).thenReturn("ROLE_USER");

            PairingStatusResponse resp = buildPairingStatus("ACTIVE");
            when(pairingService.getStatus(eq(1L), eq("ROLE_USER"))).thenReturn(resp);

            mockMvc.perform(get("/api/v1/shared/pairing/status"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.success").value(true))
                    .andExpect(jsonPath("$.message").value("Status pairing"))
                    .andExpect(jsonPath("$.data.status").value("ACTIVE"))
                    .andExpect(jsonPath("$.data.pairingId").value(1));
        }
    }

    @Test
    @WithMockUser(username = "2", roles = "GUARDIAN")
    @DisplayName("GET /api/v1/shared/pairing/status - Guardian harus return status pairing dengan role GUARDIAN")
    void status_asGuardian_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
            sh.when(SecurityHelper::getCurrentUserRole).thenReturn("ROLE_GUARDIAN");

            PairingStatusResponse resp = buildPairingStatus("ACTIVE");
            when(pairingService.getStatus(eq(2L), eq("ROLE_GUARDIAN"))).thenReturn(resp);

            mockMvc.perform(get("/api/v1/shared/pairing/status"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.message").value("Status pairing"));
        }
    }

    @Test
    @WithMockUser(username = "1", roles = "USER")
    @DisplayName("GET /api/v1/shared/pairing/status - belum ada pairing harus return status NONE")
    void status_noPairing_shouldReturnNoneStatus() throws Exception {
        try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
            sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
            sh.when(SecurityHelper::getCurrentUserRole).thenReturn("ROLE_USER");

            PairingStatusResponse resp = PairingStatusResponse.builder()
                    .status("NONE")
                    .build();
            when(pairingService.getStatus(eq(1L), anyString())).thenReturn(resp);

            mockMvc.perform(get("/api/v1/shared/pairing/status"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data.status").value("NONE"));
        }
    }
}
