package gc.mda.kcg.domain.analysis;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import gc.mda.kcg.domain.fleet.ParentResolution;
import gc.mda.kcg.domain.fleet.repository.ParentResolutionRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 어구 그룹/상관관계 직접 DB 조회 서비스.
 * kcg.group_polygon_snapshots, kcg.gear_correlation_scores,
 * kcg.correlation_param_models 를 JdbcTemplate으로 직접 쿼리.
 * ParentResolution 합성은 JPA를 통해 수행.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class VesselAnalysisGroupService {

    private final JdbcTemplate jdbc;
    private final ParentResolutionRepository parentResolutionRepo;
    private final ObjectMapper objectMapper;

    /**
     * 그룹 목록 (최신 스냅샷 per group_key+sub_cluster_id) + parentResolution 합성.
     * 목록에서는 polygon/members를 제외하여 응답 크기를 최소화.
     * polygon·members는 detail API에서만 반환.
     *
     * @param groupType null이면 전체, "GEAR"면 GEAR_IN_ZONE+GEAR_OUT_ZONE만
     */
    public Map<String, Object> getGroups(String groupType) {
        List<Map<String, Object>> rows;

        // LATERAL JOIN으로 최신 스냅샷만 빠르게 조회 (DISTINCT ON 대비 60x 개선)
        String typeFilter = "GEAR".equals(groupType)
                ? "AND group_type IN ('GEAR_IN_ZONE', 'GEAR_OUT_ZONE')"
                : "";

        String sql = """
                SELECT g.*
                FROM (
                  SELECT DISTINCT group_key, sub_cluster_id
                  FROM kcg.group_polygon_snapshots
                  WHERE snapshot_time > NOW() - INTERVAL '1 hour'
                  %s
                ) keys
                JOIN LATERAL (
                  SELECT group_type, group_key, group_label, sub_cluster_id, snapshot_time,
                         ST_AsGeoJSON(polygon)::text 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::text, color
                  FROM kcg.group_polygon_snapshots
                  WHERE group_key = keys.group_key AND sub_cluster_id = keys.sub_cluster_id
                  ORDER BY snapshot_time DESC LIMIT 1
                ) g ON true
                """.formatted(typeFilter);

        try {
            rows = jdbc.queryForList(sql);
        } catch (Exception e) {
            log.warn("group_polygon_snapshots 조회 실패: {}", e.getMessage());
            return Map.of("serviceAvailable", false, "items", List.of(), "count", 0);
        }

        // parentResolution 인덱싱
        Map<String, ParentResolution> resolutionByKey = new HashMap<>();
        for (ParentResolution r : parentResolutionRepo.findAll()) {
            resolutionByKey.put(r.getGroupKey() + "::" + r.getSubClusterId(), r);
        }

        // correlation_scores 실시간 최고 점수 + 후보 수 일괄 조회
        Map<String, Object[]> corrTopByGroup = new HashMap<>();
        try {
            List<Map<String, Object>> corrRows = jdbc.queryForList("""
                    SELECT group_key, sub_cluster_id,
                           MAX(current_score) AS max_score,
                           COUNT(*) FILTER (WHERE current_score > 0.3) AS candidate_count
                    FROM kcg.gear_correlation_scores
                    WHERE model_id = (SELECT id FROM kcg.correlation_param_models WHERE is_default = true LIMIT 1)
                      AND freeze_state = 'ACTIVE'
                    GROUP BY group_key, sub_cluster_id
                    """);
            for (Map<String, Object> cr : corrRows) {
                String ck = cr.get("group_key") + "::" + cr.get("sub_cluster_id");
                corrTopByGroup.put(ck, new Object[]{cr.get("max_score"), cr.get("candidate_count")});
            }
        } catch (Exception e) {
            log.debug("correlation top score 조회 실패: {}", e.getMessage());
        }

        List<Map<String, Object>> items = new ArrayList<>();
        for (Map<String, Object> row : rows) {
            Map<String, Object> item = buildGroupItem(row);

            String groupKey = String.valueOf(row.get("group_key"));
            Object subRaw = row.get("sub_cluster_id");
            Integer sub = subRaw == null ? null : Integer.valueOf(subRaw.toString());
            String compositeKey = groupKey + "::" + sub;
            ParentResolution res = resolutionByKey.get(compositeKey);

            // 실시간 최고 점수 (correlation_scores)
            Object[] corrTop = corrTopByGroup.get(compositeKey);
            if (corrTop != null) {
                item.put("liveTopScore", corrTop[0]);
                item.put("candidateCount", corrTop[1]);
            }

            if (res != null) {
                Map<String, Object> resolution = new LinkedHashMap<>();
                resolution.put("status", res.getStatus());
                resolution.put("selectedParentMmsi", res.getSelectedParentMmsi());
                resolution.put("selectedParentName", res.getSelectedParentName());
                resolution.put("topScore", res.getTopScore());
                resolution.put("confidence", res.getConfidence());
                resolution.put("secondScore", res.getSecondScore());
                resolution.put("scoreMargin", res.getScoreMargin());
                resolution.put("decisionSource", res.getDecisionSource());
                resolution.put("stableCycles", res.getStableCycles());
                resolution.put("approvedAt", res.getApprovedAt());
                resolution.put("manualComment", res.getManualComment());
                item.put("resolution", resolution);
            } else {
                item.put("resolution", null);
            }

            items.add(item);
        }

        return Map.of("serviceAvailable", true, "count", items.size(), "items", items);
    }

    /**
     * 단일 그룹 상세 (최신 스냅샷 + 24시간 이력).
     */
    public Map<String, Object> getGroupDetail(String groupKey) {
        String latestSql = """
                SELECT group_type, group_key, group_label, sub_cluster_id, snapshot_time,
                    ST_AsGeoJSON(polygon)::text 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::text, color
                FROM kcg.group_polygon_snapshots
                WHERE group_key = ?
                ORDER BY snapshot_time DESC
                LIMIT 1
                """;

        String historySql = """
                SELECT snapshot_time,
                    ST_Y(center_point) AS center_lat, ST_X(center_point) AS center_lon,
                    member_count,
                    ST_AsGeoJSON(polygon)::text AS polygon_geojson,
                    members::text, sub_cluster_id, area_sq_nm
                FROM kcg.group_polygon_snapshots
                WHERE group_key = ? AND snapshot_time > NOW() - INTERVAL '24 hours'
                ORDER BY snapshot_time ASC
                """;

        try {
            List<Map<String, Object>> latestRows = jdbc.queryForList(latestSql, groupKey);
            if (latestRows.isEmpty()) {
                return Map.of("serviceAvailable", false, "groupKey", groupKey,
                        "message", "그룹을 찾을 수 없습니다.");
            }

            Map<String, Object> latest = buildGroupItem(latestRows.get(0));

            List<Map<String, Object>> historyRows = jdbc.queryForList(historySql, groupKey);
            List<Map<String, Object>> history = new ArrayList<>();
            for (Map<String, Object> row : historyRows) {
                Map<String, Object> entry = new LinkedHashMap<>();
                entry.put("snapshotTime", row.get("snapshot_time"));
                entry.put("centerLat", row.get("center_lat"));
                entry.put("centerLon", row.get("center_lon"));
                entry.put("memberCount", row.get("member_count"));
                entry.put("polygon", parseGeoJson((String) row.get("polygon_geojson")));
                entry.put("members", parseJsonArray((String) row.get("members")));
                entry.put("subClusterId", row.get("sub_cluster_id"));
                entry.put("areaSqNm", row.get("area_sq_nm"));
                history.add(entry);
            }

            Map<String, Object> result = new LinkedHashMap<>();
            result.put("groupKey", groupKey);
            result.put("latest", latest);
            result.put("history", history);
            return result;

        } catch (Exception e) {
            log.warn("getGroupDetail 조회 실패 groupKey={}: {}", groupKey, e.getMessage());
            return Map.of("serviceAvailable", false, "groupKey", groupKey);
        }
    }

    /**
     * 그룹별 상관관계 점수 목록 (활성 모델 기준).
     */
    public Map<String, Object> getGroupCorrelations(String groupKey, Double minScore) {
        String sql = """
                SELECT s.target_mmsi, s.target_type, s.target_name,
                    s.current_score AS score, s.streak_count AS streak,
                    s.observation_count AS observations, s.freeze_state,
                    0 AS shadow_bonus, s.sub_cluster_id,
                    s.proximity_ratio, s.visit_score, s.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
                WHERE s.group_key = ? AND m.is_active = true
                  AND (? IS NULL OR s.current_score >= ?)
                ORDER BY s.current_score DESC
                """;

        try {
            List<Map<String, Object>> rows = jdbc.queryForList(
                    sql, groupKey, minScore, minScore);

            List<Map<String, Object>> items = new ArrayList<>();
            for (Map<String, Object> row : rows) {
                Map<String, Object> item = new LinkedHashMap<>();
                item.put("targetMmsi", row.get("target_mmsi"));
                item.put("targetType", row.get("target_type"));
                item.put("targetName", row.get("target_name"));
                item.put("score", row.get("score"));
                item.put("streak", row.get("streak"));
                item.put("observations", row.get("observations"));
                item.put("freezeState", row.get("freeze_state"));
                item.put("subClusterId", row.get("sub_cluster_id"));
                item.put("proximityRatio", row.get("proximity_ratio"));
                item.put("visitScore", row.get("visit_score"));
                item.put("headingCoherence", row.get("heading_coherence"));
                item.put("modelId", row.get("model_id"));
                item.put("modelName", row.get("model_name"));
                item.put("isDefault", row.get("is_default"));
                items.add(item);
            }

            return Map.of("groupKey", groupKey, "count", items.size(), "items", items);

        } catch (Exception e) {
            log.warn("getGroupCorrelations 조회 실패 groupKey={}: {}", groupKey, e.getMessage());
            return Map.of("serviceAvailable", false, "groupKey", groupKey, "items", List.of(), "count", 0);
        }
    }

    /**
     * 후보 상세 raw metrics (최근 N건).
     */
    public Map<String, Object> getCandidateMetrics(String groupKey, String targetMmsi) {
        String sql = """
                SELECT observed_at, proximity_ratio, visit_score, activity_sync,
                    dtw_similarity, speed_correlation, heading_coherence, drift_similarity,
                    shadow_stay, shadow_return, gear_group_active_ratio
                FROM kcg.gear_correlation_raw_metrics
                WHERE group_key = ? AND target_mmsi = ?
                ORDER BY observed_at DESC LIMIT 20
                """;
        try {
            List<Map<String, Object>> rows = jdbc.queryForList(sql, groupKey, targetMmsi);
            List<Map<String, Object>> items = new ArrayList<>();
            for (Map<String, Object> row : rows) {
                Map<String, Object> item = new LinkedHashMap<>();
                item.put("observedAt", row.get("observed_at"));
                item.put("proximityRatio", row.get("proximity_ratio"));
                item.put("visitScore", row.get("visit_score"));
                item.put("activitySync", row.get("activity_sync"));
                item.put("dtwSimilarity", row.get("dtw_similarity"));
                item.put("speedCorrelation", row.get("speed_correlation"));
                item.put("headingCoherence", row.get("heading_coherence"));
                item.put("driftSimilarity", row.get("drift_similarity"));
                item.put("shadowStay", row.get("shadow_stay"));
                item.put("shadowReturn", row.get("shadow_return"));
                item.put("gearGroupActiveRatio", row.get("gear_group_active_ratio"));
                items.add(item);
            }
            return Map.of("groupKey", groupKey, "targetMmsi", targetMmsi, "count", items.size(), "items", items);
        } catch (Exception e) {
            log.warn("getCandidateMetrics 실패: {}", e.getMessage());
            return Map.of("groupKey", groupKey, "targetMmsi", targetMmsi, "items", List.of(), "count", 0);
        }
    }

    /**
     * 모선 확정/제외 처리.
     */
    public Map<String, Object> resolveParent(String groupKey, String action, String targetMmsi, String comment) {
        try {
            // 먼저 resolution 존재 확인
            List<Map<String, Object>> existing = jdbc.queryForList(
                    "SELECT id, sub_cluster_id FROM kcg.gear_group_parent_resolution WHERE group_key = ? LIMIT 1",
                    groupKey);
            if (existing.isEmpty()) {
                return Map.of("ok", false, "message", "resolution을 찾을 수 없습니다.");
            }

            Long id = ((Number) existing.get(0).get("id")).longValue();

            if ("confirm".equals(action)) {
                jdbc.update("""
                        UPDATE kcg.gear_group_parent_resolution
                        SET status = 'MANUAL_CONFIRMED', selected_parent_mmsi = ?,
                            manual_comment = ?, approved_at = NOW(), updated_at = NOW()
                        WHERE id = ?
                        """, targetMmsi, comment, id);
            } else if ("reject".equals(action)) {
                jdbc.update("""
                        UPDATE kcg.gear_group_parent_resolution
                        SET rejected_candidate_mmsi = ?, manual_comment = ?,
                            rejected_at = NOW(), updated_at = NOW()
                        WHERE id = ?
                        """, targetMmsi, comment, id);
            } else {
                return Map.of("ok", false, "message", "알 수 없는 액션: " + action);
            }
            return Map.of("ok", true, "action", action, "groupKey", groupKey, "targetMmsi", targetMmsi);
        } catch (Exception e) {
            log.warn("resolveParent 실패: {}", e.getMessage());
            return Map.of("ok", false, "message", e.getMessage());
        }
    }

    // ── 내부 헬퍼 ──────────────────────────────────────────────────────────────

    private Map<String, Object> buildGroupItem(Map<String, Object> row) {
        Map<String, Object> item = new LinkedHashMap<>();
        item.put("groupType", row.get("group_type"));
        item.put("groupKey", row.get("group_key"));
        item.put("groupLabel", row.get("group_label"));
        item.put("subClusterId", row.get("sub_cluster_id"));
        item.put("snapshotTime", row.get("snapshot_time"));
        item.put("polygon", parseGeoJson((String) row.get("polygon_geojson")));
        item.put("centerLat", row.get("center_lat"));
        item.put("centerLon", row.get("center_lon"));
        item.put("areaSqNm", row.get("area_sq_nm"));
        item.put("memberCount", row.get("member_count"));
        item.put("zoneId", row.get("zone_id"));
        item.put("zoneName", row.get("zone_name"));
        item.put("members", parseJsonArray((String) row.get("members")));
        item.put("color", row.get("color"));
        item.put("resolution", null);
        item.put("candidateCount", null);
        return item;
    }

    private Map<String, Object> parseGeoJson(String geoJson) {
        if (geoJson == null || geoJson.isBlank()) return null;
        try {
            return objectMapper.readValue(geoJson, new TypeReference<Map<String, Object>>() {});
        } catch (Exception e) {
            log.debug("GeoJSON 파싱 실패: {}", e.getMessage());
            return null;
        }
    }

    private List<Object> parseJsonArray(String json) {
        if (json == null || json.isBlank()) return List.of();
        try {
            return objectMapper.readValue(json, new TypeReference<List<Object>>() {});
        } catch (Exception e) {
            log.debug("JSON 배열 파싱 실패: {}", e.getMessage());
            return List.of();
        }
    }
}
