package incheon.uis.ums.shp;

import org.locationtech.jts.geom.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * WFS DescribeFeatureType을 통해 레이어 스키마를 조회하는 유틸리티
 */
public class WfsSchemaUtil {

    private static final Logger log = LoggerFactory.getLogger(WfsSchemaUtil.class);

    public static class SchemaInfo {
        public final List<FieldDef> attributeFields;
        public final Class<? extends Geometry> geometryClass;

        public SchemaInfo(List<FieldDef> attributeFields, Class<? extends Geometry> geometryClass) {
            this.attributeFields = attributeFields;
            this.geometryClass = geometryClass;
        }
    }

    /**
     * GeoServer WFS DescribeFeatureType 호출하여 스키마 반환
     * 실패 시 null 반환 (호출부에서 fallback 처리)
     *
     * @param baseUrl  gis.server.url (예: http://10.100.232.241/MapPrimeServer)
     * @param typeName 레이어명 (예: incheon:rdl_rdct_l)
     */
    public static SchemaInfo describeFeatureType(String baseUrl, String typeName) {
        try {
            String url = baseUrl + "/map/wfs?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=1.0.0&TYPENAME="
                    + URLEncoder.encode(typeName, StandardCharsets.UTF_8);

            HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setConnectTimeout(10_000);
            conn.setReadTimeout(30_000);
            conn.setRequestMethod("GET");

            int code = conn.getResponseCode();
            if (code != HttpURLConnection.HTTP_OK) {
                log.warn("DescribeFeatureType 요청 실패(HTTP {}): {}", code, url);
                return null;
            }

            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(conn.getInputStream());

            List<FieldDef> attributeFields = new ArrayList<>();
            Class<? extends Geometry> geometryClass = Geometry.class;

            NodeList elements = doc.getElementsByTagNameNS("http://www.w3.org/2001/XMLSchema", "element");
            for (int i = 0; i < elements.getLength(); i++) {
                Element el = (Element) elements.item(i);
                String name = el.getAttribute("name");
                String type = el.getAttribute("type");

                if (name == null || name.isEmpty() || type == null || type.isEmpty()) continue;

                // substitutionGroup이 있는 element는 피처 타입 정의 자체 → 스킵
                if (!el.getAttribute("substitutionGroup").isEmpty()) continue;

                if (type.startsWith("gml:")) {
                    geometryClass = mapGmlToGeometry(type);
                    continue;
                }

                attributeFields.add(new FieldDef(name, mapXsdToJava(type)));
            }

            log.info("DescribeFeatureType 완료 (typeName={}, 필드수={})", typeName, attributeFields.size());
            return new SchemaInfo(attributeFields, geometryClass);

        } catch (Exception e) {
            log.warn("DescribeFeatureType 실패 (typeName={}): {}", typeName, e.getMessage());
            return null;
        }
    }

    private static Class<?> mapXsdToJava(String xsdType) {
        String localType = xsdType.contains(":") ? xsdType.substring(xsdType.indexOf(':') + 1) : xsdType;
        switch (localType) {
            case "decimal":
            case "double":
            case "float":
                return Double.class;
            case "integer":
            case "int":
            case "short":
            case "byte":
                return Integer.class;
            case "long":
                return Long.class;
            default:
                return String.class; // string, date, dateTime 등
        }
    }

    private static Class<? extends Geometry> mapGmlToGeometry(String gmlType) {
        String lower = gmlType.toLowerCase();
        if (lower.contains("multipolygon"))    return MultiPolygon.class;
        if (lower.contains("multilinestring")) return MultiLineString.class;
        if (lower.contains("multipoint"))      return MultiPoint.class;
        if (lower.contains("polygon"))         return Polygon.class;
        if (lower.contains("linestring"))      return LineString.class;
        if (lower.contains("point"))           return Point.class;
        return Geometry.class;
    }
}