package com.zioinfo.kintex.common.geo;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;

import java.util.ArrayList;
import java.util.List;

/**
 * 홀 로컬 좌표계(SRID 0, 미터) 지오메트리 ↔ 좌표 배열 변환 유틸.
 *
 * <ul>
 *   <li>PostGIS {@code ST_AsGeoJSON} 결과(Polygon/LineString) → 좌표 배열 파싱</li>
 *   <li>좌표 배열 → PostGIS {@code ST_GeomFromText} 입력용 WKT 조립(닫힌 링 보정)</li>
 * </ul>
 *
 * <p>순수 좌표 변환만 수행하며 민감정보를 다루지 않는다. 파싱 실패 시 요약 예외만 던진다(스택트레이스 미노출).
 */
public final class GeometryCodec {

    private GeometryCodec() {
    }

    /** GeoJSON Polygon 문자열 → 외곽 링 좌표열 [[x,y], ...]. null/오류 시 빈 리스트. */
    public static List<List<Double>> parsePolygonRing(ObjectMapper mapper, String geoJson) {
        if (geoJson == null || geoJson.isBlank()) {
            return List.of();
        }
        try {
            JsonNode root = mapper.readTree(geoJson);
            JsonNode coords = root.path("coordinates");
            if (!coords.isArray() || coords.isEmpty()) {
                return List.of();
            }
            return toCoordList(coords.get(0)); // Polygon: coordinates[0] = 외곽 링
        } catch (Exception e) {
            return List.of();
        }
    }

    /** GeoJSON LineString 문자열 → 좌표열 [[x,y], ...]. null/오류 시 빈 리스트. */
    public static List<List<Double>> parseLineCoords(ObjectMapper mapper, String geoJson) {
        if (geoJson == null || geoJson.isBlank()) {
            return List.of();
        }
        try {
            JsonNode root = mapper.readTree(geoJson);
            JsonNode coords = root.path("coordinates");
            if (!coords.isArray()) {
                return List.of();
            }
            return toCoordList(coords); // LineString: coordinates = 좌표열
        } catch (Exception e) {
            return List.of();
        }
    }

    /** 좌표열 [[x,y], ...] → {@code POLYGON((x y,x y,...))} WKT. 링을 닫아준다(첫점≠끝점이면 첫점 추가). */
    public static String toPolygonWkt(List<List<Double>> ring) {
        if (ring == null || ring.size() < 3) {
            throw new ApiException(ErrorCode.VALIDATION, "부스 폴리곤은 최소 3개 정점이 필요합니다.");
        }
        List<List<Double>> closed = new ArrayList<>(ring);
        List<Double> first = closed.get(0);
        List<Double> last = closed.get(closed.size() - 1);
        if (!pointEquals(first, last)) {
            closed.add(first);
        }
        StringBuilder sb = new StringBuilder("POLYGON((");
        for (int i = 0; i < closed.size(); i++) {
            List<Double> p = closed.get(i);
            if (p == null || p.size() < 2) {
                throw new ApiException(ErrorCode.VALIDATION, "부스 폴리곤 정점 좌표가 올바르지 않습니다.");
            }
            if (i > 0) {
                sb.append(',');
            }
            sb.append(fmt(p.get(0))).append(' ').append(fmt(p.get(1)));
        }
        return sb.append("))").toString();
    }

    /** 좌표열의 산술 중심(centroid 근사) — 위반 핀 앵커용. 빈 링이면 null. */
    public static List<Double> centroid(List<List<Double>> ring) {
        if (ring == null || ring.isEmpty()) {
            return null;
        }
        double sx = 0;
        double sy = 0;
        int n = 0;
        for (List<Double> p : ring) {
            if (p != null && p.size() >= 2) {
                sx += p.get(0);
                sy += p.get(1);
                n++;
            }
        }
        if (n == 0) {
            return null;
        }
        return List.of(round2(sx / n), round2(sy / n));
    }

    private static List<List<Double>> toCoordList(JsonNode arr) {
        List<List<Double>> out = new ArrayList<>();
        if (arr == null || !arr.isArray()) {
            return out;
        }
        for (JsonNode pt : arr) {
            if (pt.isArray() && pt.size() >= 2) {
                out.add(List.of(pt.get(0).asDouble(), pt.get(1).asDouble()));
            }
        }
        return out;
    }

    private static boolean pointEquals(List<Double> a, List<Double> b) {
        return a != null && b != null && a.size() >= 2 && b.size() >= 2
                && Math.abs(a.get(0) - b.get(0)) < 1e-9 && Math.abs(a.get(1) - b.get(1)) < 1e-9;
    }

    private static String fmt(double d) {
        if (d == Math.floor(d) && !Double.isInfinite(d)) {
            return String.valueOf((long) d);
        }
        return String.valueOf(d);
    }

    private static double round2(double v) {
        return Math.round(v * 100.0) / 100.0;
    }
}
