package gc.mda.signal_batch.domain.vessel.service;

import gc.mda.signal_batch.global.websocket.dto.MergedVesselTrack;
import gc.mda.signal_batch.global.websocket.dto.VesselTrackData;
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.jdbc.core.JdbcTemplate;
import org.springframework.test.util.ReflectionTestUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

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

@ExtendWith(MockitoExtension.class)
class VesselTrackMergerTest {

    @InjectMocks
    private VesselTrackMerger merger;

    @Mock
    private JdbcTemplate queryJdbcTemplate;

    @BeforeEach
    void setUp() {
        ReflectionTestUtils.setField(merger, "enableMergerFiltering", false);
        ReflectionTestUtils.setField(merger, "largeGapThresholdHours", 6);
    }

    private VesselTrackData createSegment(String mmsi, LocalDateTime start, LocalDateTime end,
                                           String geom, double distance, int pointCount) {
        VesselTrackData data = new VesselTrackData();
        data.setMmsi(mmsi);
        data.setNationalCode(mmsi.length() >= 3 ? mmsi.substring(0, 3) : "000");
        data.setTrackGeom(geom);
        data.setDistanceNm(distance);
        data.setAvgSpeed(10.0);
        data.setMaxSpeed(15.0);
        data.setPointCount(pointCount);
        data.setStartTime(start);
        data.setEndTime(end);
        return data;
    }

    @Nested
    @DisplayName("mergeTracksByVessel")
    class MergeTracksByVessel {

        @Test
        @DisplayName("단일 선박 단일 세그먼트 병합")
        void singleVessel_singleSegment() {
            LocalDateTime t1 = LocalDateTime.of(2025, 1, 1, 10, 0);
            LocalDateTime t2 = LocalDateTime.of(2025, 1, 1, 10, 5);
            String geom = "LINESTRING M(127.0 34.0 1000, 127.01 34.01 1300)";

            when(queryJdbcTemplate.queryForObject(anyString(), eq(Double.class), anyString()))
                    .thenReturn(1.5);

            List<VesselTrackData> tracks = List.of(
                    createSegment("440331240", t1, t2, geom, 1.5, 2)
            );

            List<MergedVesselTrack> result = merger.mergeTracksByVessel(tracks);

            assertThat(result).hasSize(1);
            assertThat(result.get(0).getMmsi()).isEqualTo("440331240");
            assertThat(result.get(0).getNationalCode()).isEqualTo("440");
            assertThat(result.get(0).getMergedTrackGeom()).isEqualTo(geom);
        }

        @Test
        @DisplayName("다중 선박 각각 병합")
        void multipleVessels_merged() {
            LocalDateTime t1 = LocalDateTime.of(2025, 1, 1, 10, 0);
            LocalDateTime t2 = LocalDateTime.of(2025, 1, 1, 10, 5);
            String geom1 = "LINESTRING M(127.0 34.0 1000, 127.01 34.01 1300)";
            String geom2 = "LINESTRING M(126.0 35.0 1000, 126.01 35.01 1300)";

            when(queryJdbcTemplate.queryForObject(anyString(), eq(Double.class), anyString()))
                    .thenReturn(1.5);

            List<VesselTrackData> tracks = List.of(
                    createSegment("440111111", t1, t2, geom1, 1.0, 2),
                    createSegment("440222222", t1, t2, geom2, 2.0, 2)
            );

            List<MergedVesselTrack> result = merger.mergeTracksByVessel(tracks);

            assertThat(result).hasSize(2);
        }

        @Test
        @DisplayName("빈 geometry 세그먼트 필터링")
        void emptyGeometry_filtered() {
            LocalDateTime t1 = LocalDateTime.of(2025, 1, 1, 10, 0);
            LocalDateTime t2 = LocalDateTime.of(2025, 1, 1, 10, 5);

            List<VesselTrackData> tracks = List.of(
                    createSegment("440331240", t1, t2, null, 0, 0),
                    createSegment("440331240", t1, t2, "LINESTRING EMPTY", 0, 0),
                    createSegment("440331240", t1, t2, "GEOMETRYCOLLECTION EMPTY", 0, 0)
            );

            List<MergedVesselTrack> result = merger.mergeTracksByVessel(tracks);

            assertThat(result).isEmpty();
        }

        @Test
        @DisplayName("다중 세그먼트 병합 시 시간순 정렬 및 시간 범위")
        void multipleSegments_sortedByTime() {
            LocalDateTime t1 = LocalDateTime.of(2025, 1, 1, 10, 0);
            LocalDateTime t2 = LocalDateTime.of(2025, 1, 1, 10, 5);
            LocalDateTime t3 = LocalDateTime.of(2025, 1, 1, 10, 10);
            LocalDateTime t4 = LocalDateTime.of(2025, 1, 1, 10, 15);

            String geom1 = "LINESTRING M(127.0 34.0 0, 127.01 34.01 300)";
            String geom2 = "LINESTRING M(127.02 34.02 0, 127.03 34.03 300)";

            when(queryJdbcTemplate.queryForObject(anyString(), eq(Double.class), anyString()))
                    .thenReturn(3.0);

            List<VesselTrackData> tracks = List.of(
                    createSegment("440331240", t1, t2, geom1, 1.5, 2),
                    createSegment("440331240", t3, t4, geom2, 1.5, 2)
            );

            List<MergedVesselTrack> result = merger.mergeTracksByVessel(tracks);

            assertThat(result).hasSize(1);
            MergedVesselTrack merged = result.get(0);
            assertThat(merged.getMergedTrackGeom()).startsWith("LINESTRING M(");
            assertThat(merged.getStartTime()).isEqualTo(t1);
            assertThat(merged.getEndTime()).isEqualTo(t4);
            assertThat(merged.getTimeBuckets()).hasSize(2);
        }

        @Test
        @DisplayName("PostGIS 거리 계산 실패 시 fallback 합산")
        void postgisFails_fallbackToSum() {
            LocalDateTime t1 = LocalDateTime.of(2025, 1, 1, 10, 0);
            LocalDateTime t2 = LocalDateTime.of(2025, 1, 1, 10, 5);
            String geom = "LINESTRING M(127.0 34.0 1000, 127.01 34.01 1300)";

            when(queryJdbcTemplate.queryForObject(anyString(), eq(Double.class), anyString()))
                    .thenThrow(new RuntimeException("PostGIS unavailable"));

            List<VesselTrackData> tracks = List.of(
                    createSegment("440331240", t1, t2, geom, 1.5, 2)
            );

            List<MergedVesselTrack> result = merger.mergeTracksByVessel(tracks);

            assertThat(result).hasSize(1);
            assertThat(result.get(0).getTotalDistanceNm()).isEqualTo(1.5);
        }

        @Test
        @DisplayName("MMSI 3자리 미만 시 nationalCode='000'")
        void shortMmsi_nationalCode000() {
            LocalDateTime t1 = LocalDateTime.of(2025, 1, 1, 10, 0);
            LocalDateTime t2 = LocalDateTime.of(2025, 1, 1, 10, 5);
            String geom = "LINESTRING M(127.0 34.0 1000, 127.01 34.01 1300)";

            when(queryJdbcTemplate.queryForObject(anyString(), eq(Double.class), anyString()))
                    .thenReturn(1.0);

            VesselTrackData segment = createSegment("AB", t1, t2, geom, 1.0, 2);
            segment.setMmsi("AB");

            List<MergedVesselTrack> result = merger.mergeTracksByVessel(List.of(segment));

            assertThat(result).hasSize(1);
            assertThat(result.get(0).getNationalCode()).isEqualTo("000");
        }
    }

    @Nested
    @DisplayName("large gap 필터링")
    class LargeGapFiltering {

        @Test
        @DisplayName("enableMergerFiltering=true 시 큰 gap 세그먼트 제거")
        void largeGap_filtered() {
            ReflectionTestUtils.setField(merger, "enableMergerFiltering", true);

            LocalDateTime t1 = LocalDateTime.of(2025, 1, 1, 10, 0);
            LocalDateTime t2 = LocalDateTime.of(2025, 1, 1, 10, 5);
            LocalDateTime t3 = LocalDateTime.of(2025, 1, 1, 17, 5);
            LocalDateTime t4 = LocalDateTime.of(2025, 1, 1, 17, 10);

            String geom1 = "LINESTRING M(127.0 34.0 0, 127.01 34.01 300)";
            String geom2 = "LINESTRING M(127.02 34.02 0, 127.03 34.03 300)";

            when(queryJdbcTemplate.queryForObject(anyString(), eq(Double.class), anyString()))
                    .thenReturn(1.5);

            List<VesselTrackData> tracks = new ArrayList<>();
            tracks.add(createSegment("440331240", t1, t2, geom1, 1.5, 2));
            tracks.add(createSegment("440331240", t3, t4, geom2, 1.5, 2));

            List<MergedVesselTrack> result = merger.mergeTracksByVessel(tracks);

            assertThat(result).hasSize(1);
            assertThat(result.get(0).getTimeBuckets()).hasSize(1);
        }

        @Test
        @DisplayName("enableMergerFiltering=false 시 gap 무시하고 모두 포함")
        void largeGap_notFiltered() {
            ReflectionTestUtils.setField(merger, "enableMergerFiltering", false);

            LocalDateTime t1 = LocalDateTime.of(2025, 1, 1, 10, 0);
            LocalDateTime t2 = LocalDateTime.of(2025, 1, 1, 10, 5);
            LocalDateTime t3 = LocalDateTime.of(2025, 1, 1, 17, 5);
            LocalDateTime t4 = LocalDateTime.of(2025, 1, 1, 17, 10);

            String geom1 = "LINESTRING M(127.0 34.0 0, 127.01 34.01 300)";
            String geom2 = "LINESTRING M(127.02 34.02 0, 127.03 34.03 300)";

            when(queryJdbcTemplate.queryForObject(anyString(), eq(Double.class), anyString()))
                    .thenReturn(3.0);

            List<VesselTrackData> tracks = new ArrayList<>();
            tracks.add(createSegment("440331240", t1, t2, geom1, 1.5, 2));
            tracks.add(createSegment("440331240", t3, t4, geom2, 1.5, 2));

            List<MergedVesselTrack> result = merger.mergeTracksByVessel(tracks);

            assertThat(result).hasSize(1);
            assertThat(result.get(0).getTimeBuckets()).hasSize(2);
        }
    }
}
