package incheon.product.geoview2d.layer.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import incheon.com.cmm.exception.RestApiExceptionHandler;
import incheon.com.security.vo.LoginVO;
import incheon.product.geoview2d.layer.service.LayerEditService;
import incheon.product.geoview2d.layer.service.TaskLayerService;
import incheon.product.geoview2d.layer.vo.TaskLayerVO;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
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.http.MediaType;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import java.util.Collections;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * LayerApiController MockMvc 테스트.
 * getCurrentUserId() 인증 검증(BL-009-4), 404 처리(BL-010-2), 페이지 클램핑을 검증한다.
 */
@ExtendWith(MockitoExtension.class)
class ProductLayerApiControllerTest {

    @InjectMocks
    private ProductLayerApiController controller;

    @Mock
    private LayerEditService layerEditService;

    @Mock
    private TaskLayerService taskLayerService;

    private MockMvc mockMvc;
    private ObjectMapper objectMapper;

    @BeforeEach
    void setup() {
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        mockMvc = MockMvcBuilders.standaloneSetup(controller)
                .setControllerAdvice(new RestApiExceptionHandler())
                .setMessageConverters(new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter(objectMapper))
                .build();
    }

    @AfterEach
    void clearSecurity() {
        SecurityContextHolder.clearContext();
    }

    private void setAuthenticatedUser(String userId) {
        LoginVO loginVO = new LoginVO();
        loginVO.setUserId(userId);
        UsernamePasswordAuthenticationToken auth =
                new UsernamePasswordAuthenticationToken(loginVO, null, Collections.emptyList());
        SecurityContextHolder.getContext().setAuthentication(auth);
    }

    @Nested
    @DisplayName("GET /api/v1/product/g2d/layers/task/{id} — 업무 레이어 상세")
    class GetTaskLayer {

        @Test
        @DisplayName("존재하는 레이어 조회 성공")
        void existingLayer() throws Exception {
            TaskLayerVO layer = new TaskLayerVO();
            layer.setTaskLyrId(1);
            layer.setTaskLyrNm("테스트 레이어");
            when(taskLayerService.getById(1)).thenReturn(layer);

            mockMvc.perform(get("/api/v1/product/g2d/layers/task/1"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data.taskLyrNm").value("테스트 레이어"));
        }

        @Test
        @DisplayName("존재하지 않는 레이어면 404 (BL-010-2)")
        void notFoundLayer() throws Exception {
            when(taskLayerService.getById(999)).thenReturn(null);

            mockMvc.perform(get("/api/v1/product/g2d/layers/task/999"))
                    .andExpect(status().isNotFound());
        }
    }

    @Nested
    @DisplayName("GET /api/v1/product/g2d/layers/task — 업무 레이어 목록")
    class GetTaskLayerList {

        @Test
        @DisplayName("업무 레이어 목록 조회")
        void listTaskLayers() throws Exception {
            when(taskLayerService.getTaskLayerList(any(), any())).thenReturn(List.of());

            mockMvc.perform(get("/api/v1/product/g2d/layers/task"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data.content").isArray());
        }

        @Test
        @DisplayName("pageIndex 0이면 1로 클램핑")
        void pageIndexClamped() throws Exception {
            when(taskLayerService.getTaskLayerList(any(), any())).thenReturn(List.of());

            mockMvc.perform(get("/api/v1/product/g2d/layers/task")
                            .param("pageIndex", "0"))
                    .andExpect(status().isOk());
        }
    }

    @Nested
    @DisplayName("POST /api/v1/product/g2d/layers/task — 업무 레이어 생성")
    class CreateTaskLayer {

        @Test
        @DisplayName("인증된 사용자로 레이어 생성 성공")
        void createWithAuth() throws Exception {
            setAuthenticatedUser("testUser");

            TaskLayerVO taskLayer = new TaskLayerVO();
            taskLayer.setTaskLyrNm("새 레이어");
            taskLayer.setLyrPhysNm("new_layer");

            mockMvc.perform(post("/api/v1/product/g2d/layers/task")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(taskLayer)))
                    .andExpect(status().isOk());

            verify(taskLayerService).create(any(TaskLayerVO.class));
        }

        @Test
        @DisplayName("인증 없이 생성 시도하면 401 (BL-009-4)")
        void createWithoutAuth() throws Exception {
            SecurityContextHolder.clearContext();

            TaskLayerVO taskLayer = new TaskLayerVO();
            taskLayer.setTaskLyrNm("새 레이어");

            mockMvc.perform(post("/api/v1/product/g2d/layers/task")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(taskLayer)))
                    .andExpect(status().isUnauthorized());
        }
    }

    @Nested
    @DisplayName("DELETE /api/v1/product/g2d/layers/task/{id} — 업무 레이어 삭제")
    class DeleteTaskLayer {

        @Test
        @DisplayName("업무 레이어 삭제 성공")
        void deleteLayer() throws Exception {
            mockMvc.perform(delete("/api/v1/product/g2d/layers/task/1"))
                    .andExpect(status().isOk());

            verify(taskLayerService).delete(1);
        }
    }

    @Nested
    @DisplayName("GET /api/v1/product/g2d/layers/task/all — 전체 목록")
    class GetAllTaskLayers {

        @Test
        @DisplayName("전체 업무 레이어 목록 반환")
        void getAllLayers() throws Exception {
            when(taskLayerService.getAll()).thenReturn(List.of());

            mockMvc.perform(get("/api/v1/product/g2d/layers/task/all"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data").isArray());
        }
    }
}
