package incheon.product.geoview3d.theme.web;

import incheon.product.geoview3d.theme.service.ThemeService;
import incheon.product.geoview3d.theme.vo.ThemeCategoryVO;
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 com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.http.MediaType;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import java.util.List;
import java.util.Map;

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

/**
 * ThemeApiController MockMvc 테스트.
 * 엔드포인트 바인딩, Content-Type, 서비스 위임을 검증한다.
 */
@ExtendWith(MockitoExtension.class)
class ThemeApiControllerTest {

    @InjectMocks
    private ThemeApiController controller;

    @Mock
    private ThemeService themeService;

    private MockMvc mockMvc;

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

    @Nested
    @DisplayName("GET /api/v1/product/g3d/theme/list — 주제도 목록")
    class ThemeList {

        @Test
        @DisplayName("JSON 문자열 그대로 반환")
        void themeListJson() throws Exception {
            when(themeService.getThemeListJson()).thenReturn("[{\"id\":\"t1\"}]");

            mockMvc.perform(get("/api/v1/product/g3d/theme/list"))
                    .andExpect(status().isOk())
                    .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                    .andExpect(content().string("[{\"id\":\"t1\"}]"));
        }
    }

    @Nested
    @DisplayName("GET /api/v1/product/g3d/theme/{themeId} — 단일 주제도")
    class SingleTheme {

        @Test
        @DisplayName("themeId로 주제도 JSON 조회")
        void themeJson() throws Exception {
            when(themeService.getThemeJson("t1", null, null)).thenReturn("{\"id\":\"t1\"}");

            mockMvc.perform(get("/api/v1/product/g3d/theme/t1"))
                    .andExpect(status().isOk())
                    .andExpect(content().string("{\"id\":\"t1\"}"));
        }

        @Test
        @DisplayName("q, limit 파라미터 전달")
        void themeJsonWithParams() throws Exception {
            when(themeService.getThemeJson("t1", "검색어", 20)).thenReturn("{\"id\":\"t1\"}");

            mockMvc.perform(get("/api/v1/product/g3d/theme/t1")
                            .param("q", "검색어")
                            .param("limit", "20"))
                    .andExpect(status().isOk());
            verify(themeService).getThemeJson("t1", "검색어", 20);
        }
    }

    @Nested
    @DisplayName("GET /api/v1/product/g3d/theme/{themeId}/search — 주제도 검색")
    class ThemeSearch {

        @Test
        @DisplayName("검색어와 limit으로 주제도 검색")
        void searchTheme() throws Exception {
            when(themeService.getThemeJson("t1", "키워드", 50)).thenReturn("{}");

            mockMvc.perform(get("/api/v1/product/g3d/theme/t1/search")
                            .param("q", "키워드")
                            .param("limit", "50"))
                    .andExpect(status().isOk());
            verify(themeService).getThemeJson("t1", "키워드", 50);
        }
    }

    @Nested
    @DisplayName("GET /api/v1/product/g3d/theme/categories — 카테고리")
    class Categories {

        @Test
        @DisplayName("카테고리 목록을 DefaultApiResponse로 반환")
        void getCategories() throws Exception {
            when(themeService.getCategories()).thenReturn(List.of());

            mockMvc.perform(get("/api/v1/product/g3d/theme/categories"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data").isArray());
        }
    }

    @Nested
    @DisplayName("GET /api/v1/product/g3d/theme/filter — 필터 값")
    class FilterValues {

        @Test
        @DisplayName("thmId, filter 파라미터로 필터 값 조회")
        void getFilterValues() throws Exception {
            when(themeService.getFilterValues("t1", "col_a")).thenReturn(List.of("v1", "v2"));

            mockMvc.perform(get("/api/v1/product/g3d/theme/filter")
                            .param("thmId", "t1")
                            .param("filter", "col_a"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data[0]").value("v1"))
                    .andExpect(jsonPath("$.data[1]").value("v2"));
        }
    }

    @Nested
    @DisplayName("GET /api/v1/product/g3d/theme/filter/child — 계층 필터")
    class FilterChildValues {

        @Test
        @DisplayName("4개 파라미터로 계층 필터 값 조회")
        void getFilterChildValues() throws Exception {
            when(themeService.getFilterChildValues("t1", "col_a", "val1", "col_b"))
                    .thenReturn(List.of("child1"));

            mockMvc.perform(get("/api/v1/product/g3d/theme/filter/child")
                            .param("thmId", "t1")
                            .param("filterName1", "col_a")
                            .param("filterValue", "val1")
                            .param("filterName2", "col_b"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data[0]").value("child1"));
        }
    }

    @Nested
    @DisplayName("GET /api/v1/product/g3d/theme/election/geom — 선거구")
    class ElectionGeom {

        @Test
        @DisplayName("poiId로 선거구 지오메트리 조회")
        void getElectionGeom() throws Exception {
            when(themeService.getElectionGeom("poi1")).thenReturn("{\"type\":\"Polygon\"}");

            mockMvc.perform(get("/api/v1/product/g3d/theme/election/geom")
                            .param("poiId", "poi1"))
                    .andExpect(status().isOk())
                    .andExpect(content().string("{\"type\":\"Polygon\"}"));
        }
    }

    @Nested
    @DisplayName("GET /api/v1/product/g3d/theme/wastebag — 쓰레기봉투")
    class WasteBag {

        @Test
        @DisplayName("sgg로 쓰레기봉투 가격 조회")
        void getWasteBagPrices() throws Exception {
            when(themeService.getWasteBagPrices("남동구"))
                    .thenReturn(List.of(Map.of("type", "일반", "price", 500)));

            mockMvc.perform(get("/api/v1/product/g3d/theme/wastebag")
                            .param("sgg", "남동구"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data[0].type").value("일반"));
        }
    }
}
