package incheon.ags.mrb.upload.service.util;

import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.NoSuchAuthorityCodeException;
import org.geotools.api.referencing.operation.TransformException;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Polygon;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.api.referencing.operation.MathTransform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 좌표계 유효성 검증 유틸리티.
 * 대한민국(지도 서비스) 영역 밖에 있는 데이터를 걸러내기 위해 사용한다.
 */
public final class CoordinateValidationUtil {

    private static final Logger logger = LoggerFactory.getLogger(CoordinateValidationUtil.class);

    private static final Envelope KOREA_BOUNDS_3857;
    private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
    private static final double MAX_ABS_COORDINATE = 30_000_000.0;
    private static final double PROJECTED_MIN_ABS = 1_000.0;

    static {
        Envelope transformed;
        try {
            CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326", true);
            CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857", true);
            MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);

            Coordinate[] wgs84Coords = new Coordinate[] {
                new Coordinate(120.0, 30.0),
                new Coordinate(120.0, 43.0),
                new Coordinate(135.0, 43.0),
                new Coordinate(135.0, 30.0),
                new Coordinate(120.0, 30.0)
            };
            Polygon wgs84Polygon = GEOMETRY_FACTORY.createPolygon(wgs84Coords);
            Geometry webMercatorGeom = JTS.transform(wgs84Polygon, transform);
            transformed = webMercatorGeom.getEnvelopeInternal();
        } catch (TransformException | FactoryException e) {
            if (logger.isWarnEnabled()) {
                logger.warn(e.getMessage(), e);
            }
            // 좌표 변환 실패 시 안전한 기본값(대한민국 대략 범위) 사용
            transformed = new Envelope(13200000, 15600000, 3600000, 5300000);
        }
        KOREA_BOUNDS_3857 = transformed;
    }

    private CoordinateValidationUtil() {}

    /**
     * 주어진 Envelope이 대한민국 서비스 영역 안에 포함되는지 확인한다.
     */
    public static boolean isWithinKorea(Envelope envelope) {
        if (envelope == null || envelope.isNull()) {
            return false;
        }
        return KOREA_BOUNDS_3857.contains(envelope);
    }

    /**
     * 대한민국(웹 머서터) 기본 경계 반환.
     */
    public static Envelope getKoreaBounds3857() {
        return new Envelope(KOREA_BOUNDS_3857);
    }

    /**
     * 단일 좌표가 웹 머서터 합리 범위 안인지 검사.
     */
    public static boolean isReasonableCoordinate(double x, double y) {
        return Math.abs(x) <= MAX_ABS_COORDINATE && Math.abs(y) <= MAX_ABS_COORDINATE;
    }

    /**
     * Envelope이 합리 범위 안인지 검사.
     */
    public static boolean isReasonableEnvelope(Envelope envelope) {
        if (envelope == null || envelope.isNull()) {
            return false;
        }
        return isReasonableCoordinate(envelope.getMinX(), envelope.getMinY())
            && isReasonableCoordinate(envelope.getMaxX(), envelope.getMaxY());
    }

    /**
     * 위도/경도 좌표 범위(-180~180, -90~90)인지 검사.
     */
    public static boolean isLatLonEnvelope(Envelope envelope) {
        if (envelope == null || envelope.isNull()) {
            return false;
        }
        double minX = envelope.getMinX();
        double maxX = envelope.getMaxX();
        double minY = envelope.getMinY();
        double maxY = envelope.getMaxY();

        return Double.isFinite(minX) && Double.isFinite(maxX)
                && Double.isFinite(minY) && Double.isFinite(maxY)
                && minX >= -180.0 && maxX <= 180.0
                && minY >= -90.0 && maxY <= 90.0;
    }

    /**
     * 선택한 SRID와 주어진 Envelope의 좌표 범위가 논리적으로 일치하는지 확인한다.
     * SRID가 명확하지 않거나 Envelope이 비어 있으면 true를 반환하여 검증을 건너뛴다.
     */
    public static boolean isEnvelopeConsistentWithSrid(Envelope envelope, int srid) {
        if (envelope == null || envelope.isNull() || srid <= 0) {
            return true;
        }

        if (!isReasonableEnvelope(envelope)) {
            return false;
        }

        boolean isLatLon = isLatLonEnvelope(envelope);

        if (srid == 4326 || srid == 4258) {
            return isLatLon;
        }

        if (srid == 3857 || srid == 900913 || srid == 102100) {
            return !isLatLon;
        }

        // 국내에서 사용하는 TM 계열(51xx, 52xx 등)은 일반적으로 1,000m 이상의 좌표값을 가진다.
        if (srid >= 5000 && srid <= 6000) {
            return !isLatLon && hasProjectedMagnitude(envelope);
        }

        return true;
    }

    private static boolean hasProjectedMagnitude(Envelope envelope) {
        double maxAbs = Math.max(
            Math.max(Math.abs(envelope.getMinX()), Math.abs(envelope.getMaxX())),
            Math.max(Math.abs(envelope.getMinY()), Math.abs(envelope.getMaxY()))
        );
        return maxAbs >= PROJECTED_MIN_ABS;
    }
}

