package incheon.product.common.geo;

import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.crs.CRSFactory;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.api.referencing.crs.GeographicCRS;
import org.geotools.api.referencing.crs.ProjectedCRS;
import org.geotools.api.referencing.datum.GeodeticDatum;
import org.geotools.api.referencing.operation.MathTransform;
import org.geotools.api.referencing.operation.TransformException;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.store.ReprojectingFeatureCollection;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.datum.BursaWolfParameters;
import org.geotools.referencing.datum.DefaultGeodeticDatum;
import org.locationtech.jts.geom.Geometry;

import java.util.HashMap;
import java.util.Map;

/**
 * 좌표계 변환 유틸리티.
 * 기존 CoordinateUtils의 기능을 통합하고 한국 좌표계(Datum 6162) TOWGS84 교정을 포함.
 *
 * 지원 좌표계: EPSG:3857(Web Mercator), EPSG:4326(WGS84),
 *              EPSG:5179(Korea Central), EPSG:5181(인천 기본), EPSG:5186(Korea 2000)
 */
public final class CoordinateConverter {

    private CoordinateConverter() {}

    /** 한국 좌표계 TOWGS84 Bursa-Wolf 파라미터 (Datum 6162) */
    private static final double DX = -115.80;
    private static final double DY = 474.99;
    private static final double DZ = 674.11;
    private static final double EX = 1.16;
    private static final double EY = -2.31;
    private static final double EZ = -1.63;
    private static final double PPM = 6.43;
    private static final String KOREA_DATUM_CODE = "6162";

    /**
     * 한국 좌표계(Datum 6162)의 TOWGS84 파라미터를 교정한 CRS를 생성한다.
     *
     * @param projectedCRS 교정 대상 ProjectedCRS
     * @return 교정된 CRS (한국 좌표계가 아닌 경우 원본 반환)
     */
    public static CoordinateReferenceSystem createCorrectedCRS(ProjectedCRS projectedCRS) throws FactoryException {
        if (!(projectedCRS.getDatum() instanceof DefaultGeodeticDatum)) {
            return projectedCRS;
        }
        if (!KOREA_DATUM_CODE.equals(projectedCRS.getDatum().getIdentifiers().iterator().next().getCode())) {
            return projectedCRS;
        }

        BursaWolfParameters bwp = new BursaWolfParameters(DefaultGeographicCRS.WGS84.getDatum());
        bwp.dx = DX;
        bwp.dy = DY;
        bwp.dz = DZ;
        bwp.ex = EX;
        bwp.ey = EY;
        bwp.ez = EZ;
        bwp.ppm = PPM;

        CRSFactory crsFactory = ReferencingFactoryFinder.getCRSFactory(null);
        GeographicCRS geographicCRS = projectedCRS.getBaseCRS();

        Map<String, Object> props = new HashMap<>(DefaultGeographicCRS.getProperties(geographicCRS));
        props.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, bwp);

        GeodeticDatum datum = new DefaultGeodeticDatum(
                props,
                geographicCRS.getDatum().getEllipsoid(),
                geographicCRS.getDatum().getPrimeMeridian()
        );
        geographicCRS = crsFactory.createGeographicCRS(props, datum, geographicCRS.getCoordinateSystem());

        return crsFactory.createProjectedCRS(
                new HashMap<>(DefaultGeographicCRS.getProperties(projectedCRS)),
                geographicCRS,
                projectedCRS.getConversionFromBase(),
                projectedCRS.getCoordinateSystem()
        );
    }

    /**
     * FeatureCollection을 EPSG:3857(Web Mercator)로 변환한다.
     *
     * @param features 원본 FeatureCollection
     * @return EPSG:3857로 변환된 FeatureCollection
     */
    public static SimpleFeatureCollection reprojectTo3857(SimpleFeatureCollection features) throws FactoryException {
        CoordinateReferenceSystem sourceCRS = features.getSchema().getGeometryDescriptor()
                .getCoordinateReferenceSystem();

        if (sourceCRS instanceof ProjectedCRS) {
            sourceCRS = createCorrectedCRS((ProjectedCRS) sourceCRS);
        }

        CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857");
        return new ReprojectingFeatureCollection(features, sourceCRS, targetCRS);
    }

    /**
     * 단일 Geometry를 EPSG:3857에서 지정 좌표계로 변환한다.
     *
     * @param geometry 변환 대상 Geometry (EPSG:3857)
     * @param targetSrid 변환 목표 좌표계 SRID
     * @return 변환된 Geometry
     */
    public static Geometry reprojectFrom3857(Geometry geometry, int targetSrid)
            throws FactoryException, TransformException {

        CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:3857");
        CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:" + targetSrid);

        if (targetCRS instanceof ProjectedCRS) {
            targetCRS = createCorrectedCRS((ProjectedCRS) targetCRS);
        }

        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
        return JTS.transform(geometry, transform);
    }

    /**
     * 단일 Geometry를 소스 좌표계에서 목표 좌표계로 변환한다.
     *
     * @param geometry   변환 대상 Geometry
     * @param sourceSrid 소스 좌표계 SRID
     * @param targetSrid 목표 좌표계 SRID
     * @return 변환된 Geometry
     */
    public static Geometry reproject(Geometry geometry, int sourceSrid, int targetSrid)
            throws FactoryException, TransformException {

        if (sourceSrid == targetSrid) {
            return geometry;
        }

        CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:" + sourceSrid);
        CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:" + targetSrid);

        if (sourceCRS instanceof ProjectedCRS) {
            sourceCRS = createCorrectedCRS((ProjectedCRS) sourceCRS);
        }
        if (targetCRS instanceof ProjectedCRS) {
            targetCRS = createCorrectedCRS((ProjectedCRS) targetCRS);
        }

        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
        return JTS.transform(geometry, transform);
    }
}
