package incheon.product.geoview2d.download.service.impl;

import incheon.com.cmm.exception.BusinessException;
import incheon.com.cmm.exception.EntityNotFoundException;
import incheon.product.common.config.GeoViewProperties;
import incheon.product.geoview2d.download.mapper.DownloadMapper;
import incheon.product.geoview2d.download.repository.SpatialDataRepository;
import incheon.product.geoview2d.download.service.FileFormatConverter;
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 java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * SpatialDownloadServiceImpl 단위 테스트.
 * 메타데이터 분기, SRID 폴백, 포맷 검증을 테스트한다.
 */
@ExtendWith(MockitoExtension.class)
class SpatialDownloadServiceImplTest {

    @InjectMocks
    private SpatialDownloadServiceImpl downloadService;

    @Mock
    private DownloadMapper downloadMapper;

    @Mock
    private SpatialDataRepository dataRepository;

    @Mock
    private FileFormatConverter fileFormatConverter;

    @Mock
    private GeoViewProperties geoViewProperties;

    @Nested
    @DisplayName("download — 메타데이터 분기")
    class LayerMetaResolution {

        @Test
        @DisplayName("meta가 null이면 EntityNotFoundException 발생")
        void nullMetaThrows() {
            when(downloadMapper.selectDownloadLayerInfo(1L)).thenReturn(null);

            assertThatThrownBy(() -> downloadService.download(1L, "TASK", "csv", null))
                    .isInstanceOf(EntityNotFoundException.class)
                    .hasMessageContaining("레이어를 찾을 수 없습니다");
        }

        @Test
        @DisplayName("TASK 유형이면 selectDownloadLayerInfo 호출")
        void taskLayerType() {
            when(downloadMapper.selectDownloadLayerInfo(1L)).thenReturn(null);

            assertThatThrownBy(() -> downloadService.download(1L, "TASK", "csv", null))
                    .isInstanceOf(EntityNotFoundException.class);
            verify(downloadMapper).selectDownloadLayerInfo(1L);
        }

        @Test
        @DisplayName("TASK 외 유형이면 selectDownloadUserLayerInfo 호출")
        void userLayerType() {
            when(downloadMapper.selectDownloadUserLayerInfo(1L)).thenReturn(null);

            assertThatThrownBy(() -> downloadService.download(1L, "USER", "csv", null))
                    .isInstanceOf(EntityNotFoundException.class);
            verify(downloadMapper).selectDownloadUserLayerInfo(1L);
        }
    }

    @Nested
    @DisplayName("resolveLayerName — layerType별 키 분기")
    class LayerNameResolution {

        @Test
        @DisplayName("TASK 유형이면 task_lyr_nm 키 사용")
        void taskLayerName() {
            Map<String, Object> meta = Map.of(
                    "lyr_phys_nm", "iccom.test_table",
                    "task_lyr_nm", "과업레이어",
                    "spce_ty", "POLYGON",
                    "cntm", 5181
            );
            when(downloadMapper.selectDownloadLayerInfo(1L)).thenReturn(meta);
            when(dataRepository.selectGeometryColumnName("iccom.test_table")).thenReturn("geom");
            when(fileFormatConverter.sanitizeFileName("과업레이어")).thenReturn("과업레이어");

            // 지원하지 않는 포맷으로 호출해 processFormat에서 BusinessException 발생 유도
            assertThatThrownBy(() -> downloadService.download(1L, "TASK", "INVALID_FORMAT", "col1"))
                    .isInstanceOf(BusinessException.class)
                    .hasMessageContaining("지원하지 않는 포맷");
        }

        @Test
        @DisplayName("USER 유형이면 user_lyr_nm 키 사용")
        void userLayerName() {
            Map<String, Object> meta = Map.of(
                    "lyr_phys_nm", "iccom.user_table",
                    "user_lyr_nm", "사용자레이어",
                    "spce_ty", "POINT",
                    "cntm", 3857
            );
            when(downloadMapper.selectDownloadUserLayerInfo(2L)).thenReturn(meta);
            when(dataRepository.selectGeometryColumnName("iccom.user_table")).thenReturn("geom");
            when(fileFormatConverter.sanitizeFileName("사용자레이어")).thenReturn("사용자레이어");

            assertThatThrownBy(() -> downloadService.download(2L, "USER", "INVALID_FORMAT", "col1"))
                    .isInstanceOf(BusinessException.class)
                    .hasMessageContaining("지원하지 않는 포맷");
        }
    }

    @Nested
    @DisplayName("resolveSrid — SRID 폴백 로직")
    class SridResolution {

        @Test
        @DisplayName("meta에 cntm이 없으면 기본 SRID 사용")
        void fallbackSrid() {
            Map<String, Object> meta = Map.of(
                    "lyr_phys_nm", "iccom.test",
                    "task_lyr_nm", "테스트",
                    "spce_ty", "POLYGON"
                    // cntm 키 없음
            );
            GeoViewProperties.Coordinate coord = new GeoViewProperties.Coordinate();
            when(downloadMapper.selectDownloadLayerInfo(1L)).thenReturn(meta);
            when(geoViewProperties.getCoordinate()).thenReturn(coord);
            when(dataRepository.selectGeometryColumnName("iccom.test")).thenReturn("geom");
            when(fileFormatConverter.sanitizeFileName("테스트")).thenReturn("테스트");

            assertThatThrownBy(() -> downloadService.download(1L, "TASK", "UNKNOWN", "col"))
                    .isInstanceOf(BusinessException.class);
            // coord.getDefaultSrid()가 호출되었으므로 기본값 5181이 사용됨
            verify(geoViewProperties).getCoordinate();
        }
    }

    @Nested
    @DisplayName("download — 포맷 검증")
    class FormatValidation {

        @Test
        @DisplayName("지원하지 않는 포맷이면 BusinessException 발생")
        void unsupportedFormat() {
            Map<String, Object> meta = Map.of(
                    "lyr_phys_nm", "iccom.t",
                    "task_lyr_nm", "test",
                    "spce_ty", "POINT",
                    "cntm", 5181
            );
            when(downloadMapper.selectDownloadLayerInfo(1L)).thenReturn(meta);
            when(dataRepository.selectGeometryColumnName("iccom.t")).thenReturn("geom");
            when(fileFormatConverter.sanitizeFileName("test")).thenReturn("test");

            assertThatThrownBy(() -> downloadService.download(1L, "TASK", "pdf", "col1"))
                    .isInstanceOf(BusinessException.class)
                    .hasMessageContaining("지원하지 않는 포맷");
        }
    }

    @Nested
    @DisplayName("download — 컬럼 분기")
    class ColumnResolution {

        @Test
        @DisplayName("columns 파라미터가 null이면 DB에서 컬럼 목록 조회")
        void nullColumnsQueryFromDb() {
            Map<String, Object> meta = Map.of(
                    "lyr_phys_nm", "iccom.t",
                    "task_lyr_nm", "test",
                    "spce_ty", "POINT",
                    "cntm", 5181
            );
            when(downloadMapper.selectDownloadLayerInfo(1L)).thenReturn(meta);
            when(downloadMapper.selectTableColumns("iccom.t"))
                    .thenReturn(List.of(Map.of("column_name", "col1"), Map.of("column_name", "col2")));
            when(dataRepository.selectGeometryColumnName("iccom.t")).thenReturn("geom");
            when(fileFormatConverter.sanitizeFileName("test")).thenReturn("test");

            // 지원하지 않는 포맷으로 강제 오류 발생시켜 분기만 검증
            assertThatThrownBy(() -> downloadService.download(1L, "TASK", "UNKNOWN", null))
                    .isInstanceOf(BusinessException.class);
            verify(downloadMapper).selectTableColumns("iccom.t");
        }
    }
}
