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

import incheon.com.cmm.exception.BusinessException;
import incheon.com.cmm.exception.EntityNotFoundException;
import incheon.product.common.config.GeoViewProperties;
import incheon.product.geoview2d.layer.domain.LayerType;
import incheon.product.geoview2d.layer.mapper.LayerMapper;
import incheon.product.geoview2d.layer.vo.FeatureVO;
import incheon.product.geoview2d.layer.vo.LayerEditRequestVO;
import incheon.product.geoview2d.layer.vo.LayerEditResultVO;
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 java.util.ArrayList;
import java.util.HashMap;
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.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * LayerEditServiceImpl 단위 테스트.
 * 메타데이터 분기, 물리명 검증, 배치 처리를 검증한다.
 */
@ExtendWith(MockitoExtension.class)
class LayerEditServiceImplTest {

    @InjectMocks
    private LayerEditServiceImpl layerEditService;

    @Mock
    private LayerMapper layerMapper;

    @Mock
    private GeoViewProperties geoViewProperties;

    // ========== editLayer — 검증 분기 ==========

    @Nested
    @DisplayName("editLayer — 메타데이터 검증")
    class MetadataValidation {

        @Test
        @DisplayName("TASK 유형 메타데이터가 null이면 EntityNotFoundException")
        void nullTaskMeta() {
            LayerEditRequestVO request = createRequest("layer1", LayerType.TASK);
            when(layerMapper.selectTaskLayerMetadata("layer1")).thenReturn(null);

            assertThatThrownBy(() -> layerEditService.editLayer(request, "user1"))
                    .isInstanceOf(EntityNotFoundException.class)
                    .hasMessageContaining("레이어를 찾을 수 없습니다");
        }

        @Test
        @DisplayName("USER 유형 메타데이터가 null이면 EntityNotFoundException")
        void nullUserMeta() {
            LayerEditRequestVO request = createRequest("layer2", LayerType.USER);
            when(layerMapper.selectUserLayerMetadata("layer2")).thenReturn(null);

            assertThatThrownBy(() -> layerEditService.editLayer(request, "user1"))
                    .isInstanceOf(EntityNotFoundException.class)
                    .hasMessageContaining("레이어를 찾을 수 없습니다");
        }

        @Test
        @DisplayName("TASK 유형이면 selectTaskLayerMetadata 호출")
        void taskTypeCallsTaskMapper() {
            LayerEditRequestVO request = createRequest("l1", LayerType.TASK);
            when(layerMapper.selectTaskLayerMetadata("l1")).thenReturn(null);

            assertThatThrownBy(() -> layerEditService.editLayer(request, "user"))
                    .isInstanceOf(EntityNotFoundException.class);
            verify(layerMapper).selectTaskLayerMetadata("l1");
            verify(layerMapper, never()).selectUserLayerMetadata(anyString());
        }

        @Test
        @DisplayName("USER 유형이면 selectUserLayerMetadata 호출")
        void userTypeCallsUserMapper() {
            LayerEditRequestVO request = createRequest("l2", LayerType.USER);
            when(layerMapper.selectUserLayerMetadata("l2")).thenReturn(null);

            assertThatThrownBy(() -> layerEditService.editLayer(request, "user"))
                    .isInstanceOf(EntityNotFoundException.class);
            verify(layerMapper).selectUserLayerMetadata("l2");
            verify(layerMapper, never()).selectTaskLayerMetadata(anyString());
        }
    }

    @Nested
    @DisplayName("editLayer — 물리명 형식 검증")
    class PhysicalNameValidation {

        @Test
        @DisplayName("lyr_phys_nm이 null이면 BusinessException")
        void nullPhysicalName() {
            LayerEditRequestVO request = createRequest("l1", LayerType.TASK);
            Map<String, Object> meta = new HashMap<>();
            meta.put("lyr_phys_nm", null);
            when(layerMapper.selectTaskLayerMetadata("l1")).thenReturn(meta);

            assertThatThrownBy(() -> layerEditService.editLayer(request, "user"))
                    .isInstanceOf(BusinessException.class)
                    .hasMessageContaining("스키마.테이블");
        }

        @Test
        @DisplayName("lyr_phys_nm에 '.'가 없으면 BusinessException")
        void noDotInPhysicalName() {
            LayerEditRequestVO request = createRequest("l1", LayerType.TASK);
            Map<String, Object> meta = new HashMap<>();
            meta.put("lyr_phys_nm", "table_without_schema");
            when(layerMapper.selectTaskLayerMetadata("l1")).thenReturn(meta);

            assertThatThrownBy(() -> layerEditService.editLayer(request, "user"))
                    .isInstanceOf(BusinessException.class)
                    .hasMessageContaining("스키마.테이블");
        }
    }

    @Nested
    @DisplayName("editLayer — 빈 변경사항 처리")
    class EmptyChanges {

        @Test
        @DisplayName("변경사항이 모두 비어있으면 결과 카운트 0")
        void allEmptyChanges() {
            LayerEditRequestVO request = createRequest("l1", LayerType.TASK);

            Map<String, Object> meta = new HashMap<>();
            meta.put("lyr_phys_nm", "iccom.test_table");
            meta.put("cntm", 5181);
            when(layerMapper.selectTaskLayerMetadata("l1")).thenReturn(meta);

            GeoViewProperties.Layer layer = new GeoViewProperties.Layer();
            when(geoViewProperties.getLayer()).thenReturn(layer);

            LayerEditResultVO result = layerEditService.editLayer(request, "user1");

            assertThat(result.getLayerId()).isEqualTo("l1");
            assertThat(result.getProcessed().getAdded()).isZero();
            assertThat(result.getProcessed().getModified()).isZero();
            assertThat(result.getProcessed().getDeleted()).isZero();
        }
    }

    @Nested
    @DisplayName("editLayer — Delete 배치 처리")
    class DeleteBatch {

        @Test
        @DisplayName("삭제 피처가 있으면 deleteLayerFeatures 호출")
        void deleteFeatures() {
            LayerEditRequestVO request = createRequest("l1", LayerType.TASK);
            request.getChanges().setDeleted(List.of(1, 2, 3));

            Map<String, Object> meta = new HashMap<>();
            meta.put("lyr_phys_nm", "iccom.test_table");
            meta.put("cntm", 5181);
            when(layerMapper.selectTaskLayerMetadata("l1")).thenReturn(meta);

            GeoViewProperties.Layer layer = new GeoViewProperties.Layer();
            when(geoViewProperties.getLayer()).thenReturn(layer);
            when(layerMapper.deleteLayerFeatures(any())).thenReturn(3);
            when(layerMapper.checkHistoryTableExists("iccom", "test_table_h")).thenReturn(false);

            LayerEditResultVO result = layerEditService.editLayer(request, "user1");
            assertThat(result.getProcessed().getDeleted()).isEqualTo(3);
            verify(layerMapper).deleteLayerFeatures(any());
        }
    }

    @Nested
    @DisplayName("editLayer — Add 배치 처리")
    class AddBatch {

        @Test
        @DisplayName("추가 피처가 있으면 batchInsertLayerFeatures 호출")
        void addFeatures() {
            LayerEditRequestVO request = createRequest("l1", LayerType.TASK);
            FeatureVO feature = new FeatureVO();
            Map<String, Object> geometry = new HashMap<>();
            geometry.put("type", "Point");
            geometry.put("coordinates", new ArrayList<>(List.of(126.0, 37.0)));
            feature.setGeometry(geometry);
            feature.setProperties(new HashMap<>(Map.of("name", "test")));
            request.getChanges().setAdded(new ArrayList<>(List.of(feature)));

            Map<String, Object> meta = new HashMap<>();
            meta.put("lyr_phys_nm", "iccom.test_table");
            meta.put("cntm", 5181);
            when(layerMapper.selectTaskLayerMetadata("l1")).thenReturn(meta);

            GeoViewProperties.Layer layer = new GeoViewProperties.Layer();
            when(geoViewProperties.getLayer()).thenReturn(layer);
            when(layerMapper.selectTableColumns("iccom", "test_table")).thenReturn(new ArrayList<>());
            when(layerMapper.batchInsertLayerFeatures(any())).thenReturn(new ArrayList<>(List.of(new HashMap<>(Map.of("gid", 1)))));
            when(layerMapper.checkHistoryTableExists("iccom", "test_table_h")).thenReturn(false);

            LayerEditResultVO result = layerEditService.editLayer(request, "user1");
            assertThat(result.getProcessed().getAdded()).isEqualTo(1);
            verify(layerMapper).batchInsertLayerFeatures(any());
        }
    }

    // ========== Helper ==========

    private LayerEditRequestVO createRequest(String layerId, LayerType layerType) {
        LayerEditRequestVO request = new LayerEditRequestVO();
        request.setLayerId(layerId);
        request.setLayerType(layerType);
        request.setChanges(new LayerEditRequestVO.ChangesVO());
        return request;
    }
}
