package com.eactive.eai.util;

import org.json.simple.JSONObject;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

// TOUSE
// kbbank 어댑터에서 업무데이터의 암호화 필드에 대한 암복호화시
// 해당 필드에 대한 get/set 을 수행
public class JsonPathUtil {
	private JsonPathUtil() {

	}

	private static final ObjectMapper objectMapper = new ObjectMapper();

    // ---------------------------------------------------------------
    // JsonNode 기반 단일 파싱 API — 연속 get/set 시 파싱 횟수 절감
    // ---------------------------------------------------------------

	/** JSON 문자열을 JsonNode 트리로 파싱. */
    public static JsonNode toTree(Object json) throws Exception {
    	if(json == null)
    		return null;
    	else if(json instanceof JsonNode)
        	return (JsonNode)json;
    	else if(json instanceof String)
    		return objectMapper.readTree((String)json);
    	else if(json instanceof JSONObject)
    		return objectMapper.readTree(((JSONObject)json).toJSONString());
    	else
    		return objectMapper.readTree(json.toString());
    }

    /** 이미 파싱된 트리에서 경로의 값을 추출. */
    public static String getAt(JsonNode root, String path) {
        JsonNode node = getNodeAtPath(root, path, "/");
        return node != null ? node.asText() : null;
    }
    
    /** 이미 파싱된 트리에서 경로의 값을 추출. */
    public static String getAt(JsonNode root, String path, String delimeter) {
        JsonNode node = getNodeAtPath(root, path, delimeter);
        return node != null ? node.asText() : null;
    }

    /** 이미 파싱된 트리의 경로에 값을 설정 (in-place). */
    public static void setAt(JsonNode root, String path, String value) {
        JsonNode parent = getParentNode(root, getParentPath(path));
        if (parent == null) return;
        String key = getFieldName(path);
        if (parent.isArray()) {
            ((ArrayNode) parent).set(Integer.parseInt(key), objectMapper.convertValue(value, JsonNode.class));
        } else if (parent.isObject()) {
            ((ObjectNode) parent).put(key, value);
        }
    }

    /** JsonNode 트리를 JSON 문자열로 직렬화. */
    public static String fromTree(JsonNode root) throws Exception {
        return objectMapper.writeValueAsString(root);
    }

    // ---------------------------------------------------------------
    // 문자열 기반 API (내부적으로 위 메서드에 위임)
    // ---------------------------------------------------------------

    // 특정 경로의 값을 설정하는 함수
    public static String setValueAtPath(String jsonString, String path, String newValue, boolean isPretty) {
        try {
            JsonNode rootNode = objectMapper.readTree(jsonString);
            setAt(rootNode, path, newValue);
            if(isPretty)
            	return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
            else
            	return objectMapper.writeValueAsString(rootNode);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    // 특정 경로의 값을 가져오는 함수
    public static String getValueAtPath(String jsonString, String path) {
        try {
            return getAt(objectMapper.readTree(jsonString), path);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    // 경로에서 부모 경로를 추출하는 유틸리티 함수
    private static String getParentPath(String path) {
        int lastSlashIndex = path.lastIndexOf('/');
        return lastSlashIndex == 0 ? "/" : path.substring(0, lastSlashIndex);
    }

    // 경로에서 필드 이름 또는 배열 인덱스를 추출하는 유틸리티 함수
    private static String getFieldName(String path) {
        int lastSlashIndex = path.lastIndexOf('/');
        return path.substring(lastSlashIndex + 1);
    }

    // 특정 경로에 해당하는 부모 노드를 가져오는 유틸리티 함수
    private static JsonNode getParentNode(JsonNode rootNode, String parentPath) {
        return getNodeAtPath(rootNode, parentPath, "/");
    }

    // 특정 경로에 해당하는 노드를 가져오는 유틸리티 함수
    private static JsonNode getNodeAtPath(JsonNode rootNode, String path, String delimeter) {
        String[] tokens = path.split(delimeter);
        JsonNode currentNode = rootNode;

        for (String token : tokens) {
            if (token.isEmpty()) continue;

            if (token.matches("\\d+")) {
                // 배열 인덱스 처리
                currentNode = currentNode.get(Integer.parseInt(token));
            } else {
                // 객체 필드 처리
                currentNode = currentNode.get(token);
            }

            if (currentNode == null) {
                return null; // 경로가 존재하지 않음
            }
        }
        return currentNode;
    }

    /**
     * 이미 파싱된 bodyNode에서 removeFromPath 필드를 제거하고 mergeJson을 병합.
     * body의 재파싱 없이 호출 가능하여 파싱 횟수를 1회 절감.
     *
     * body와 mergeJson이 모두 JSON 객체인 경우에만 병합을 수행한다.
     * 어느 한쪽이 JSON 객체가 아니면 mergeJson을 그대로 반환한다 (전체 교체 fallback).
     *
     * @param bodyNode       이미 파싱된 원본 JsonNode
     * @param removeFromPath 제거할 필드의 경로 (예: /encryptedData)
     * @param mergeJson      병합할 JSON 문자열
     * @return 병합된 JSON 문자열, 병합 불가 시 mergeJson 그대로 반환
     */
    public static JsonNode mergeAtRoot(JsonNode bodyNode, String removeFromPath, String mergeJson) {
        try {
            JsonNode mergeNode = objectMapper.readTree(mergeJson);

            if (!bodyNode.isObject() || !mergeNode.isObject()) {
                return bodyNode;
            }

            ObjectNode result = (ObjectNode) bodyNode.deepCopy();

            // removeFromPath 필드 제거
            int lastSlash = removeFromPath.lastIndexOf('/');
            String fieldName = removeFromPath.substring(lastSlash + 1);
            if (lastSlash == 0) {
                result.remove(fieldName);
            } else {
                JsonNode parent = getNodeAtPath(result, removeFromPath.substring(0, lastSlash), "/");
                if (parent instanceof ObjectNode) {
                    ((ObjectNode) parent).remove(fieldName);
                }
            }

            // mergeJson 필드를 body에 병합 (기존 필드 덮어쓰기)
            result.setAll((ObjectNode) mergeNode);
//            return objectMapper.writeValueAsString(result);
            return result;
        } catch (Exception e) {
            return bodyNode;
        }
    }

    /**
     * body에서 removeFromPath 필드를 제거한 뒤, mergeJson의 필드를 body에 병합하여 반환한다.
     * (문자열 기반 overload — 내부적으로 body를 1회 파싱하여 위 메서드에 위임)
     *
     * 사용 예 (toPath="/" 복호화):
     *   body        = {"encryptedData":"...","requestId":"REQ001"}
     *   removeFrom  = /encryptedData
     *   mergeJson   = {"userId":"U001","name":"test"}
     *   결과        = {"requestId":"REQ001","userId":"U001","name":"test"}
     *
     * @param body           원본 JSON 문자열
     * @param removeFromPath 제거할 필드의 경로 (예: /encryptedData)
     * @param mergeJson      병합할 JSON 문자열
     * @return 병합된 JSON 문자열, 병합 불가 시 mergeJson 그대로 반환
     */
    public static String mergeAtRoot(String body, String removeFromPath, String mergeJson) {
        try {
            return objectMapper.writeValueAsString(mergeAtRoot(objectMapper.readTree(body), removeFromPath, mergeJson));
        } catch (Exception e) {
            return mergeJson;
        }
    }

    public static void main(String[] args) {
        String jsonString = "{ \"person\": { \"name\": \"John\", \"age\": 30, \"groups\": [ { \"id\": 1, \"name\": \"Group A\" }, { \"id\": 2, \"name\": \"Group B\" }, { \"id\": 3, \"name\": \"Group C\" } ] } }";

        // System.out.println("Source JSON: \n" + jsonString);
        
        // 경로를 사용하여 값 가져오기 (반복되는 그룹 포함)
        String getPath = "/person/groups/1/name";
        String secondGroupName = getValueAtPath(jsonString, getPath);
        // System.out.println("getPath: " + getPath + " -> " + secondGroupName);

        // 경로를 사용하여 값 설정하기 (반복되는 그룹 포함) 및 Pretty 출력
        String putPath = "/person/groups/2/name";
        String updatedJsonString = setValueAtPath(jsonString, putPath, "Group Z", true);
        // System.out.println("putPath: " + putPath);
        // System.out.println("Updated JSON (Pretty): \n" + updatedJsonString);
        
        getPath = "/person/name";         
        // System.out.println("getPath: " + getPath + " -> " + getValueAtPath(jsonString, getPath));
        getPath = "/person/age";         
        // System.out.println("getPath: " + getPath + " -> " + getValueAtPath(jsonString, getPath));
    }
}
