package incheon.product.geoview3d.data3d.service.impl;

import incheon.product.common.geo3d.ExternalApiClient;
import incheon.product.common.geo3d.GeoView3DProperties;
import incheon.product.geoview3d.data3d.mapper.Data3DMapper;
import incheon.product.geoview3d.data3d.vo.Data3DModelVO;
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.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * Data3DServiceImpl 단위 테스트.
 * 3D 데이터 CRUD 및 GIS Manager 연동 로직을 검증한다.
 */
@ExtendWith(MockitoExtension.class)
class Data3DServiceImplTest {

    @InjectMocks
    private Data3DServiceImpl data3DService;

    @Mock
    private Data3DMapper data3DMapper;

    @Mock
    private GeoView3DProperties properties;

    @Mock
    private ExternalApiClient externalApiClient;

    private GeoView3DProperties.Data3d data3dConfig;
    private GeoView3DProperties.GisManager gisManagerConfig;

    @BeforeEach
    void setUp() {
        data3dConfig = new GeoView3DProperties.Data3d();
        data3dConfig.setModelTable("icsgp.dgtl_pair_mdl");

        gisManagerConfig = new GeoView3DProperties.GisManager();
        gisManagerConfig.setBuildUrl("http://gis-manager:8080");

        lenient().when(properties.getData3d()).thenReturn(data3dConfig);
        lenient().when(properties.getGisManager()).thenReturn(gisManagerConfig);
    }

    @Nested
    @DisplayName("getList — 목록 조회")
    class GetList {

        @Test
        @DisplayName("페이징 파라미터를 계산하고 mapper에 위임한다")
        void delegatesToMapper() {
            Data3DModelVO vo = new Data3DModelVO();
            vo.setPageIndex(2);
            vo.setPageUnit(10);

            List<Data3DModelVO> expected = List.of(new Data3DModelVO());
            when(data3DMapper.selectList(vo)).thenReturn(expected);

            List<Data3DModelVO> result = data3DService.getList(vo);

            assertThat(result).isEqualTo(expected);
            assertThat(vo.getModelTable()).isEqualTo("icsgp.dgtl_pair_mdl");
            assertThat(vo.getSize()).isEqualTo(10);
            assertThat(vo.getOffset()).isEqualTo(10);
        }

        @Test
        @DisplayName("첫 페이지 offset은 0이다")
        void firstPageOffset() {
            Data3DModelVO vo = new Data3DModelVO();
            vo.setPageIndex(1);
            vo.setPageUnit(5);

            when(data3DMapper.selectList(vo)).thenReturn(List.of());

            data3DService.getList(vo);

            assertThat(vo.getOffset()).isEqualTo(0);
        }
    }

    @Nested
    @DisplayName("getById — 단건 조회")
    class GetById {

        @Test
        @DisplayName("mapper에 id, srvcSeCd, modelTable을 전달한다")
        void delegatesToMapper() {
            Data3DModelVO expected = new Data3DModelVO();
            expected.setDgtlPairMdlId(1);
            when(data3DMapper.selectById(1, "2", "icsgp.dgtl_pair_mdl")).thenReturn(expected);

            Data3DModelVO result = data3DService.getById(1, "2");

            assertThat(result).isEqualTo(expected);
            verify(data3DMapper).selectById(1, "2", "icsgp.dgtl_pair_mdl");
        }
    }

    @Nested
    @DisplayName("create — 등록 + GIS Manager 연동")
    class Create {

        @Test
        @DisplayName("insert 후 GIS Manager에 asset 생성 요청을 보낸다")
        void insertAndCallGisManager() {
            Data3DModelVO vo = new Data3DModelVO();
            vo.setDgtlPairMdlNm("테스트 모델");
            vo.setDgtlPairMdlSrvcNm("test_service");

            when(externalApiClient.exchangeForGis(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(String.class)))
                    .thenReturn(ResponseEntity.ok("OK"));

            Data3DModelVO result = data3DService.create(vo);

            verify(data3DMapper).insert(vo);
            verify(externalApiClient).exchangeForGis(
                    eq("http://gis-manager:8080/api/storage/asset"),
                    eq(HttpMethod.POST),
                    any(HttpEntity.class),
                    eq(String.class)
            );
            assertThat(result.getModelTable()).isEqualTo("icsgp.dgtl_pair_mdl");
        }

        @Test
        @DisplayName("buildUrl이 비어있으면 GIS Manager 호출을 건너뛴다")
        void skipGisManagerWhenNoBuildUrl() {
            gisManagerConfig.setBuildUrl("");

            Data3DModelVO vo = new Data3DModelVO();
            data3DService.create(vo);

            verify(data3DMapper).insert(vo);
            verifyNoInteractions(externalApiClient);
        }

        @Test
        @DisplayName("GIS Manager 호출 실패 시 예외를 삼키고 insert는 유지된다")
        void gisManagerFailureDoesNotRollback() {
            Data3DModelVO vo = new Data3DModelVO();
            vo.setDgtlPairMdlId(99);

            when(externalApiClient.exchangeForGis(anyString(), eq(HttpMethod.POST), any(), eq(String.class)))
                    .thenThrow(new RuntimeException("GIS Manager 연결 실패"));

            Data3DModelVO result = data3DService.create(vo);

            verify(data3DMapper).insert(vo);
            assertThat(result).isNotNull();
        }
    }

    @Nested
    @DisplayName("update — 수정")
    class Update {

        @Test
        @DisplayName("mapper.update에 위임하고 modelTable을 주입한다")
        void delegatesToMapper() {
            Data3DModelVO vo = new Data3DModelVO();
            when(data3DMapper.update(vo)).thenReturn(1);

            int result = data3DService.update(vo);

            assertThat(result).isEqualTo(1);
            assertThat(vo.getModelTable()).isEqualTo("icsgp.dgtl_pair_mdl");
        }
    }

    @Nested
    @DisplayName("delete — 삭제 + GIS Manager 연동")
    class Delete {

        @Test
        @DisplayName("mapper.delete 후 GIS Manager에 asset 삭제 요청을 보낸다")
        void deleteAndCallGisManager() {
            when(data3DMapper.delete(1, "2", "icsgp.dgtl_pair_mdl")).thenReturn(1);
            when(externalApiClient.exchangeForGis(anyString(), eq(HttpMethod.DELETE), isNull(), eq(String.class)))
                    .thenReturn(ResponseEntity.ok("OK"));

            int result = data3DService.delete(1, "2");

            assertThat(result).isEqualTo(1);
            verify(externalApiClient).exchangeForGis(
                    eq("http://gis-manager:8080/api/storage/asset/1"),
                    eq(HttpMethod.DELETE),
                    isNull(),
                    eq(String.class)
            );
        }

        @Test
        @DisplayName("GIS Manager 호출 실패 시 예외를 삼키고 delete 결과를 반환한다")
        void gisManagerFailureDoesNotAffectResult() {
            when(data3DMapper.delete(2, "2", "icsgp.dgtl_pair_mdl")).thenReturn(1);
            when(externalApiClient.exchangeForGis(anyString(), eq(HttpMethod.DELETE), isNull(), eq(String.class)))
                    .thenThrow(new RuntimeException("연결 실패"));

            int result = data3DService.delete(2, "2");

            assertThat(result).isEqualTo(1);
        }
    }

    @Nested
    @DisplayName("updateStatus — 상태 변경")
    class UpdateStatus {

        @Test
        @DisplayName("mapper.updateStatus에 위임한다")
        void delegatesToMapper() {
            when(data3DMapper.updateStatus(1, "DONE", "service1", "icsgp.dgtl_pair_mdl")).thenReturn(1);

            int result = data3DService.updateStatus(1, "DONE", "service1");

            assertThat(result).isEqualTo(1);
            verify(data3DMapper).updateStatus(1, "DONE", "service1", "icsgp.dgtl_pair_mdl");
        }
    }
}
