package incheon.ags.mrb.style.utils;

import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import incheon.ags.mrb.style.vo.JsonStyleVO;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
 * 스타일 양방향 변환 클래스
 * - JSON ↔ SLD XML 상호 변환
 * - OGC SLD 1.0.0 표준 준수
 */
@Component
public class StyleJsonConverter {

    private final StyleConverter styleConverter;

    public StyleJsonConverter(StyleConverter styleConverter) {
        this.styleConverter = styleConverter;
    }

    // ==================== JSON → SLD ====================

    /**
     * JSON 스타일을 SLD XML로 변환
     *
     * @param jsonStyle JSON 스타일 객체
     * @return SLD XML 문자열
     * @throws Exception 변환 실패 시
     */
    public String convertToSld(JsonStyleVO jsonStyle) throws Exception {
        return styleConverter.convertToSld(jsonStyle);
    }

    // ==================== SLD → JSON ====================

    /**
     * SLD XML을 JSON 스타일로 변환
     *
     * @param sldXml SLD XML 문자열
     * @param layerName 레이어명
     * @return JsonStyleVO 객체
     * @throws Exception 변환 실패 시
     */
    public JsonStyleVO convertToJson(String sldXml, String layerName) throws Exception {
        return convertToJson(sldXml, layerName, null);
    }

    /**
     * SLD XML을 JSON 스타일로 변환 (icon_info 포함)
     *
     * @param sldXml SLD XML 문자열
     * @param layerName 레이어명
     * @param iconInfoMap 아이콘 정보 Map (인덱스 → iconId)
     * @return JsonStyleVO 객체
     * @throws IOException, ParserConfigurationException, SAXException 변환 실패 시
     */
    public JsonStyleVO convertToJson(String sldXml, String layerName, java.util.Map<String, Object> iconInfoMap) throws IOException, ParserConfigurationException, SAXException {
        // 입력 유효성 검사
        validateInput(sldXml);

        // XML 파싱
        Document doc = parseSldDocument(sldXml);

        // LayerType 자동 감지
        String layerType = XmlUtils.detectLayerTypeFromSld(doc);

        // JsonStyleVO 생성 및 기본 설정
        JsonStyleVO jsonStyle = createBaseJsonStyle(doc, layerName, layerType);

        // UserStyle에서 Rule 파싱
        parseUserStyle(doc, jsonStyle, layerType, iconInfoMap);

        return jsonStyle;
    }

    // ==================== Private 메서드 ====================

    /**
     * 입력 유효성 검사
     */
    private void validateInput(String sldXml) {
        if (sldXml == null || sldXml.trim().isEmpty()) {
            throw new IllegalArgumentException("SLD XML이 비어있습니다.");
        }

        String cleanedXml = XmlUtils.preprocessXml(sldXml);
        if (cleanedXml == null || sldXml.isBlank()) {
            throw new IllegalArgumentException("XML이 없거나 비어 있습니다.");
        }
        if (!cleanedXml.trim().startsWith("<")) {
            throw new IllegalArgumentException("유효하지 않은 XML 형식입니다. XML은 '<'로 시작해야 합니다.");
        }
    }

    /**
     * SLD XML 문서 파싱
     */
    private Document parseSldDocument(String sldXml) throws IOException, ParserConfigurationException, SAXException {
        // XML 전처리
        String cleanedXml = XmlUtils.preprocessXml(sldXml);

        // DocumentBuilderFactory 설정 (XXE 공격 방지)
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        factory.setXIncludeAware(false);
        factory.setExpandEntityReferences(false);

        DocumentBuilder builder = factory.newDocumentBuilder();

        return builder.parse(new ByteArrayInputStream(cleanedXml.getBytes("UTF-8")));
    }

    /**
     * 기본 JsonStyleVO 생성
     */
    private JsonStyleVO createBaseJsonStyle(Document doc, String layerName, String layerType) {
        JsonStyleVO jsonStyle = new JsonStyleVO();
        jsonStyle.setLayerName(layerName);
        jsonStyle.setLayerType(layerType);
        jsonStyle.setStyleMode("single"); // 기본값

        // NamedLayer에서 레이어명 추출
        Element namedLayerElement = XmlUtils.getFirstElementByTagName(doc, "NamedLayer");
        if (namedLayerElement != null) {
            Element nameElement = XmlUtils.getFirstElementByTagName(namedLayerElement, "Name");
            if (nameElement != null) {
                String extractedLayerName = nameElement.getTextContent();
                jsonStyle.setLayerName(extractedLayerName);
                jsonStyle.setLayerId(extractedLayerName);
            }
        }

        // layerId가 설정되지 않았으면 layerName과 동일하게 설정
        if (jsonStyle.getLayerId() == null && layerName != null) {
            jsonStyle.setLayerId(layerName);
        }

        return jsonStyle;
    }

    /**
     * UserStyle 파싱
     */
    private void parseUserStyle(Document doc, JsonStyleVO jsonStyle, String layerType, java.util.Map<String, Object> iconInfoMap) {
        Element userStyleElement = XmlUtils.getFirstElementByTagName(doc, "UserStyle");
        if (userStyleElement == null) return;

        Element featureTypeStyleElement = XmlUtils.getFirstElementByTagName(userStyleElement, "FeatureTypeStyle");
        if (featureTypeStyleElement == null) return;

        NodeList rules = featureTypeStyleElement.getElementsByTagNameNS("*", "Rule");

        if (rules.getLength() == 0) {
            // 규칙이 없는 경우 기본값
            jsonStyle.setStyleMode("single");
            return;
        }

        // 규칙이 1개인 경우: Filter 유무로 Single vs Categorized/Graduated 판단
        if (rules.getLength() == 1) {
            Element rule = (Element) rules.item(0);
            Element filter = XmlUtils.getFirstElementByTagName(rule, "Filter");

            if (filter == null) {
                // Filter가 없으면 순수 단일 스타일
                parseSingleStyleRule(rules, jsonStyle, layerType, iconInfoMap);
            } else {
                // Filter가 있으면 항목이 1개인 유형별/범위별 스타일
                parseMultipleStyleRules(rules, jsonStyle, layerType, iconInfoMap);
            }
        } else {
            // 다중 스타일 (범위별 또는 카테고리별)
            parseMultipleStyleRules(rules, jsonStyle, layerType, iconInfoMap);
        }
    }

    /**
     * 단일 스타일 Rule 파싱
     */
    private void parseSingleStyleRule(NodeList rules, JsonStyleVO jsonStyle, String layerType, java.util.Map<String, Object> iconInfoMap) {
        Element rule = (Element) rules.item(0);
        jsonStyle.setStyleMode("single");
        jsonStyle.setStyle(SldParser.parseSingleStyle(rule, layerType, iconInfoMap, 0));

        // 줌 레벨 파싱
        JsonStyleVO.DisplayLevelConfig displayLevel = SldParser.parseDisplayLevel(rule);
        if (displayLevel != null) {
            jsonStyle.setDisplayLevel(displayLevel);
        }
    }

    /**
     * 다중 스타일 Rule 파싱
     */
    private void parseMultipleStyleRules(NodeList rules, JsonStyleVO jsonStyle, String layerType, java.util.Map<String, Object> iconInfoMap) {
        // 첫 번째 Rule의 Filter를 분석하여 graduated vs categorized 판별
        if (rules.getLength() == 0) return;

        Element firstRule = (Element) rules.item(0);
        Element filter = XmlUtils.getFirstElementByTagName(firstRule, "Filter");

        if (filter != null && isGraduatedFilter(filter)) {
            // Graduated 스타일 (범위별)
            parseGraduatedRules(rules, jsonStyle, layerType, iconInfoMap);
        } else {
            // Categorized 스타일 (카테고리별)
            parseCategorizedRules(rules, jsonStyle, layerType, iconInfoMap);
        }
    }

    /**
     * Filter가 graduated(범위별) 타입인지 확인
     */
    private boolean isGraduatedFilter(Element filter) {
        // PropertyIsGreaterThanOrEqualTo와 PropertyIsLessThan이 있으면 graduated
        Element and = XmlUtils.getFirstElementByTagName(filter, "And");
        if (and != null) {
            Element greaterThan = XmlUtils.getFirstElementByTagName(and, "PropertyIsGreaterThanOrEqualTo");
            Element lessThan = XmlUtils.getFirstElementByTagName(and, "PropertyIsLessThan");
            Element lessThanOrEqual = XmlUtils.getFirstElementByTagName(and, "PropertyIsLessThanOrEqualTo");

            return greaterThan != null && (lessThan != null || lessThanOrEqual != null);
        }

        return false;
    }

    /**
     * Graduated 스타일 파싱
     */
    private void parseGraduatedRules(NodeList rules, JsonStyleVO jsonStyle, String layerType, java.util.Map<String, Object> iconInfoMap) {
        jsonStyle.setStyleMode("graduated");

        JsonStyleVO.ClassificationConfig classificationConfig = new JsonStyleVO.ClassificationConfig();

        List<JsonStyleVO.RangeRule> ranges = new ArrayList<>();
        String fieldName = null;
        String classificationMethod = "equalInterval"; // 기본값

        for (int i = 0; i < rules.getLength(); i++) {
            Element rule = (Element) rules.item(i);

            // 첫 번째 Rule에서 field, 분류방법, displayLevel 추출
            if (i == 0) {
                if (fieldName == null) {
                    fieldName = extractFieldNameFromRule(rule);
                }
                // Description/Title에서 분류방법 추출
                String extractedMethod = extractClassificationMethodFromRule(rule);
                if (extractedMethod != null && !extractedMethod.isEmpty()) {
                    classificationMethod = extractedMethod;
                }
                // 줌 레벨 파싱
                JsonStyleVO.DisplayLevelConfig displayLevel = SldParser.parseDisplayLevel(rule);
                if (displayLevel != null) {
                    jsonStyle.setDisplayLevel(displayLevel);
                }
            }

            JsonStyleVO.RangeRule rangeRule = parseGraduatedRule(rule, layerType, iconInfoMap, i);
            if (rangeRule != null) {
                ranges.add(rangeRule);
            }
        }

        // field와 method 설정
        if (fieldName != null) {
            classificationConfig.setField(fieldName);
        }
        classificationConfig.setMethod(classificationMethod);
        classificationConfig.setRanges(ranges);
        jsonStyle.setClassificationConfig(classificationConfig);
    }

    /**
     * Rule의 Description/Title에서 분류방법 추출
     */
    private String extractClassificationMethodFromRule(Element rule) {
        Element description = XmlUtils.getFirstElementByTagName(rule, "Description");
        if (description != null) {
            Element title = XmlUtils.getFirstElementByTagName(description, "Title");
            if (title != null) {
                String titleText = title.getTextContent();
                if (titleText != null && titleText.startsWith("classificationMethod:")) {
                    return titleText.substring("classificationMethod:".length()).trim();
                }
            }
        }
        return null;
    }

    /**
     * Rule의 Filter에서 PropertyName(field) 추출
     */
    private String extractFieldNameFromRule(Element rule) {
        Element filter = XmlUtils.getFirstElementByTagName(rule, "Filter");
        if (filter != null) {
            // And 요소 안에서 먼저 찾기
            Element and = XmlUtils.getFirstElementByTagName(filter, "And");
            if (and != null) {
                NodeList propertyNames = and.getElementsByTagNameNS("*", "PropertyName");
                if (propertyNames.getLength() > 0) {
                    return propertyNames.item(0).getTextContent();
                }
            }
            // And가 없으면 Filter 전체에서 PropertyName 찾기
            NodeList propertyNames = filter.getElementsByTagNameNS("*", "PropertyName");
            if (propertyNames.getLength() > 0) {
                return propertyNames.item(0).getTextContent();
            }
        }
        return null;
    }

    /**
     * 단일 Graduated Rule 파싱
     */
    private JsonStyleVO.RangeRule parseGraduatedRule(Element rule, String layerType, java.util.Map<String, Object> iconInfoMap, int index) {
        JsonStyleVO.RangeRule rangeRule = new JsonStyleVO.RangeRule();

        // Filter에서 min/max 추출
        Element filter = XmlUtils.getFirstElementByTagName(rule, "Filter");
        if (filter != null) {
            Element and = XmlUtils.getFirstElementByTagName(filter, "And");
            if (and != null) {
                // PropertyIsGreaterThanOrEqualTo에서 min 추출
                Element greaterThan = XmlUtils.getFirstElementByTagName(and, "PropertyIsGreaterThanOrEqualTo");
                if (greaterThan != null) {
                    NodeList literals = greaterThan.getElementsByTagNameNS("*", "Literal");
                    if (literals.getLength() > 0) {
                        String minValue = literals.item(0).getTextContent();
                        Number minNumber = parseNumberValue(minValue);
                        rangeRule.setMin(minNumber != null ? minNumber.doubleValue() : null);
                    }
                }

                // PropertyIsLessThan(OrEqualTo)에서 max 추출
                Element lessThan = XmlUtils.getFirstElementByTagName(and, "PropertyIsLessThan");
                if (lessThan == null) {
                    lessThan = XmlUtils.getFirstElementByTagName(and, "PropertyIsLessThanOrEqualTo");
                }
                if (lessThan != null) {
                    NodeList literals = lessThan.getElementsByTagNameNS("*", "Literal");
                    if (literals.getLength() > 0) {
                        String maxValue = literals.item(0).getTextContent();
                        Number maxNumber = parseNumberValue(maxValue);
                        rangeRule.setMax(maxNumber != null ? maxNumber.doubleValue() : null);
                    }
                }
            }
        }

        // Symbolizer에서 스타일 속성 추출 (icon_info 포함)
        JsonStyleVO.StyleConfig styleConfig = SldParser.parseSingleStyle(rule, layerType, iconInfoMap, index);
        rangeRule.setStyle(styleConfig);

        // Rule의 Name에서 라벨 추출 (선택 사항)
        Element nameElement = XmlUtils.getFirstElementByTagName(rule, "Name");
        if (nameElement != null) {
            rangeRule.setLabel(nameElement.getTextContent());
        }

        return rangeRule;
    }

    /**
     * Categorized 스타일 파싱
     */
    private void parseCategorizedRules(NodeList rules, JsonStyleVO jsonStyle, String layerType, java.util.Map<String, Object> iconInfoMap) {
        jsonStyle.setStyleMode("categorized");

        JsonStyleVO.ClassificationConfig classificationConfig = new JsonStyleVO.ClassificationConfig();
        List<JsonStyleVO.CategoryRule> categories = new ArrayList<>();
        String fieldName = null;

        for (int i = 0; i < rules.getLength(); i++) {
            Element rule = (Element) rules.item(i);

            // 첫 번째 Rule에서 field, displayLevel 추출
            if (i == 0) {
                if (fieldName == null) {
                    fieldName = extractCategorizedFieldName(rule);
                }
                // 줌 레벨 파싱
                JsonStyleVO.DisplayLevelConfig displayLevel = SldParser.parseDisplayLevel(rule);
                if (displayLevel != null) {
                    jsonStyle.setDisplayLevel(displayLevel);
                }
            }

            JsonStyleVO.CategoryRule categoryRule = parseCategorizedRule(rule, layerType, iconInfoMap, i);
            if (categoryRule != null) {
                categories.add(categoryRule);
            }
        }

        // field 설정
        if (fieldName != null) {
            classificationConfig.setField(fieldName);
        }
        classificationConfig.setCategories(categories);
        jsonStyle.setClassificationConfig(classificationConfig);
    }

    /**
     * Rule의 PropertyIsEqualTo Filter에서 필드명 추출
     */
    private String extractCategorizedFieldName(Element rule) {
        Element filter = XmlUtils.getFirstElementByTagName(rule, "Filter");
        if (filter != null) {
            // PropertyIsEqualTo 안에서 먼저 찾기
            Element propertyIsEqualTo = XmlUtils.getFirstElementByTagName(filter, "PropertyIsEqualTo");
            if (propertyIsEqualTo != null) {
                NodeList propertyNames = propertyIsEqualTo.getElementsByTagNameNS("*", "PropertyName");
                if (propertyNames.getLength() > 0) {
                    return propertyNames.item(0).getTextContent();
                }
            }
            // PropertyIsEqualTo가 없으면 Filter 전체에서 PropertyName 찾기
            NodeList propertyNames = filter.getElementsByTagNameNS("*", "PropertyName");
            if (propertyNames.getLength() > 0) {
                return propertyNames.item(0).getTextContent();
            }
        }
        return null;
    }

    /**
     * 단일 Categorized Rule 파싱
     */
    private JsonStyleVO.CategoryRule parseCategorizedRule(Element rule, String layerType, java.util.Map<String, Object> iconInfoMap, int index) {
        JsonStyleVO.CategoryRule categoryRule = new JsonStyleVO.CategoryRule();

        // Rule Name에서 라벨/값 추출
        Element nameElement = XmlUtils.getFirstElementByTagName(rule, "Name");
        if (nameElement != null) {
            String name = nameElement.getTextContent();
            categoryRule.setValue(name);
            categoryRule.setLabel(name);
        }

        // Filter에서 카테고리 값 추출 (Name이 없을 경우)
        if (categoryRule.getValue() == null) {
            Element filter = XmlUtils.getFirstElementByTagName(rule, "Filter");
            if (filter != null) {
                Element propertyIsEqualTo = XmlUtils.getFirstElementByTagName(filter, "PropertyIsEqualTo");
                if (propertyIsEqualTo != null) {
                    NodeList literals = propertyIsEqualTo.getElementsByTagNameNS("*", "Literal");
                    if (literals.getLength() > 0) {
                        String value = literals.item(0).getTextContent();
                        categoryRule.setValue(value);
                        categoryRule.setLabel(value);
                    }
                }
            }
        }

        // Symbolizer에서 스타일 속성 추출 (icon_info 포함)
        JsonStyleVO.StyleConfig styleConfig = SldParser.parseSingleStyle(rule, layerType, iconInfoMap, index);
        categoryRule.setStyle(styleConfig);

        return categoryRule;
    }

    /**
     * 문자열을 Number로 파싱
     */
    private Number parseNumberValue(String value) {
        try {
            if (value.contains(".")) {
                return Double.parseDouble(value);
            } else {
                return Integer.parseInt(value);
            }
        } catch (NumberFormatException e) {
            return 0;
        }
    }

}
