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

import incheon.com.cmm.exception.BusinessException;
import incheon.com.cmm.exception.EntityNotFoundException;
import incheon.product.common.config.GeoViewProperties;
import incheon.product.geoview2d.flight.mapper.FlightPhotoMapper;
import incheon.product.geoview2d.flight.vo.FlightPhotoDownloadRequestVO;
import incheon.product.geoview2d.flight.vo.FlightPhotoLayerVO;
import incheon.product.geoview2d.flight.vo.FlightPhotoSearchVO;
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.List;
import java.util.Set;

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

/**
 * FlightPhotoServiceImpl 단위 테스트.
 * 항공 사진 조회 및 다운로드 요청 검증 로직을 테스트한다.
 */
@ExtendWith(MockitoExtension.class)
class FlightPhotoServiceImplTest {

    @InjectMocks
    private FlightPhotoServiceImpl flightPhotoService;

    @Mock
    private FlightPhotoMapper flightPhotoMapper;

    @Mock
    private GeoViewProperties geoViewProperties;

    private GeoViewProperties.Layer layerConfig;

    @BeforeEach
    void setUp() {
        layerConfig = new GeoViewProperties.Layer();
        layerConfig.setImageMaxSize(4000);
        layerConfig.setImageDefaultSize(1024);

        lenient().when(geoViewProperties.getLayer()).thenReturn(layerConfig);
    }

    @Nested
    @DisplayName("조회 메서드")
    class ReadMethods {

        @Test
        @DisplayName("getFlightPhotoLayerList는 mapper에 위임한다")
        void layerList() {
            FlightPhotoSearchVO searchVO = new FlightPhotoSearchVO();
            List<FlightPhotoLayerVO> expected = List.of(new FlightPhotoLayerVO());
            when(flightPhotoMapper.selectFlightPhotoLayerList(searchVO, true)).thenReturn(expected);

            List<FlightPhotoLayerVO> result = flightPhotoService.getFlightPhotoLayerList(searchVO, true);

            assertThat(result).isEqualTo(expected);
        }

        @Test
        @DisplayName("getFlightPhotoLayerListTotCnt는 mapper에 위임한다")
        void layerListCount() {
            FlightPhotoSearchVO searchVO = new FlightPhotoSearchVO();
            when(flightPhotoMapper.selectFlightPhotoLayerListTotCnt(searchVO, false)).thenReturn(5L);

            long result = flightPhotoService.getFlightPhotoLayerListTotCnt(searchVO, false);

            assertThat(result).isEqualTo(5L);
        }

        @Test
        @DisplayName("getFlightPhotoLayerById는 mapper에 위임한다")
        void layerById() {
            FlightPhotoLayerVO expected = new FlightPhotoLayerVO();
            when(flightPhotoMapper.selectFlightPhotoLayerById(1)).thenReturn(expected);

            assertThat(flightPhotoService.getFlightPhotoLayerById(1)).isEqualTo(expected);
        }
    }

    @Nested
    @DisplayName("validateDownloadRequest — 다운로드 요청 검증")
    class ValidateDownloadRequest {

        @Test
        @DisplayName("bbox가 유효하지 않으면 BusinessException 발생")
        void invalidBbox() {
            FlightPhotoDownloadRequestVO req = createValidRequest();
            req.setMinx(10.0);
            req.setMaxx(5.0);  // max < min

            assertThatThrownBy(() -> flightPhotoService.validateDownloadRequest(req))
                    .isInstanceOf(BusinessException.class)
                    .hasMessageContaining("범위");
        }

        @Test
        @DisplayName("width/height가 maxSize를 초과하면 BusinessException 발생")
        void exceedsMaxSize() {
            FlightPhotoDownloadRequestVO req = createValidRequest();
            req.setWidth(5000);
            req.setHeight(5000);

            assertThatThrownBy(() -> flightPhotoService.validateDownloadRequest(req))
                    .isInstanceOf(BusinessException.class)
                    .hasMessageContaining("넘을 수 없습니다");
        }

        @Test
        @DisplayName("존재하지 않는 레이어 ID가 있으면 EntityNotFoundException 발생")
        void layerNotFound() {
            FlightPhotoDownloadRequestVO req = createValidRequest();
            req.setLayerIds(Set.of(1, 2));
            req.setWidth(1024);
            req.setHeight(1024);

            FlightPhotoLayerVO layer1 = new FlightPhotoLayerVO();
            layer1.setFlightPhotoLyrId(1);
            when(flightPhotoMapper.selectFlightPhotoLayerInIds(Set.of(1, 2))).thenReturn(List.of(layer1));

            assertThatThrownBy(() -> flightPhotoService.validateDownloadRequest(req))
                    .isInstanceOf(EntityNotFoundException.class)
                    .hasMessageContaining("2");
        }

        @Test
        @DisplayName("모든 검증 통과 시 예외 없음")
        void validRequest() {
            FlightPhotoDownloadRequestVO req = createValidRequest();
            req.setWidth(1024);
            req.setHeight(1024);
            req.setLayerIds(Set.of(1));

            FlightPhotoLayerVO layer = new FlightPhotoLayerVO();
            layer.setFlightPhotoLyrId(1);
            when(flightPhotoMapper.selectFlightPhotoLayerInIds(Set.of(1))).thenReturn(List.of(layer));

            flightPhotoService.validateDownloadRequest(req);

            verify(flightPhotoMapper).selectFlightPhotoLayerInIds(Set.of(1));
        }

        private FlightPhotoDownloadRequestVO createValidRequest() {
            FlightPhotoDownloadRequestVO req = new FlightPhotoDownloadRequestVO();
            req.setMinx(126.0);
            req.setMiny(37.0);
            req.setMaxx(127.0);
            req.setMaxy(38.0);
            req.setSrid(5186);
            req.setLayerIds(Set.of(1));
            return req;
        }
    }
}
