package gc.mda.kcg.domain.fleet;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import gc.mda.kcg.config.CacheConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class GroupPolygonService {

    private final JdbcTemplate jdbcTemplate;
    private final CacheManager cacheManager;
    private final ObjectMapper objectMapper;

    private static final long CACHE_TTL_MS = 5 * 60_000L; // 5분
    private volatile long lastCacheTime = 0;

    private static final String LATEST_GROUPS_SQL = """
            SELECT group_type, group_key, group_label, snapshot_time,
                   ST_AsGeoJSON(polygon) AS polygon_geojson,
                   ST_Y(center_point) AS center_lat, ST_X(center_point) AS center_lon,
                   area_sq_nm, member_count, zone_id, zone_name, members, color
            FROM kcg.group_polygon_snapshots
            WHERE snapshot_time = (SELECT MAX(snapshot_time) FROM kcg.group_polygon_snapshots)
            ORDER BY group_type, member_count DESC
            """;

    private static final String GROUP_DETAIL_SQL = """
            SELECT group_type, group_key, group_label, snapshot_time,
                   ST_AsGeoJSON(polygon) AS polygon_geojson,
                   ST_Y(center_point) AS center_lat, ST_X(center_point) AS center_lon,
                   area_sq_nm, member_count, zone_id, zone_name, members, color
            FROM kcg.group_polygon_snapshots
            WHERE group_key = ?
            ORDER BY snapshot_time DESC
            LIMIT 1
            """;

    private static final String GROUP_HISTORY_SQL = """
            SELECT group_type, group_key, group_label, snapshot_time,
                   ST_AsGeoJSON(polygon) AS polygon_geojson,
                   ST_Y(center_point) AS center_lat, ST_X(center_point) AS center_lon,
                   area_sq_nm, member_count, zone_id, zone_name, members, color
            FROM kcg.group_polygon_snapshots
            WHERE group_key = ? AND snapshot_time > NOW() - CAST(? || ' hours' AS INTERVAL)
            ORDER BY snapshot_time DESC
            """;

    private static final String GROUP_CORRELATIONS_SQL = """
            SELECT s.target_mmsi, s.target_type, s.target_name,
                   s.current_score, s.streak_count, s.observation_count,
                   s.freeze_state, s.shadow_bonus_total,
                   r.proximity_ratio, r.visit_score, r.heading_coherence,
                   m.id AS model_id, m.name AS model_name, m.is_default
            FROM kcg.gear_correlation_scores s
            JOIN kcg.correlation_param_models m ON s.model_id = m.id
            LEFT JOIN LATERAL (
                SELECT proximity_ratio, visit_score, heading_coherence
                FROM kcg.gear_correlation_raw_metrics
                WHERE group_key = s.group_key AND target_mmsi = s.target_mmsi
                ORDER BY observed_at DESC LIMIT 1
            ) r ON TRUE
            WHERE s.group_key = ? AND s.current_score >= ? AND m.is_active = TRUE
            ORDER BY m.is_default DESC, s.current_score DESC
            """;

    private static final String GEAR_STATS_SQL = """
            SELECT COUNT(*) AS gear_groups,
                   COALESCE(SUM(member_count), 0) AS gear_count
            FROM kcg.group_polygon_snapshots
            WHERE snapshot_time = (SELECT MAX(snapshot_time) FROM kcg.group_polygon_snapshots)
              AND group_type IN ('GEAR_IN_ZONE', 'GEAR_OUT_ZONE')
            """;

    /**
     * 어구 그룹 집계 통계 (최신 스냅샷 기준).
     */
    public Map<String, Integer> getGearStats() {
        try {
            return jdbcTemplate.queryForObject(GEAR_STATS_SQL, (rs, rowNum) -> Map.of(
                    "gearGroups", rs.getInt("gear_groups"),
                    "gearCount", rs.getInt("gear_count")
            ));
        } catch (Exception e) {
            log.warn("getGearStats failed: {}", e.getMessage());
            return Map.of("gearGroups", 0, "gearCount", 0);
        }
    }

    /**
     * 특정 어구 그룹의 연관성 점수 (멀티모델).
     */
    public List<Map<String, Object>> getGroupCorrelations(String groupKey, double minScore) {
        try {
            return jdbcTemplate.query(GROUP_CORRELATIONS_SQL, (rs, rowNum) -> {
                Map<String, Object> row = new java.util.LinkedHashMap<>();
                row.put("targetMmsi", rs.getString("target_mmsi"));
                row.put("targetType", rs.getString("target_type"));
                row.put("targetName", rs.getString("target_name"));
                row.put("score", rs.getDouble("current_score"));
                row.put("streak", rs.getInt("streak_count"));
                row.put("observations", rs.getInt("observation_count"));
                row.put("freezeState", rs.getString("freeze_state"));
                row.put("shadowBonus", rs.getDouble("shadow_bonus_total"));
                row.put("proximityRatio", rs.getObject("proximity_ratio"));
                row.put("visitScore", rs.getObject("visit_score"));
                row.put("headingCoherence", rs.getObject("heading_coherence"));
                row.put("modelId", rs.getInt("model_id"));
                row.put("modelName", rs.getString("model_name"));
                row.put("isDefault", rs.getBoolean("is_default"));
                return row;
            }, groupKey, minScore);
        } catch (Exception e) {
            log.warn("getGroupCorrelations failed for {}: {}", groupKey, e.getMessage());
            return List.of();
        }
    }

    /**
     * 최신 스냅샷의 전체 그룹 폴리곤 목록 (5분 캐시).
     */
    @SuppressWarnings("unchecked")
    public List<GroupPolygonDto> getLatestGroups() {
        Cache cache = cacheManager.getCache(CacheConfig.GROUP_POLYGONS);
        long now = System.currentTimeMillis();

        if (cache != null && (now - lastCacheTime) < CACHE_TTL_MS) {
            Cache.ValueWrapper wrapper = cache.get("data");
            if (wrapper != null) {
                return (List<GroupPolygonDto>) wrapper.get();
            }
        }

        List<GroupPolygonDto> results = jdbcTemplate.query(LATEST_GROUPS_SQL, this::mapRow);

        if (cache != null) {
            cache.put("data", results);
            lastCacheTime = now;
        }
        return results;
    }

    /**
     * 특정 그룹의 최신 상세 정보.
     */
    public GroupPolygonDto getGroupDetail(String groupKey) {
        List<GroupPolygonDto> results = jdbcTemplate.query(GROUP_DETAIL_SQL, this::mapRow, groupKey);
        return results.isEmpty() ? null : results.get(0);
    }

    /**
     * 특정 그룹의 시간별 히스토리.
     */
    public List<GroupPolygonDto> getGroupHistory(String groupKey, int hours) {
        return jdbcTemplate.query(GROUP_HISTORY_SQL, this::mapRow, groupKey, String.valueOf(hours));
    }

    private GroupPolygonDto mapRow(ResultSet rs, int rowNum) throws SQLException {
        Object polygonObj = null;
        String polygonJson = rs.getString("polygon_geojson");
        if (polygonJson != null) {
            try {
                polygonObj = objectMapper.readValue(polygonJson, new TypeReference<Map<String, Object>>() {});
            } catch (Exception e) {
                log.warn("Failed to parse polygon GeoJSON: {}", e.getMessage());
            }
        }

        List<Map<String, Object>> members = List.of();
        String membersJson = rs.getString("members");
        if (membersJson != null) {
            try {
                members = objectMapper.readValue(membersJson, new TypeReference<>() {});
            } catch (Exception e) {
                log.warn("Failed to parse members JSON: {}", e.getMessage());
            }
        }

        return GroupPolygonDto.builder()
                .groupType(rs.getString("group_type"))
                .groupKey(rs.getString("group_key"))
                .groupLabel(rs.getString("group_label"))
                .snapshotTime(rs.getString("snapshot_time"))
                .polygon(polygonObj)
                .centerLat(rs.getDouble("center_lat"))
                .centerLon(rs.getDouble("center_lon"))
                .areaSqNm(rs.getDouble("area_sq_nm"))
                .memberCount(rs.getInt("member_count"))
                .zoneId(rs.getString("zone_id"))
                .zoneName(rs.getString("zone_name"))
                .members(members)
                .color(rs.getString("color"))
                .build();
    }
}
