package incheon.product.geoview2d.download.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import incheon.com.cmm.exception.RestApiExceptionHandler;
import incheon.product.geoview2d.download.service.SpatialDownloadService;
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.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.ResourceHttpMessageConverter;
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 static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * DownloadApiController MockMvc 테스트.
 * format/layerType 화이트리스트 검증(BL-009-3)을 중점 검증한다.
 */
@ExtendWith(MockitoExtension.class)
class DownloadApiControllerTest {

    @InjectMocks
    private DownloadApiController controller;

    @Mock
    private SpatialDownloadService spatialDownloadService;

    private MockMvc mockMvc;

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

    @Nested
    @DisplayName("GET /api/v1/product/g2d/download — 다운로드")
    class Download {

        @Test
        @DisplayName("허용된 format으로 다운로드 성공")
        void validFormatSucceeds() throws Exception {
            byte[] content = "test".getBytes();
            when(spatialDownloadService.download(anyLong(), anyString(), anyString(), any()))
                    .thenReturn(ResponseEntity.ok()
                            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=test.csv")
                            .contentType(MediaType.APPLICATION_OCTET_STREAM)
                            .body(new org.springframework.core.io.ByteArrayResource(content)));

            mockMvc.perform(get("/api/v1/product/g2d/download")
                            .param("layerId", "1")
                            .param("format", "csv"))
                    .andExpect(status().isOk());

            verify(spatialDownloadService).download(1L, "TASK", "csv", null);
        }

        @Test
        @DisplayName("허용되지 않는 format이면 400 (BL-009-3)")
        void invalidFormatReturns400() throws Exception {
            mockMvc.perform(get("/api/v1/product/g2d/download")
                            .param("layerId", "1")
                            .param("format", "exe"))
                    .andExpect(status().isBadRequest());

            verify(spatialDownloadService, never()).download(anyLong(), anyString(), anyString(), any());
        }

        @Test
        @DisplayName("허용되지 않는 layerType이면 400 (BL-009-3)")
        void invalidLayerTypeReturns400() throws Exception {
            mockMvc.perform(get("/api/v1/product/g2d/download")
                            .param("layerId", "1")
                            .param("format", "csv")
                            .param("layerType", "ADMIN"))
                    .andExpect(status().isBadRequest());

            verify(spatialDownloadService, never()).download(anyLong(), anyString(), anyString(), any());
        }

        @Test
        @DisplayName("format 대소문자 무관하게 허용 (shapefile)")
        void formatCaseInsensitive() throws Exception {
            when(spatialDownloadService.download(anyLong(), anyString(), anyString(), any()))
                    .thenReturn(ResponseEntity.ok()
                            .contentType(MediaType.APPLICATION_OCTET_STREAM)
                            .body(new org.springframework.core.io.ByteArrayResource("test".getBytes())));

            mockMvc.perform(get("/api/v1/product/g2d/download")
                            .param("layerId", "1")
                            .param("format", "SHAPEFILE"))
                    .andExpect(status().isOk());
        }

        @Test
        @DisplayName("layerType 대소문자 무관하게 허용 (USER)")
        void layerTypeCaseInsensitive() throws Exception {
            when(spatialDownloadService.download(anyLong(), anyString(), anyString(), any()))
                    .thenReturn(ResponseEntity.ok()
                            .contentType(MediaType.APPLICATION_OCTET_STREAM)
                            .body(new org.springframework.core.io.ByteArrayResource("test".getBytes())));

            mockMvc.perform(get("/api/v1/product/g2d/download")
                            .param("layerId", "1")
                            .param("format", "csv")
                            .param("layerType", "user"))
                    .andExpect(status().isOk());
        }
    }

    @Nested
    @DisplayName("GET /api/v1/product/g2d/download/formats — 지원 포맷")
    class Formats {

        @Test
        @DisplayName("5개 지원 포맷 목록 반환")
        void getSupportedFormats() throws Exception {
            mockMvc.perform(get("/api/v1/product/g2d/download/formats"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data").isArray())
                    .andExpect(jsonPath("$.data.length()").value(5));
        }
    }
}
