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

import gc.mda.signal_batch.domain.vessel.dto.CompactVesselTrack;
import gc.mda.signal_batch.domain.vessel.dto.TrackResponse;
import gc.mda.signal_batch.domain.vessel.dto.VesselTracksRequest;
import gc.mda.signal_batch.global.exception.QueryTimeoutException;
import gc.mda.signal_batch.global.util.TrackConverter;
import gc.mda.signal_batch.global.websocket.service.ActiveQueryManager;
import gc.mda.signal_batch.global.websocket.service.CacheTrackSimplifier;
import gc.mda.signal_batch.global.websocket.service.DailyTrackCacheManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * GIS 서비스 V2 - CompactVesselTrack 기반 응답
 * WebSocket API와 동일한 응답 구조 제공
 *
 * - Semaphore 기반 동시성 제어 (ActiveQueryManager 공유)
 * - POST /vessels: DailyTrackCacheManager 캐시 우선 조회
 * - 2단계 간소화 파이프라인 (표준 간소화 + 포인트 버짓 강제)
 */
@Slf4j
@Service
public class GisServiceV2 {

    private final DataSource queryDataSource;
    private final ActiveQueryManager activeQueryManager;
    private final DailyTrackCacheManager dailyTrackCacheManager;
    private final CacheTrackSimplifier cacheTrackSimplifier;
    private final GisService gisService;

    @Value("${rest.v2.query.timeout-seconds:30}")
    private int restQueryTimeout;

    @Value("${rest.v2.query.max-total-points:500000}")
    private int maxTotalPoints;

    // 선박 정보 캐시 (TTL: 1시간)
    private final ConcurrentHashMap<String, VesselInfoCache> vesselInfoCache = new ConcurrentHashMap<>();
    private static final long VESSEL_CACHE_TTL = 3600_000; // 1시간

    public GisServiceV2(@Qualifier("queryDataSource") DataSource queryDataSource,
                        ActiveQueryManager activeQueryManager,
                        DailyTrackCacheManager dailyTrackCacheManager,
                        CacheTrackSimplifier cacheTrackSimplifier,
                        GisService gisService) {
        this.queryDataSource = queryDataSource;
        this.activeQueryManager = activeQueryManager;
        this.dailyTrackCacheManager = dailyTrackCacheManager;
        this.cacheTrackSimplifier = cacheTrackSimplifier;
        this.gisService = gisService;
    }

    /**
     * 해구별 선박 항적 조회 (V2 - CompactVesselTrack 반환)
     */
    public List<CompactVesselTrack> getHaeguTracks(Integer haeguNo, int minutes, boolean filterByIntegration) {
        String queryId = "rest-haegu-" + haeguNo + "-" + UUID.randomUUID().toString().substring(0, 8);
        boolean slotAcquired = false;

        try {
            slotAcquired = acquireSlotWithWait(queryId);

            JdbcTemplate jdbcTemplate = new JdbcTemplate(queryDataSource);
            List<TrackResponse> rawTracks = new ArrayList<>();

            LocalDateTime now = LocalDateTime.now();
            LocalDateTime startTime = now.minusMinutes(minutes);

            if (minutes > 60) {
                LocalDateTime currentHour = now.withMinute(0).withSecond(0).withNano(0);

                if (minutes <= 1440) {
                    String hourlySql = """
                        SELECT DISTINCT t.mmsi, t.time_bucket,
                               public.ST_AsText(t.track_geom) as track_geom,
                               t.distance_nm, t.avg_speed, t.max_speed, t.point_count
                        FROM signal.t_vessel_tracks_hourly t
                        WHERE EXISTS (
                            SELECT 1 FROM signal.t_grid_vessel_tracks g
                            WHERE g.mmsi = t.mmsi
                                AND g.haegu_no = %d
                                AND g.time_bucket >= '%s'
                        )
                        AND t.time_bucket >= '%s'
                        AND t.time_bucket < '%s'
                        ORDER BY t.mmsi, t.time_bucket
                    """.formatted(haeguNo, startTime, startTime, currentHour);

                    rawTracks.addAll(jdbcTemplate.query(hourlySql, this::mapTrackResponse));
                }

                String recentSql = """
                    SELECT DISTINCT t.mmsi, t.time_bucket,
                           public.ST_AsText(t.track_geom) as track_geom,
                           t.distance_nm, t.avg_speed, t.max_speed, t.point_count
                    FROM signal.t_vessel_tracks_5min t
                    WHERE EXISTS (
                        SELECT 1 FROM signal.t_grid_vessel_tracks g
                        WHERE g.mmsi = t.mmsi
                            AND g.haegu_no = %d
                            AND g.time_bucket >= '%s'
                    )
                    AND t.time_bucket >= '%s'
                    ORDER BY t.mmsi, t.time_bucket
                """.formatted(haeguNo, startTime, currentHour);

                rawTracks.addAll(jdbcTemplate.query(recentSql, this::mapTrackResponse));

            } else {
                String sql = """
                    SELECT DISTINCT t.mmsi, t.time_bucket,
                           public.ST_AsText(t.track_geom) as track_geom,
                           t.distance_nm, t.avg_speed, t.max_speed, t.point_count
                    FROM signal.t_vessel_tracks_5min t
                    WHERE EXISTS (
                        SELECT 1 FROM signal.t_grid_vessel_tracks g
                        WHERE g.mmsi = t.mmsi
                            AND g.haegu_no = %d
                            AND g.time_bucket >= NOW() - INTERVAL '%d minutes'
                    )
                    AND t.time_bucket >= NOW() - INTERVAL '%d minutes'
                    ORDER BY t.mmsi, t.time_bucket
                """.formatted(haeguNo, minutes, minutes);

                rawTracks = jdbcTemplate.query(sql, this::mapTrackResponse);
            }

            List<CompactVesselTrack> result = TrackConverter.convert(rawTracks, this::getVesselInfo);
            result = applySimplificationPipeline(result);

            log.debug("V2 API: Fetched {} compact tracks for haegu {} in last {} minutes",
                    result.size(), haeguNo, minutes);

            return result;

        } finally {
            if (slotAcquired) {
                activeQueryManager.releaseQuerySlot(queryId);
                if (activeQueryManager.isHeapPressureHigh()) {
                    System.gc();
                }
            }
        }
    }

    /**
     * 영역별 선박 항적 조회 (V2 - CompactVesselTrack 반환)
     */
    public List<CompactVesselTrack> getAreaTracks(String areaId, int minutes, boolean filterByIntegration) {
        String queryId = "rest-area-" + areaId + "-" + UUID.randomUUID().toString().substring(0, 8);
        boolean slotAcquired = false;

        try {
            slotAcquired = acquireSlotWithWait(queryId);

            JdbcTemplate jdbcTemplate = new JdbcTemplate(queryDataSource);
            List<TrackResponse> rawTracks = new ArrayList<>();

            LocalDateTime now = LocalDateTime.now();
            LocalDateTime startTime = now.minusMinutes(minutes);

            if (minutes > 60) {
                LocalDateTime currentHour = now.withMinute(0).withSecond(0).withNano(0);

                if (minutes <= 1440) {
                    String hourlySql = """
                        SELECT DISTINCT t.mmsi, t.time_bucket,
                               public.ST_AsText(t.track_geom) as track_geom,
                               t.distance_nm, t.avg_speed, t.max_speed, t.point_count
                        FROM signal.t_vessel_tracks_hourly t
                        WHERE EXISTS (
                            SELECT 1 FROM signal.t_area_vessel_tracks a
                            WHERE a.mmsi = t.mmsi
                                AND a.area_id = '%s'
                                AND a.time_bucket >= '%s'
                        )
                        AND t.time_bucket >= '%s'
                        AND t.time_bucket < '%s'
                        ORDER BY t.mmsi, t.time_bucket
                    """.formatted(areaId, startTime, startTime, currentHour);

                    rawTracks.addAll(jdbcTemplate.query(hourlySql, this::mapTrackResponse));
                }

                String recentSql = """
                    SELECT DISTINCT t.mmsi, t.time_bucket,
                           public.ST_AsText(t.track_geom) as track_geom,
                           t.distance_nm, t.avg_speed, t.max_speed, t.point_count
                    FROM signal.t_vessel_tracks_5min t
                    WHERE EXISTS (
                        SELECT 1 FROM signal.t_area_vessel_tracks a
                        WHERE a.mmsi = t.mmsi
                            AND a.area_id = '%s'
                            AND a.time_bucket >= '%s'
                    )
                    AND t.time_bucket >= '%s'
                    ORDER BY t.mmsi, t.time_bucket
                """.formatted(areaId, startTime, currentHour);

                rawTracks.addAll(jdbcTemplate.query(recentSql, this::mapTrackResponse));

            } else {
                String sql = """
                    SELECT DISTINCT t.mmsi, t.time_bucket,
                           public.ST_AsText(t.track_geom) as track_geom,
                           t.distance_nm, t.avg_speed, t.max_speed, t.point_count
                    FROM signal.t_vessel_tracks_5min t
                    WHERE EXISTS (
                        SELECT 1 FROM signal.t_area_vessel_tracks a
                        WHERE a.mmsi = t.mmsi
                            AND a.area_id = '%s'
                            AND a.time_bucket >= NOW() - INTERVAL '%d minutes'
                    )
                    AND t.time_bucket >= NOW() - INTERVAL '%d minutes'
                    ORDER BY t.mmsi, t.time_bucket
                """.formatted(areaId, minutes, minutes);

                rawTracks = jdbcTemplate.query(sql, this::mapTrackResponse);
            }

            List<CompactVesselTrack> result = TrackConverter.convert(rawTracks, this::getVesselInfo);
            result = applySimplificationPipeline(result);

            log.debug("V2 API: Fetched {} compact tracks for area {} in last {} minutes",
                    result.size(), areaId, minutes);

            return result;

        } finally {
            if (slotAcquired) {
                activeQueryManager.releaseQuerySlot(queryId);
                if (activeQueryManager.isHeapPressureHigh()) {
                    System.gc();
                }
            }
        }
    }

    /**
     * 선박별 항적 조회 V2 (캐시 + Semaphore + 간소화)
     */
    public List<CompactVesselTrack> getVesselTracksV2(VesselTracksRequest request) {
        String queryId = "rest-vessels-" + UUID.randomUUID().toString().substring(0, 8);
        boolean slotAcquired = false;

        try {
            slotAcquired = acquireSlotWithWait(queryId);

            List<CompactVesselTrack> result;

            if (dailyTrackCacheManager.isEnabled() &&
                    dailyTrackCacheManager.getStatus() != DailyTrackCacheManager.CacheStatus.NOT_STARTED &&
                    dailyTrackCacheManager.getStatus() != DailyTrackCacheManager.CacheStatus.DISABLED) {

                result = queryWithCache(request);
            } else {
                result = gisService.getVesselTracks(request);
            }

            result = applySimplificationPipeline(result);

            log.debug("V2 API: Returned {} tracks for {} vessels (cache={})",
                    result.size(), request.getVessels().size(), dailyTrackCacheManager.isEnabled());

            return result;

        } finally {
            if (slotAcquired) {
                activeQueryManager.releaseQuerySlot(queryId);
                if (activeQueryManager.isHeapPressureHigh()) {
                    System.gc();
                }
            }
        }
    }

    // ── 캐시 조회 로직 ──

    private List<CompactVesselTrack> queryWithCache(VesselTracksRequest request) {
        LocalDateTime startTime = request.getStartTime();
        LocalDateTime endTime = request.getEndTime();

        DailyTrackCacheManager.SplitQueryResult split =
                dailyTrackCacheManager.splitQueryRange(startTime, endTime);

        List<CompactVesselTrack> allTracks = new ArrayList<>();

        Set<String> requestedMmsis = new HashSet<>(request.getVessels());

        // 1. 캐시에서 조회 (캐시된 날짜)
        if (split.hasCachedData()) {
            List<CompactVesselTrack> cachedTracks =
                    dailyTrackCacheManager.getCachedTracksMultipleDays(split.getCachedDates());

            int totalCachedCount = cachedTracks.size();
            List<CompactVesselTrack> filteredCached = cachedTracks.stream()
                    .filter(t -> requestedMmsis.contains(t.getVesselId()))
                    .map(t -> t.toBuilder().build())
                    .collect(Collectors.toList());

            cachedTracks.clear();

            allTracks.addAll(filteredCached);
            log.debug("[CacheQuery] cached {} days -> {} tracks (filtered from {})",
                    split.getCachedDates().size(), filteredCached.size(), totalCachedCount);
        }

        // 2. DB에서 조회 (캐시 미적중 과거 날짜)
        if (split.hasDbRanges()) {
            for (DailyTrackCacheManager.DateRange dbRange : split.getDbRanges()) {
                VesselTracksRequest dbRequest = VesselTracksRequest.builder()
                        .startTime(dbRange.getStart())
                        .endTime(dbRange.getEnd())
                        .vessels(request.getVessels())
                        .build();
                List<CompactVesselTrack> dbTracks = gisService.getVesselTracks(dbRequest);
                allTracks.addAll(dbTracks);
                log.debug("[CacheQuery] DB range {} ~ {} -> {} tracks",
                        dbRange.getStart(), dbRange.getEnd(), dbTracks.size());
            }
        }

        // 3. 오늘 구간 DB 조회 (hourly + 5min)
        if (split.hasTodayRange()) {
            DailyTrackCacheManager.DateRange today = split.getTodayRange();
            VesselTracksRequest todayRequest = VesselTracksRequest.builder()
                    .startTime(today.getStart())
                    .endTime(today.getEnd())
                    .vessels(request.getVessels())
                    .build();
            List<CompactVesselTrack> todayTracks = gisService.getVesselTracks(todayRequest);
            allTracks.addAll(todayTracks);
            log.debug("[CacheQuery] today {} ~ {} -> {} tracks",
                    today.getStart(), today.getEnd(), todayTracks.size());
        }

        // 4. 동일 선박 병합 (캐시 + DB 결과)
        List<CompactVesselTrack> merged = mergeTracksByVessel(allTracks);
        allTracks.clear();

        return merged;
    }

    // ── Semaphore 슬롯 획득 ──

    private boolean acquireSlotWithWait(String queryId) {
        if (activeQueryManager.tryAcquireQuerySlotImmediate(queryId)) {
            return true;
        }

        try {
            boolean acquired = activeQueryManager.tryAcquireQuerySlot(queryId);
            if (!acquired) {
                throw new QueryTimeoutException(
                        "서버가 과부하 상태입니다. " + restQueryTimeout + "초 대기 후 타임아웃되었습니다.");
            }
            return true;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new QueryTimeoutException("쿼리 대기 중 인터럽트가 발생했습니다.");
        }
    }

    // ── 간소화 파이프라인 ──

    private List<CompactVesselTrack> applySimplificationPipeline(List<CompactVesselTrack> tracks) {
        if (tracks == null || tracks.isEmpty()) {
            return tracks;
        }

        tracks = cacheTrackSimplifier.simplify(tracks, CacheTrackSimplifier.SimplificationConfig.builder().build());
        tracks = cacheTrackSimplifier.enforcePointBudget(tracks, maxTotalPoints);

        return tracks;
    }

    // ── 선박별 트랙 병합 ──

    private List<CompactVesselTrack> mergeTracksByVessel(List<CompactVesselTrack> tracks) {
        if (tracks == null || tracks.size() <= 1) {
            return tracks != null ? tracks : Collections.emptyList();
        }

        Map<String, List<CompactVesselTrack>> grouped = tracks.stream()
                .collect(Collectors.groupingBy(CompactVesselTrack::getVesselId));

        if (grouped.values().stream().allMatch(list -> list.size() == 1)) {
            return tracks;
        }

        List<CompactVesselTrack> merged = new ArrayList<>();

        for (Map.Entry<String, List<CompactVesselTrack>> entry : grouped.entrySet()) {
            List<CompactVesselTrack> vesselTracks = entry.getValue();

            if (vesselTracks.size() == 1) {
                merged.add(vesselTracks.get(0));
                continue;
            }

            CompactVesselTrack base = vesselTracks.get(0);
            List<double[]> allGeometry = new ArrayList<>(base.getGeometry() != null ? base.getGeometry() : Collections.emptyList());
            List<String> allTimestamps = new ArrayList<>(base.getTimestamps() != null ? base.getTimestamps() : Collections.emptyList());
            List<Double> allSpeeds = new ArrayList<>(base.getSpeeds() != null ? base.getSpeeds() : Collections.emptyList());
            double totalDistance = base.getTotalDistance();
            double maxSpeed = base.getMaxSpeed();
            int totalPointCount = base.getPointCount();

            for (int i = 1; i < vesselTracks.size(); i++) {
                CompactVesselTrack t = vesselTracks.get(i);
                if (t.getGeometry() != null) allGeometry.addAll(t.getGeometry());
                if (t.getTimestamps() != null) allTimestamps.addAll(t.getTimestamps());
                if (t.getSpeeds() != null) allSpeeds.addAll(t.getSpeeds());
                totalDistance += t.getTotalDistance();
                maxSpeed = Math.max(maxSpeed, t.getMaxSpeed());
                totalPointCount += t.getPointCount();
            }

            CompactVesselTrack mergedTrack = CompactVesselTrack.builder()
                    .vesselId(base.getVesselId())
                    .nationalCode(base.getNationalCode())
                    .shipName(base.getShipName())
                    .shipType(base.getShipType())
                    .shipKindCode(base.getShipKindCode())
                    .geometry(allGeometry)
                    .timestamps(allTimestamps)
                    .speeds(allSpeeds)
                    .totalDistance(totalDistance)
                    .avgSpeed(base.getAvgSpeed())
                    .maxSpeed(maxSpeed)
                    .pointCount(totalPointCount)
                    .build();

            merged.add(mergedTrack);
        }

        log.debug("[MergeVessels] {} tracks -> {} merged vessels", tracks.size(), merged.size());
        return merged;
    }

    // ── 유틸리티 메서드 ──

    private TrackResponse mapTrackResponse(ResultSet rs, int rowNum) throws SQLException {
        return TrackResponse.builder()
                .mmsi(rs.getString("mmsi"))
                .timeBucket(rs.getObject("time_bucket", LocalDateTime.class))
                .trackGeom(rs.getString("track_geom"))
                .distanceNm(rs.getBigDecimal("distance_nm"))
                .avgSpeed(rs.getBigDecimal("avg_speed"))
                .maxSpeed(rs.getBigDecimal("max_speed"))
                .pointCount(rs.getInt("point_count"))
                .build();
    }

    private TrackConverter.VesselInfo getVesselInfo(String mmsi) {
        VesselInfoCache cached = vesselInfoCache.get(mmsi);
        if (cached != null && !cached.isExpired()) {
            return new TrackConverter.VesselInfo(cached.shipName, cached.shipType);
        }

        JdbcTemplate jdbcTemplate = new JdbcTemplate(queryDataSource);
        try {
            String sql = """
                SELECT ship_nm, vessel_type
                FROM signal.t_ais_position
                WHERE mmsi = ?
                LIMIT 1
            """;

            Map<String, Object> result = jdbcTemplate.queryForMap(sql, mmsi);
            String shipName = result.get("ship_nm") != null ? result.get("ship_nm").toString() : "-";
            String shipType = result.get("vessel_type") != null ? result.get("vessel_type").toString() : "-";

            vesselInfoCache.put(mmsi, new VesselInfoCache(shipName, shipType));

            return new TrackConverter.VesselInfo(shipName, shipType);
        } catch (Exception e) {
            return new TrackConverter.VesselInfo("-", "-");
        }
    }

    private static class VesselInfoCache {
        String shipName;
        String shipType;
        long cacheTime;

        VesselInfoCache(String shipName, String shipType) {
            this.shipName = shipName;
            this.shipType = shipType;
            this.cacheTime = System.currentTimeMillis();
        }

        boolean isExpired() {
            return System.currentTimeMillis() - cacheTime > VESSEL_CACHE_TTL;
        }
    }
}
